i have another "movie database" application in react. At the mount it renders movies based on api key which is set to "new movies". Then i have useEffect which update movie list based on searchbar and its value. Problem is, it renders new movies and just after that renders movies based on searchbar value which is empty. I know that useEffect is running on mount. What is best practice to use it this way? Or is there any better hook for this particular use? Thank you.
React.useEffect(() => {
fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}, [searchValue]);
Hey #JSpadawan best practice is to use filter like-
const [data,setData] = useState()
useEffect(()=>{
const apiData = fetch(APILINK).then((res)=>res.json()).catch(err)
if(apiData.length>0){
setData(apiData)
}
},[])
This will set the data of api.. now use filter like-
const [searchValue, setSearchValue]= useState()
const filterData = data && data.filter((data)=>data.includes(searchValue))
After this you can use filterdata any where you want. If you still facing issue just lemme know, i will help you more.
Thanks
You can set the movies with the useEffect, but I would recommend having a secondary useEffect that would be for the seach bar. You should try to avoid making tons of API calls as the more you make, the more it will slow down the application. So have one useEffect that will set the data, and a second one that you can filter out the ones that match the search bar.
If the searchValue is updated every key stroke then searching for a movie with a long title would create tons of API calls. You could put the fetch call in an async/await function so you get the data, then set it, then you can filter with a different effect.
useEffect(() => {
const getData = async () => {
await fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}
getData();
}, []); // empty array will run once on component load, or add a dependency of when you want it to re-call the api
Then a second useEffect for filtering
useEffect(() => {
let searchTextFilter = movies.filter(i => {
return (!searchValue || (searchValue && i.Title.toLowerCase().includes(searchFilter?.toLowerCase() )))
});
}, [searchValue])
Related
I'm very new to JavaScript and the world of React. I've learning hooks, and tried to fetch some API for searchbar and it doesn't work.
I'm trying to grab the data from the url (its array) and search bar to filter items by its title.
function Search() {
const [searchTerm, setSearchTerm] = useState([]);
const [text,setText] = useState([]);
const getAPI = async() => {
const response = await fetch("https://fakestoreapi.com/products")
const data = await response.json()
setText(data.Search)
}
useEffect( () => {
getAPI()
}, [])
return <div>
<input
placeholder="searching"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}/>
</div>
};
there is not any key with Search name inside response data you should setState all your data and then filter with input text value.
Please read useEffect documentation.
useEffect( () => {
getAPI()
}, [])
The above code only runs when the component is mounted. When the user changes the value of input, Search component rerenders, because its state changes. But useEffect will not be executed because you have provided an empty array of dependencies.
You have declared getAPI inside your component. So you should probably add it to the array of dependencies of useEffect.
You should call getAPI in onChange of the input. So it fetches the data from server based on the query parameters.
You have not used searchTerm inside getAPI function.
Be aware of infinite loop caused by useEffect.
I'm working on web scraping a news website to show top headlines and URLs. In my backend, I put each title and URL in an object within an array, which works fine. When I fetch this information in React, it is not working as expected.
I used the useState hook to initialize an empty array, where I would put each object containing the title and URL. Then, I map through that array and render each title and URL to the page.
When I refresh the webpage, it takes several seconds for each title and URL to pop up, and additionally, they are all the same title and URL.
It seems that my array is not being updated properly by putting in the same article information each time I set the state. However, I do not understand why it is taking so long for all the information to show up on the webpage, and do not know how to make it a faster process. I want all the information to be on the page after the user hits refresh, not appear after several seconds at a time. Could anyone help me see where I'm going wrong?
Here is my code:
import {useState} from 'react';
const News = () => {
const [articles, setArticles] = useState([])
fetch('http://localhost:8000/news')
.then(response => {return response.json()})
.then(data => {
data.forEach(article => {
setArticles([...articles, {
title: article.article_title,
url: article.article_url}])
})
})
.catch(err => console.log(err))
return (
<div>
{articles.map(article => {
return (
<div className="indv-article">
<h1 className="article-title" key={article.title}>{article.title}</h1>
<p className='article-url' key={article.url}>{article.url}</p>
</div>);
})}
</div>
)
}
export default News
Couple of things that may solve your issues.
First: In React, all side-effects (such as data fetching, for example) should be handled inside a useEffect hook.
Second: As stated in #Khorne07's answer, the key attribute should be on the root DOM node that is being returned from the map.
Third: I don't really know the purpose of looping through your data to set the state. If the reason you are doing this is because the response contains other information that you are not interested to display and you just want the title and url for each article, I suggest you to create an adapter function that will receive this data as a parameter and return just the information that you are interested in.
Additional: You can use a loading state to show a loading indicator while the data is being fetched and improve user experience.
Putting it all together:
const News = () => {
const [articles, setArticles] = useState([])
const [loading, setLoading] = useState(false)
const adaptArticles = (data) => {
return data.map(({ article_title, article_url }) => ({
title: article_title,
url: article_url
}))
}
useEffect(() => {
setLoading(true)
fetch('http://localhost:8000/news')
.then(response => {return response.json()})
.then(data => setArticles((prevArticles) => prevArticles.concat(adaptArticles(data))))
.catch(err => console.log(err))
.finally(() => {
setLoading(false)
})
}, []) //Insert the corresponding dependencies (if any) in the dependencies array so the useEffect hook gets executed when any of these dependencies change.
if(loading) //Return some loading indicator, like a Spinner for example.
return (
<div>
{articles.map(article => {
return (
<div className="indv-article" key={article.title}>
<h1 className="article-title">{article.title}</h1>
<p className='article-url'>{article.url}</p>
</div>);
})}
</div>
)
}
Edited:
You have some errors on your current code:
First of all, and, as mentioned by the accepted answer, all side effects like fetch calls should be inside a useEffect hook.
The second error is related to the way you are updating your state array. When your new state depends on the previous state value, you should use the callback function inside your setState function, in order to have your data correctly synchronized with the previous value. And in this particular example you are also calling a setState function inside a loop, which is a bad idea and can potentially drive your app into unexpected behavior. The best approach is described in the code snippet bellow.
fetch('http://localhost:8000/news')
.then(response => {return response.json()})
.then(data => {
const articlesArray = []
data.forEach(article => {
articlesArray.push({
title: article.article_title,
url: article.article_url
})
setArticles(currentArticles => [...currentArticles, ...articlesArray])
})
})
.catch(err => console.log(err))
And on the map function, the key attribute should be on the root node you are returning:
{articles.map(article => {
return (
<div className="indv-article" key={article.title}>{article.title}>
<h1 className="article-title"</h1>
<p className='article-url'</p>
</div>);
})}
I currently have a component that holds a posts array, this component takes in a prop called sortBy which tells the component to either fetch data sorted by popular or new. The way my component is set up is upon initialization the posts are fetched then stored in redux, I only get new posts if my posts is empty. That way I'm not constantly fetching new data.
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
const { sortby } = props;
// here I would like to check if props has changed
if (Object.keys(posts).length === 0) {
dispatch(getPostsAsync(sortby)).then((response) => {
setLoading(false);
});
}
}, [dispatch, props, posts]);
Now I would like to check if the sortBy props has changed value. So originally it might be sortby='-numLikes' but when I sortby 'new' it should then be sortby='-createdAt' after it changes I would like to tell Redux to fetch new data.
All you need to do to get the behavior you want is to change the useEffect to:
const posts = useSelector((state) => state.posts.posts);
const { sortby } = props;
useEffect(() => {
dispatch(getPostsAsync(sortby)).then((response) => {
setLoading(false);
});
}, [dispatch, sortby]);
Then the useEffect will run every time sortby is changed.
I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..
I need to fetch some data and set the state, but it gives me infinite loop and I don't know how to fix this.
example in my Routes.tsx:
// get all posts
useEffect(() => {
Axios.get('/api/posts', config)
.then(res => setAllPosts(res.data))
.catch(err => console.log(err))
}, [config]);
if I don't put 'config' in dependency, it will show results only on refresh, but then it wont give me infinite loop.
here is my project: https://github.com/marinavrataric/social_network
Try using useStata[] with useEffect[]
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios.get('/api/posts', config)
.then(res => setAllPosts(res.data))
.catch(err => console.log(err))
setData(result);
}, []);
return (
<ul>
{data.posts.map(item => (
// stuff
))}
</ul>
);
As you are calling your API in useEffect and also updating the state in useEffect too, and useEffect without dependency is called every time the component renders, so here is the loop:
** call the useEffect > call API> updating state using setState> as the state is update, call the useEffect **
So you need to add a dependency for useEffect which is not changed by the setState. And then, whenever the value of the dependentis changed, the API will be called.
BTW, its better not to implement API call in useEffect. Rather You should try using readux and thunk.