Cannot match dynamic routes like /recipes/:id/ingredients - node.js

I am using the npm package react-router-dom#4.0.0-beta.6 for my react app.
I am able to match routes like:
/trends
/news
/recipes
But I can't match routes like:
/recipes/:id/ingredients or /recipes/1234/ingredients
If I switch from /recipes/1234 to /recipes/1234/ingredients I only match the route /recipes/:id but not /recipes/:id/ingredients.
The app looks like this:
const factory = (name, path) => {
return (props) => {
const { children, match, routes } = props;
const { params } = match;
// convert the child routes from /recipes/:id/ingredients to /recipes/1234/ingredients
const others = routes.map(x => pathToRegexp.compile(x.path)(params));
return (
<div>
<h3>{name}</h3>
<ul>
{others.map(x =>
<Link key={x} to={x}>{x}</Link>
)}
</ul>
{children}
</div>
);
};
};
const rootPattern = '/recipes/:id';
const routes = [
{
path: rootPattern,
component: factory('Root', rootPattern),
routes: [
{
path: `${rootPattern}/ingredients`,
component: factory('Ingredients', `${rootPattern}/ingredients`),
},
{
path: `${rootPattern}/steps`,
component: factory('Steps', `${rootPattern}/steps`),
},
],
},
];
// wrap <Route> and use this everywhere instead, then when
// sub routes are added to any route it'll work
const RouteWithSubRoutes = (route) => (
<Route path={route.path} render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes}/>
)}/>
);
const RouteConfigExample = () => (
<Router>
<div>
<ul>
<li><Link to="/recipes/1234">Sandwiches</Link></li>
</ul>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route}/>
))}
</div>
</Router>
);
To be fair I don't really understand why my app doesn't match the dynamic routes.
You can find a complete example at https://github.com/jbrieske/react-router-config.

Related

Data not reloading until refresh reactJS

I am currently building a website with two views in which a user can toggle between both views (images seen below) On both views I am allowing a user to update a database and both views display data regarding the state of that database. However, when I update the data on one toggle view of the data and switch to the other view, I can't see the updated data until I refresh.
I am wrapping my react application around this user context provider shown below
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState();
const [courseList, setCourseList] = useState();
const [semesterList, setSemesterList] = useState();
const [userMajor, setMajor] = useState();
const checkContext = useCallback(() => {
axios
.get("/auth/check", { withCredentials: true })
.then(({ data }) => {
if (data.auth) {
setIsAuthenticated(true);
setUser(data.user);
setCourseList(data.courseList);
setSemesterList(data.semesterList);
setMajor(data.major);
setIsLoading(false);
} else {
setIsAuthenticated(false);
setMajor(undefined);
setCourseList(undefined);
setSemesterList(undefined);
setUser(undefined);
setIsLoading(false);
}
})
.catch(() =>
console.log(
"Something went wrong while trying to fetch your auth status."
)
);
}, []);
useEffect(() => {
checkContext();
}, [checkContext]);
return (
<UserContext.Provider
value={{
isLoading,
isAuthenticated,
user,
courseList,
semesterList,
userMajor,
checkContext,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
This is where I toggle between views
const Home = () => {
const [selected, setSelected] = useState(false);
const handleChange = () => {
setSelected(!selected);
setSelected(!selected);
};
let view;
if (!selected) {
view = <CardView />;
} else {
view = <SemesterView />;
}
return (
<>
<ToggleButton
className="toggler"
value="toggle"
selected={selected}
onChange={handleChange}
>
<ViewModuleIcon />
</ToggleButton>
{view}
</>
);
};
export default Home;
For keeping this shorter, here is one of views in which I update the database using a checkbox form.
const Home = () => {
const { courseList } = useContext(UserContext);
const initialMajorState = {
majorName: "",
majorCode: "",
majorComponentFamilies: [],
};
const [majorData, setMajorData] = useState(initialMajorState);
const [checkedCourses, setCheckedCourses] = useState(courseList);
const [componentLimit, setComponentLimit] = useState([]);
const [componentFamilyLimit, setComponentFamilyLimit] = useState([]);
const [isLoadingData, setIsLoadingData] = useState(true);
/**
* updates the checked courses, the database, as well as the limits for both components and componentfamilys
* #param checkedCourse is the course that is being checkedd
* #param checkedComponentName is the course family name that is being updated
* #param componentIndex is the course component to which the course belongs to
* #param componentFamilyIndex is which componentFamily the course belongs to
*/
const handleOnChange = (
checkedCourse,
checkedComponentName,
componentIndex,
componentFamilyIndex
) => {
// copy the componentLimit array
const updatedComponentLimit = [...componentLimit];
// copy the componentFamilyLimit array
const updatedComponentFamilyLimit = [...componentFamilyLimit];
const updatedCheckedCourses = checkedCourses?.includes(checkedCourse)
? checkedCourses?.filter((name) => name !== checkedCourse)
: [...(checkedCourses ?? []), checkedCourse];
let difference = updatedCheckedCourses.filter(
(x) => !checkedCourses.includes(x)
);
// This means that we added the course
if (difference.length > 0) {
// Add the course to the databas
UserCourseService.add(checkedCourse);
// Add the course to the checked list for that component
updatedComponentLimit[componentIndex].checked.push(checkedCourse);
// if that component is now satisfied then we want to add that to the component family's satisfied components
if (
updatedComponentLimit[componentIndex].checked.length >=
updatedComponentLimit[componentIndex].limit
) {
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.push(checkedComponentName);
}
}
// if the difference length is none then that means we unchecked a box
else {
// Delete the course from our database
UserCourseService.delete(checkedCourse);
// Add the course to the updatedComponentLimit array at the component index where course lies
updatedComponentLimit[componentIndex].checked.pop(checkedCourse);
// Pop that component family (pop will return null if not found anyways)
updatedComponentFamilyLimit[
componentFamilyIndex
].checkedComponentFamily.pop(checkedComponentName);
}
// set the componentFamilyLimit array and componentLimit array and checked courses
setComponentLimit(updatedComponentLimit);
setComponentFamilyLimit(updatedComponentFamilyLimit);
setCheckedCourses(updatedCheckedCourses);
};
/**
* returns a boolean determining if a checkbox is disabled or not
* #param course deals with which course we are checking should be disabled or not
* #param componentIndex deals with which component we are looking at
* #param componentFamilyIndex deals with which compoentFamily we are looking at
*/
const isDisabled = (course, componentIndex, componentFamilyIndex) => {
// if the component family limit has been reached then just return if the checkbox has already been checked
if (
componentFamilyLimit[componentFamilyIndex].checkedComponentFamily
.length >= componentFamilyLimit[componentFamilyIndex].limit
)
return componentLimit[componentIndex].checked.indexOf(course) === -1;
// if the component family limit has not been reached then check if the component itself has been meet and
// if the element has been checked already
else {
return (
componentLimit[componentIndex].checked.length >=
componentLimit[componentIndex].limit &&
// If not already checked
componentLimit[componentIndex].checked.indexOf(course) === -1
);
}
};
/**
* gets the data after searching for a course in the searchbar from the backend. Initializes the limit arrays and checked courses
* #param query is the name of the major we are getting data from in the backend
*/
const getData = async (query) => {
// Initializing the state for our global hooks in this function
var newCheckedComponentList = [];
var newCheckedComponentFamilyList = [];
// gets the response data in regards to the major we are interested in from the backend
MajorDataService.find(query)
.then((response) => {
// sets the major data to the response data
setMajorData(response.data);
// if the data exists then we want to initialize the other stuff
if (response.data.majorComponentFamilies) {
// Looping through the component families
response.data.majorComponentFamilies.forEach((componentFamily) => {
// gets the limit from the component family
var componentFamilyLimit = parseInt(
componentFamily.required_num_components
);
var newComponentFamilyList = [];
// Loops through each of the components in each component family
componentFamily.component_list.forEach((component) => {
// courselimit on each component
var courseLimit = parseInt(component.required_num_courses);
// the course list for a component
var newUserCourseList = [];
// Loops through all of the courses in each component
component.course_list.forEach((course) => {
// if the courselist includes the course then add that to the course list
if (courseList.includes(course)) {
newUserCourseList.push(course);
}
});
// Add to the checked array for components
newCheckedComponentList.push({
checked: newUserCourseList,
limit: courseLimit,
});
if (newUserCourseList.length === courseLimit) {
newComponentFamilyList.push(component.component_name);
}
});
newCheckedComponentFamilyList.push({
checkedComponentFamily: newComponentFamilyList,
limit: componentFamilyLimit,
});
});
setComponentLimit(newCheckedComponentList);
setComponentFamilyLimit(newCheckedComponentFamilyList);
setIsLoadingData(false);
}
})
.catch((e) => {
console.log(e);
});
};
const renderCourseRequirements = (
requiredCourses,
componentIndex,
componentFamilyIndex,
componentFamilyName
) => {
return (
<Grid container spacing={1} key={[uuid()]}>
{requiredCourses.map((course, index) => {
return (
<Grid key={[uuid()]} item xs={6} lg={4} xl={4}>
<Card
key={[uuid()]}
className={
checkedCourses.includes(course)
? "CourseCardCompleted"
: "CourseCard"
}
variant="outlined"
>
<CardHeader
key={[uuid()]}
action={
<Checkbox
name={course}
value={course}
className="header"
disabled={isDisabled(
course,
componentIndex,
componentFamilyIndex
)}
checked={checkedCourses.includes(course)}
onChange={() =>
handleOnChange(
course,
componentFamilyName,
componentIndex,
componentFamilyIndex
)
}
/>
}
title={
<Typography
key={[uuid()]}
className="CourseTitle"
color="textSecondary"
gutterBottom
>
{course}
</Typography>
}
></CardHeader>
<CardContent key={[uuid()]} className="content"></CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
var componentCount = 0;
const renderComponents = (components, componentFamilyIndex) => {
return (
<Grid
container
spacing={1}
direction="column"
alignItems="center"
justifyContent="center"
key={[uuid()]}
>
{components.map((component, index) => {
var temp_index = componentCount;
componentCount = componentCount + 1;
return (
<Grid key={[uuid()]} item className="grid" xs={12} lg={4} xl={2}>
<Card
key={[uuid()]}
className={
componentLimit[temp_index].checked.length >=
componentLimit[temp_index].limit
? "ComponentCardCompleted"
: "ComponentCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentTitle"
color="textSecondary"
gutterBottom
>
{component.component_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Courses: {component.required_num_courses}
</Typography>
{renderCourseRequirements(
component.course_list,
temp_index,
componentFamilyIndex,
component.component_name
)}
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
const renderComponentFamilies = (majorComponentFamilies) => {
return (
<Masonry className="masonry" columns={3} spacing={2} key={[uuid()]}>
{majorComponentFamilies.map((componentFamily, index) => {
if (componentFamily.component_list.length > 1)
return (
<Card
key={[uuid()]}
className={
componentFamilyLimit[index].checkedComponentFamily.length >=
componentFamilyLimit[index].limit
? "ComponentFamilyCardCompleted"
: "ComponentFamilyCard"
}
variant="outlined"
>
<CardContent key={[uuid()]}>
<Typography
key={[uuid()]}
className="ComponentFamilyTitle"
color="textSecondary"
gutterBottom
>
{componentFamily.component_family_name}
</Typography>
<Typography
key={[uuid()]}
className="pos"
color="textSecondary"
>
Required # of Components :{" "}
{componentFamily.required_num_components}
</Typography>
{renderComponents(componentFamily.component_list, index)}
</CardContent>
</Card>
);
return (
<div key={[uuid()]}>
{renderComponents(componentFamily.component_list, index)}
</div>
);
})}
</Masonry>
);
};
return (
<div className="container home">
<SearchBar
key={[uuid()]}
placeholder="Enter Major Name ..."
data={MajorNames}
onChange={(value) => getData(value)}
/>
<div className="mui-grid">
{Array.isArray(majorData.majorComponentFamilies) &&
majorData.majorComponentFamilies.length > 0 &&
!isLoadingData ? (
<>
<h1>{majorData.majorName} </h1>
{renderComponentFamilies(majorData.majorComponentFamilies)}
</>
) : (
<></>
)}
</div>
</div>
);
};

How can I take a value from an input tag in a TSX component, and use that value in a Node JS file in a different directory?

I'm working on a personal project where I'm pulling an API through Fetch; at the moment I can send the call from my index.js file to a TSX component that calls the API URL when my SearchButton component is clicked, but the search term needs to be declared in index.js.
Here's my SearchButton code (TSX):
import React, { useState } from 'react'
function SearchButton() {
const [ newsResponse, setNewsResponse ]= useState(null);
function queryOnClick() {
fetch(`http://localhost:4000/news-api`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
setNewsResponse(result);
})
.catch((ex) => {
console.log('error:', ex);
});
}
return (
<div className="theme--white">
<button className="search__button padding-1 margin-1 margin-left-6" onClick={queryOnClick}>
Click to search
</button>
{newsResponse && newsResponse.articles ? (
<div className="results__container padding-2 theme--mist">
{newsResponse.articles.map((article: {
title: React.ReactNode;
author: string;
content: string;
url: string;
}) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">{article.title}</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">Written by: {article.author || 'An uncredited author'}</h3>
<p className="article__content">
{article.content.length > 150 ?
`${article.content.substring(0, 150)}... [Article shortened - Click the URL below to read more]` : article.content
}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
) : null}
</div>
);
}
export default SearchButton;
I want to change that so a user can search for an article from the API by using a HTML input to submit a topic which would amend the API URL. For instance, if I searched Bitcoin, it would search https://API-${Bitcoin}.com. Due to CORS policy blocking, I can't just call the API in my TSX file as it has to go from localhost:3000 > localhost:4000 via the Node JS file.
At the moment, my input renders the user's query into the console, but I can't seem to get it over to my index.js file. How can I pass a value that's either in the console.log, or from the input's value, through to my Node JS index.js file?
Here's my SearchBar file that handles my Input (TSX):
import React, { Component } from 'react';
type SearchBarProps = {
searchNews: (text: string) => void;
}
type SearchBarState = {
searchString: string;
}
class SearchBar extends Component<SearchBarProps, SearchBarState> {
static defaultProps = {
searchNews: (text: string) => {}
}
state = {
searchString: ''
}
searchNews = (e: any) => {
const { searchString } = this.state
if(e.key === 'Enter' && searchString !== '') {
e.preventDefault();
e.stopPropagation();
this.props.searchNews(searchString)
console.log(searchString)
}
}
onSearchTextChange = (e: any) => {
this.setState({
searchString: e.target.value.trim()
})
}
render() {
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={this.state.searchString}
onChange={this.onSearchTextChange}
onKeyPress={e => this.searchNews(e)} placeholder="Search" />
</div>
</form>
</div>
);
}
}
export default SearchBar;
...And here's my index.js Node JS file (JS):
/*
* Libs
*/
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();
/*
* Constants
*/
const PORT = 4000;
const API_KEY = 'x';
const SEARCH_QUERY = "Bitcoin";
const SORT_BY = "popularity";
const PAGE_SIZE = 10;
/*
* Setup CORS - This is needed to bypass NewsAPI CORS Policy Blocking by rerouting request to localhost
*/
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
/*
* Setup to request NewsAPI data using Fetch API
*/
app.get('/news-api', function (req, res) {
fetch(`https://newsapi.org/v2/everything?q=${SEARCH_QUERY}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
res.json(result);
})
.catch((ex) => {
console.log('error:', ex);
res.status(400).send({
message: 'This is an error!',
error: ex
});
});
})
/*
* Start Backend API Proxy server
*/
app.listen(PORT, () => {
console.log(`=================`)
console.log(`API Connected!`)
console.log(`Listening at http://localhost:${PORT}`)
console.log(`=================`)
})
TLDR:
I have a TSX component that is an input (A - value={this.state.searchString}).
I want that input's value to go to a Node JS file to append a URL via a const (B - const SEARCH_QUERY).
I know what to pull from A, and where to put it in B, but don't know how to do so.
Full tech stack
Using Fetch API, React, TypeScript, Node JS and Webpack.
File paths
SearchButton: project/frontend/src/components/SearchButton/SearchButton.tsx
SearchBar: project/frontend/src/components/SearchBar/SearchBar.tsx
Node JS handler: project/backend/index.js
Essentially what you are asking here is how to pass data from the frontend to the backend. The way to do this is by including the user's search term in your fetch request to the backend. You can either include it in the body of a POST request or include it as a query string in the URL. You would need to use the body for passing large amounts of data, but something as simple as a search term can be done with a query string.
Front End
Include the current search term as a query parameter of your fetch request. I am using encodeURIComponent to apply percent-encoding to special characters.
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
...
You are missing the communication between your SearchButton and SearchBar components. I am not sure where these two components are in relation to each other on your page. If they are siblings then you will need to lift the searchString state and the queryOnClick function up to a shared parent.
I rearranged all of your components so that you have access to the right state in the right places.
import React, { useState } from "react";
function SearchButton({ onClick }: { onClick: () => void }) {
return (
<button
className="search__button padding-1 margin-1 margin-left-6"
onClick={onClick}
>
Click to search
</button>
);
}
interface SearchBarProps {
searchNews: () => void;
searchString: string;
setSearchString: (s: string) => void;
}
function SearchBar({ searchNews, searchString, setSearchString }: SearchBarProps) {
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && searchString !== "") {
e.preventDefault();
e.stopPropagation();
searchNews();
}
};
const onSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchString(e.target.value.trim());
};
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={searchString}
onChange={onSearchTextChange}
onKeyPress={handleKeyPress}
placeholder="Search"
/>
</div>
</form>
</div>
);
}
interface Article {
title: string;
author: string;
content: string;
url: string;
}
interface NewsResponse {
articles: Article[];
}
function ArticleList({ articles }: NewsResponse) {
return (
<div className="results__container padding-2 theme--mist">
{articles.map((article) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">
{article.title}
</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">
Written by: {article.author || "An uncredited author"}
</h3>
<p className="article__content">
{article.content.length > 150
? `${article.content.substring(
0,
150
)}... [Article shortened - Click the URL below to read more]`
: article.content}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
);
}
function SearchPage() {
const [newsResponse, setNewsResponse] = useState<NewsResponse | null>(null);
const [searchString, setSearchString] = useState("");
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
headers: { "Content-Type": "application/json" }
})
.then((response) => response.json())
.then((result) => {
console.log("result:", result);
setNewsResponse(result);
})
.catch((ex) => {
console.log("error:", ex);
});
}
return (
<div className="theme--white">
<SearchBar
searchNews={queryOnClick}
searchString={searchString}
setSearchString={setSearchString}
/>
<SearchButton onClick={queryOnClick} />
{newsResponse && newsResponse.articles ? (
<ArticleList articles={newsResponse.articles} />
) : null}
</div>
);
}
export default SearchPage;
Back End
You need to access the search term from the search parameter of the request URL. We use the req.params property to get a dictionary of params. We can use your previous search term "Bitcoin" as the default value if there was no search param on the request.
I'm not certain if we need to encode again here or not -- you'll want to play with that.
app.get('/news-api', function (req, res) {
const searchQuery = req.params.search || "Bitcoin";
fetch(`https://newsapi.org/v2/everything?q=${searchQuery}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
...

API call in render method in React inside map

I have a userId array and I need to show the list of names related to that array. I want to call API call inside the render method and get the username. But this is not working. How can I fix this issue?
Below is my render method:
render(){
...
return(
<div>
{this.state.users.map(userId => {
return (
<div> {this.renderName(userId )} </div>
)
})}
</div>
)
...
}
Below is the renderName function:
renderName = (userId) => {
axios.get(backendURI.url + '/users/getUserName/' + userId)
.then(res => <div>{res.data.name}</div>)
}
Basically you cannot use asynchronous calls inside a render because they return a Promise which is not valid JSX. Rather use componentDidMount and setState to update the users array with their names.
Generally, you do not change state or fetch data in the render method directly. State is always changed by actions/events (clicks, input or whatever). The render method is called everytime a prop/state changes. If you change the state within the render method directly, you end up having an infinite loop.
You should use the lifecycle methods or hooks to load data from an api. Here's an example from the official React FAQ: https://reactjs.org/docs/faq-ajax.html
This will not render anything as the API calls are asynchronous and since renderName function isn't returning anything, it'll return undefined.
You should create a function, which will call api for all the userIds and update in state
getNames = () => {
const promises = [];
this.state.users.forEach((userId) => {
promises.push(axios.get(backendURI.url+'/users/getUserName/'+userId));
})
// Once all promises are resolved, update the state
Promise.all(promises).then((responses) => {
const names = responses.map((response) => response.data.names);
this.setState({names});
})
}
Now you can call this function in either componentDidMount or componentDidUpdate, whenever users data is available.
And finally, you can iterate over names directly and render them
<div>
{this.state.names.map((name) => {
return <div> {name} </div>;
})}
</div>
You could make user name it's own component:
const request = (id) =>
new Promise((resolve) =>
setTimeout(resolve(`id is:${id}`), 2000)
);
const UserName = React.memo(function User({ userId }) {
const [name, setName] = React.useState('');
React.useEffect(() => {
//make the request and set local state to the result
request(userId).then((result) => setName(result));
}, [userId]);
return <div> {name} </div>;
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [1, 2],
};
}
render() {
return (
<ul>
{this.state.users.map((userId) => (
<UserName key={userId} userId={userId} />
))}
</ul>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
export default ()=> {
let [users,setUsers] = useState([]);
useEffect(()=>{
let fetchUsersInfoRemote = Promise.all([...Array(10)].map(async (_,index)=>{
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${index+1}`);
return response.data;
}
catch(error) {
return ;
}
}));
fetchUsersInfoRemote.then(data=> setUsers(data));
},[]);
return (
<div className="App">
<ul>
{
users.map(user=>(<li><pre>{JSON.stringify(user,null,2)}</pre></li>))
}
</ul>
</div>
);
}

Error Getting Data from API using FetchData

i am using a react hook : useEffect for getting data from an API and i'm also using .map for rendering an array of product.
after run the npm , there is an error :
xhr.js:178 GET http://localhost:3000/api/products 404 (Not Found)
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios'
function HomeScreen (props) {
// menggunakan hooks
const [products, setProduct] = useState([]);
// fetchDate from server // sama dengan component did mount
useEffect( () =>{
const fetchData = async () => {
const { data } = await axios.get("/api/products");
setProduct(data)
}
return () => {
fetchData();
}
}, [])
return(
<div>
<ul className="products">
{
products.map( product =>
<li key={product.id}>
<div className="product" >
<Link to = {`/product/${ product.id }`}>
<img className='product-image' src={ product.image } alt={product.name} />
</Link>
<div className="product-name">
<Link to = {`/product/${ product.id }`}>{ product.name }</Link>
</div>
<div className="product-cat">{ product.brand }</div>
<div className="product-price"><b>IDR</b> { product.price }</div>
<div className="product-rating">{ product.rating } Stars ( { product.reviews } Reviews )</div>
</div>
</li>
)
}
</ul>
</div>
)
}
export default HomeScreen
and there is code from server.js
const express = require('express');
const data = require('./database/data')
const app = express();
app.get('/api/products', ( req, res) => {
res.send(data.Products)
})
const PORT = process.env.PORT || 5001
app.listen(PORT, () => {
console.log(`Server is Running on http://localhost:${PORT}`)
} )
i really hope this problem solving of this code, thank you
You are calling your API on localhost:3000, but your API should be running on localhost:5001
const { data } = await axios.get("http://localhost:5001/api/products");
You want to initialize your state with brackets "[]" instead of "{}"
const [products, setProducts] = useState([])
Also, you might want to code defensively by adding a turnery operation to check to see if products is 'truthy' if it's not, then the user will see some kind of error message i.e. the part after the ":".
{products ? products.map( product => {}) : <div>handle error</div> }
finally i've got this solve
i miss the set proxy server of the front end site, thanks !
Just you need to do is install cors by using below command:
npm install core
//Then use it in server file like this:
var cors = require('cors')
app.use(cors())

How to paginate a list of posts belonging to a specific category in GatsbyJS

I have developed a blog with Gatsby JS and I managed to add categories to each markdown file so that I can create pages by querying a specific category and list all the posts related to that category.
Now, I'm trying to add pagination to avoid an infinite list of posts inside each category page.
I have been following the official guide here: https://www.gatsbyjs.org/docs/adding-pagination/
And this is the code I came up with:
gatsby-node.js
const path = require('path')
const _ = require("lodash")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
const articleTemplate = path.resolve(`src/templates/article.js`)
const categoryTemplate = path.resolve("src/templates/category.js")
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 2000
) {
edges {
node {
html
id
frontmatter {
path
title
categories
}
}
}
}
}
`).then(result => {
if (result.errors) {
reject(result.errors)
}
const articles = result.data.allMarkdownRemark.edges
const articlesPerPage = 6
const numPages = Math.ceil(articles.length / articlesPerPage)
//Creating a page for each article
articles.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: articleTemplate,
//context: {}, // additional data can be passed via context
})
})
// Categories pages:
let categories = []
// Iterate through each article, putting all found categories into `categories`
_.each(articles, edge => {
if (_.get(edge, "node.frontmatter.categories")) {
categories = categories.concat(edge.node.frontmatter.categories)
}
})
Array.from({ length: numPages }).forEach((category, _, i) => {
createPage({
path: i === 0 ? `/${_.kebabCase(category)}/` : `/${_.kebabCase(category)}/${i + 1}`,
component: categoryTemplate,
context: {
limit: articlesPerPage,
skip: i * articlesPerPage,
category,
},
})
})
})
)
})
/templates/categories.js
import React from "react"
import PropTypes from "prop-types"
import Layout from '../layouts/layout'
import ArticleCard from '../components/articles/articlecard'
// Components
import { Link, graphql } from "gatsby"
const _ = require("lodash")
const Categories = ({ pageContext, data }) => {
const { category } = pageContext
const { edges } = data.allMarkdownRemark
return (
<Layout>
<section class="hero is-info is-medium has-text-centered">
<div class="hero-body">
<div class="container">
<h1 class="title is-top">
{category}
</h1>
</div>
</div>
</section>
<div class="section">
<div class="container">
<div class="columns is-multiline">
{edges.map(({ node }) => {
const { path, title, date } = node.frontmatter
return (
<div class="column is-half">
<div class="card">
<div class="card-header">
<p class="card-header-content">{date}</p>
</div>
<div class="card-content">
<Link to={_.kebabCase(category)}><span class="tag is-success has-padding">{category}</span></Link>
<Link to={path}>
<h2 class="title is-4">{title}</h2>
</Link>
</div>
<div class="card-footer">
<div class="card-footer-item"><Link to={path}><div class="button is-success is-inverted is-fullwidth">Read</div></Link></div>
<div class="card-footer-item"><Link to={path}><div class="button is-info is-inverted is-fullwidth">Share on Linkedin</div></Link></div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</Layout>
)
}
Categories.propTypes = {
pageContext: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Categories
export const pageQuery = graphql`
query($skip: Int!, $limit: Int!, $category: String) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { categories: { in: [$category] } } }
limit: $limit
skip: $skip
) {
totalCount
edges {
node {
frontmatter {
title
path
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
This does not work and it is now throwing the error: error gatsby-node.js returned an error, TypeError: _.kebabCase is not a function
However kebabCase was used smoothly before modifying the query to add pagination, so I don't think the problem is actually there.
Does anyone have any clue?
Thank you!
You are declaring the variable "underscore" twice:
1- from the lodash library
2- from the forEach function:
Array.from({ length: numPages }).forEach((category, _, i)
just change the the second variable to another arbitrary name like this:
Array.from({ length: numPages }).forEach((category, otherName, i)

Resources