Issue
I'm looking for the most optimal way to fetch data using useEffect() when the fetch function is used in more than one place.
Situation
Currently, I have a parent component (ItemContainer) and a child component (SearchBar). ItemContainer should fetch the all the possible list of items using getItemList() functions. I'm executing this function within the useEffect() during the first render, and also passing it down to SearchBar component, so that when a user submits a search term, it will update itemList state by triggering getItemList() in ItemContainer.
This actually works just as I expected. However, my issue is that
I'm not really sure whether it is okay to define getItemList() outside the useEffect() in this kind of situation. From what I've been reading (blog posts, react official docs) it is generally recommended that data fetching function should be defined inside the useEffect(), although there could be some edge cases. I'm wondering if my case applies as this edge cases.
Is it okay to leave the dependency array empty in useCallback? I tried filling it out using searchTerm, itemList, but none of them worked - and I'm quite confused why this is so.
I feel bad that I don't fully understand the code that I wrote. I would appreciate if any of you could enlighten me with what I'm missing here...
ItemContainer
const ItemContainer = () => {
const [itemList, setItemList] = useState([]);
const getItemList = useCallback( async (searchTerm) => {
const itemListRes = await Api.getItems(searchTerm);
setItemList(itemListRes)
}, []);
useEffect(() => {
getItemList();
}, [getItemList]);
return (
<main>
<SearchBar search={getItemList} />
<ItemList itemList={itemList} />
</main>
)
}
SearchBar
const SearchBar = ({ search }) => {
const [searchTerm, setSearchTerm] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
search(searchTerm);
setSearchTerm('');
}
const handleChange = (e) => {
setSearchTerm(e.target.value)
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder='Enter search term...'
value={searchTerm}
onChange={handleChange}
/>
<button>Search</button>
</form>
)
}
Here are my answers.
Yes, it is okay. What's inside useCallback is "frozen" respect to
the many ItemConteiner function calls that may happen. Since the
useCallback content accesses only setItemList, which is also a
frozen handler, there'll be no problems.
That's also correct, because an empty array means "dependent to
nothing". In other words, the callback is created once and keeps
frozen for all the life of the ItemContainer.
Instead, this is something weird:
useEffect(() => {
getItemList();
}, [getItemList]);
It works, but it has a very little sense. The getItemList is created once only, so why make an useEffect depending to something never changes?
Make it simpler, by running once only:
useEffect(() => {
getItemList();
}, []);
Related
import { useEffect, useState } from "react";
function Popular() {
const [popular, setPopular] = useState([]);
useEffect(() => {
getPopular();
}, []);
const getPopular = async () => {
const api = await fetch(
`https://api.spoonacular.com/recipes/random?apiKey=${process.env.REACT_APP_RECIPE_API_KEY}&number=9`
);
const data = await api.json();
setPopular(data.recipes);
};
return (
<div>
{popular.map((recipe) => {
return (
<div>
<p>{recipe.title}</p>
</div>
);
})}
</div>
);
}
export default Popular;
I am pretty new to React, and I encountered this issue which I have been trying to fix to no avail. The code is a component that is to return a list of recipe title to my app. I am fetching data from an API in the getPopular() function which is set to the setPopular function variable of the useState() method. But when I save my work and return to the browser, the changes does not display. The list does not display, but if I console.log(data.recipes) it displays on the console.
Before now, if I made any change (maybe a text change) the React app renders it without reloading, but now I have to reload the page before I see the change.
Please how do I fix this issue? So that I can see changes without having to reload the page manually.
Not saying that this is the problem, but getPopular() should not be called after its declaration? By this I mean:
const getPopular = async () => {
const api = await fetch(
/...
};
useEffect(() => {
getPopular();
}, []);
Another thing that bugs me is, although JS/React is case sensitive, I really think you should avoid having a const called popular, since your functions is Popular.
Please, let me know if the order did matter for your problem. I will review some react classes soon, if i get another inside, i'll let you know.
React: am I doing it wrong?
So I’ve been working with React for a while, and I’ve been able to create some really cool projects by utilizing what React has to offer; Hooks, props, etc. The thing is. My workflow always comes to a stop and I end up having a bad case of spaghetti-code when I try to pass variables and state between local and global functions. 9/10 I end up getting stuck and disobeying the React Hooks Rules, and have hack my way out of it with a very vanilla JS way of doing things. And then I think to myself: “What a wonderf… No, I mean: Why am I using React if I end up writing vanilla JS when I try to do something that is a bit more advanced than rendering components on a page?”. Is my approach all wrong?
Here's an example: I have a webpage which fetches to an API written in Express, which in turn returns data from a MongoDB database. I use a custom hook to fetch with an async function, and then I display everything on a page. I have a functional component that renders out everything. I also send some query-data with the API fetch, which in this example is a string representation of numbers, which in turn sets the limit of how many elements are gathered from the database. And on the useEffect hook – which is inside the custom hook I mentioned earlier – I have the number of elements to display as a dependency, so that I fetch the API every time that value changes. That value in turn, is chosen by a slider between 1-1000. Every time I fetch, the component renders again and everything flashes. This is because the data from the DB, as well as my h1, slider, and p-tags, are all in the same component. I want to avoid that, so my initial thought is to extract everything BUT the data from the DB, to a different component and render it separately. And this is where it goes wrong. The slidervalue which sets state, which in turn the custom hook uses to send as a query parameter to the API, they do not have any connection to each other anymore. Am I using React all wrong? Is this where the context API would be smart to use?
I basically want to share state between to different functional components, and render them separately on a webpage.
This is my frontend code:
import React, { useEffect, useState } from "react";
function useLoading(loadingFunction, sliderValue) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [data, setData] = useState([]);
async function load() {
try {
setLoading(true);
setData(await loadingFunction());
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
useEffect(() => {
load();
}, [sliderValue]);
return { loading, error, data };
}
async function fetchJSON(url, sliderValue) {
const res = await fetch(url + `?numberOfMovies=${sliderValue}`);
if (!res.ok) {
throw new Error(`${res.status}: ${res.statusText}`);
}
return await res.json();
}
function randomNumber() {
return Math.floor(Math.random() * 20000000000);
}
function LoadingPage() {
return (
<>
<div className="loading one" />
<div className="loading two" />
<div className="loading three" />
<div className="loading four" />
</>
);
}
function MovieCard({ movie: { title, plot, year, poster } }) {
return (
<div className={"movie-card"}>
<h3>
{title} ({year})
</h3>
<p>{plot}</p>
{poster && <img width={100} src={poster} alt="Poster" />}
</div>
);
}
function ListMovies() {
const [sliderValue, setSliderValue] = useState("300");
const { loading, error, data } = useLoading(
async () => fetchJSON("/api/movies", sliderValue),
sliderValue
);
if (loading) {
return <LoadingPage />;
}
if (error) {
return (
<div>
<h1>Error</h1>
<div>{error.toString()}</div>
</div>
);
}
function handleSliderChange(e) {
let value = (document.getElementById("slider").value = e.target.value);
document.getElementById("slider-value").innerHTML =
value <= 1 ? `${value} movie` : `${value} movies`;
setSliderValue(value);
}
return (
<div className={"movies-container"}>
<h1>Movies</h1>
<p>Sorted by highest rated on Metacritic. All movies are from Ukraine.</p>
<input
onChange={handleSliderChange}
type="range"
min="1"
max="1000"
className="slider"
id="slider"
/>
<p id="slider-value" />
<div>
{data.map((movie) => (
<MovieCard key={randomNumber()} movie={movie} />
))}
</div>
</div>
);
}
export function MainPage() {
return (
<div>
<ListMovies />
</div>
);
}
It might be enough to "lift" the state to a common ancestor. State management in React is a surprisingly complex topic and worth reading up on standard approaches. Lifting state is one of them, because components don't "usually" talk to each other "horizontally". Props flow down. There are other ways to manage this such as Context or Redux, or even "non" React approaches such as pub/sub.
The good news is that having experienced the pain points first hand, you'll appreciate some of the patterns for solving the problems.
In my opinion I'm not sure there is a "wrong" way to do things, as long as it works. But there are definitely approaches that make life hard and others that make life easier.
If you could whittle down your issue to a very specific question, without so much explanation, you're likely to get better help.
I have gone through a couple of articles on useCallback and useMemo on when to use and when not to use but I have mostly seen very contrived code. I was looking at a code at my company where I have noticed someone have done this:
const takePhoto = useCallback(() => {
launchCamera({ mediaType: "photo", cameraType: "front" }, onPickImage);
}, []);
const pickPhotoFromLibrary = async () => {
launchImageLibrary({ mediaType: "photo" }, onPickImage);
}
const onUploadPress = useCallback(() => {
Alert.alert(
"Upload Photo",
"From where would you like to take your photo?",
[
{ text: "Camera", onPress: () => takePhoto() },
{ text: "Library", onPress: () => pickPhotoFromLibrary() },
]
);
}, [pickPhotoFromLibrary, takePhoto]);
This is how onUploadPress is called:
<TouchableOpacity
style={styles.retakeButton}
onPress={onUploadPress}
>
Do you think this is the correct way of calling it? Based on my understanding from those articles, this looks in-correct. Can someone tell me when to use useCallback and also maybe explain useCallback in more human terms?
Article I read: When to useMemo and useCallback.
useCallback returns a normal JavaScript function regarding how to use it. It is the same as the one it gets as first parameter regarding what it does. The difference is that this function doesn't get recreated on a new memory reference every time the component re-renders, while a normal function does. It gets recreated on a new reference if one of the variables inside useCalback's dependency array changes.
Now, why would you wanna bother with this? Well, It's worth it whenever the normal behavior of a function is problematic for you. For example, if you have that function in the dependency array of an useEffect, or if you pass it down to a component that is memoized with memo.
The callback of an useEffect gets called on the first render and every time one of the variables inside the dependency array changes. And since normally a new version of that function is created on every render, the callback might get called infinitely. So useCallback is used to memoize it.
A memoized component with memo re-renders only if its state or props changes, not because its parent re-renders. And since normally a new version of that passed function as props is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. So useCallback is used to memoize it.
To illustrate, I created the below working React application. Click on that button to trigger re-renders of the parent and watch the console. Hope it clears things up!
const MemoizedChildWithMemoizedFunctionInProps = React.memo(
({ memoizedDummyFunction }) => {
console.log("MemoizedChildWithMemoizedFunctionInProps renders");
return <div></div>;
}
);
const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
({ nonMemoizedDummyFunction }) => {
console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
return <div></div>;
}
);
const NonMemoizedChild = () => {
console.log("Non memoized child renders");
return <div></div>;
};
const Parent = () => {
const [state, setState] = React.useState(true);
const nonMemoizedFunction = () => {};
const memoizedFunction = React.useCallback(() => {}, []);
React.useEffect(() => {
console.log("useEffect callback with nonMemoizedFunction runs");
}, [nonMemoizedFunction]);
React.useEffect(() => {
console.log("useEffect callback with memoizedFunction runs");
}, [memoizedFunction]);
console.clear();
console.log("Parent renders");
return (
<div>
<button onClick={() => setState((prev) => !prev)}>Toggle state</button>
<MemoizedChildWithMemoizedFunctionInProps
memoizedFunction={memoizedFunction}
/>
<MemoizedChildWithNonMemoizedFunctionInProps
nonMemoizedFunction={nonMemoizedFunction}
/>
<NonMemoizedChild />
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's to know that memoizing is not free, doing it wrong is worse than not having it. In your case, using useCallback for onUploadPress is a waste because a non memoized function, pickPhotoFromLibrary, is in the dependency array. Also, it's a waste if TouchableOpacity is not memoized with memo, which I'm not sure it's.
As a side note, there is useMemo, which behaves and is used like useCallback to memoize non-function but referenced values such as objects and arrays for the same reasons, or to memoize any result of a heavy calculation that you don't wanna repeat between renders.
A great resource to understand React render process in depth to know when to memorize and how to do it well: React Render.
In simple words, useCallback is used to save the function reference somewhere outside the component render so we could use the same reference again. That reference will be changed whenever one of the variables in the dependencies array changes.
As you know React tries to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render not depending on the old-value and new-value of those variables.
So, the basic usage of useCallback is to hold old-value and the new-value equally.
I will try to demonstrate it more by giving some examples in situations we must use useCalback in.
Example 1: When the function is one of the dependencies array of the useEffect.
function Component(){
const [state, setState] = useState()
// Should use `useCallback`
function handleChange(input){
setState(...)
}
useEffect(()=>{
handleChange(...)
},[handleChange])
return ...
}
Example 2: When the function is being passed to one of the children components. Especially when it is being called on their useEffect hook, it leads to an infinite loop.
function Parent(){
const [state, setState] = useState()
function handleChange(input){
setState(...)
}
return <Child onChange={handleChange} />
}
function Child({onChange}){
const [state, setState] = useState()
useEffect(()=>{
onChange(...)
},[onChange])
return "Child"
}
Example 3: When you use React Context that holds a state and returns only the state setters functions, you need the consumer of that context to not rerender every time the state update as it may harm the performance.
const Context = React.createContext();
function ContextProvider({children}){
const [state, setState] = useState([]);
// Should use `useCallback`
const addToState = (input) => {
setState(prev => [...prev, input]);
}
// Should use `useCallback`
const removeFromState = (input) => {
setState(prev => prev.filter(elem => elem.id !== input.id));
}
// Should use `useCallback` with empty []
const getState = () => {
return state;
}
const contextValue= React.useMemo(
() => ({ addToState , removeFromState , getState}),
[addToState , removeFromState , getState]
);
// if we used `useCallback`, our contextValue will never change, and all the subscribers will not re-render
<Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
Example 4: If you are subscribed to the observer, timer, document events, and need to unsubscribe when the component unmounts or for any other reason. So we need to access the same reference to unsubscribe from it.
function Component(){
// should use `useCallback`
const handler = () => {...}
useEffect(() => {
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
return ...
}
That's it, there are multiple situations you can use it too, but I hope these examples demonstrated the main idea behind useCallback. And always remember you don't need to use it if the cost of the re-render is negligible.
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I was facing almost the same problem as in this question
The code is a bit too much so I have made a stripped down version of the problem. (please forgive me if I made a mistake in doing so)
Basically, I have a main component and a sub component
main component
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
const newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
sub component
import React {useState} from 'react'
const SubComponent = ({updateStateFunction}) => {
return (
<div>
<button
onClick={() => updateStateFunction("Something new")}
>
</button>
</div>
)
}
export default SubComponent;
both the console logs give null.
My attempts at a solution:
Since most stack overflow answers suggested that stateupdates using hooks is asynchronous I tried using setTimeout
I thought we could then use async-await but that was a wrong approach
I tried updating the state inside useEffect but the point is that nothing is being re redered. This is because the variable that is being updated is never a part of an output but rather sort a helper varibale for further operations.
The way I did this was using the solution in the above refereced question:
const Main = (props) => {
/*Debugging*/
let newState = null
useEffect(()=>{
console.log("useEffect called")
setState(newState)
}, [newState])
/*Debugging*/
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
I though since the useEffect hook is not even being executed hence I did not try the other two methods in the solution
Am I referencing the wrong type of problem or is this a common behaviour in react?
Happy to provide any more information if needed
Edit:
I have added console.log() because I have operations followed by the state change that uses the value of the state variable.
Using React dev tools I see that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up in the real code whose component uses the state for other logic, hence I get an error that that state is still null
I am not sure how let newState = null has anything to do with any of the answers in the quoted question, so to be clear, this is how one would directly apply the accepted answer:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
useEffect(() => { console.log(state) }, [state])
return <SubComponent updateStateFunction = {updateStateFunction} />
}
However, there is no point of changing a state if it's not used for anything rendered on the screen - if Reacts does not detect any change in the return value, it might not commit any change to the DOM as a part of the optimizations, so it would probably not run any useEffects in that case.
I would recommend using React Dev Tools for checking the current state of a Component.
Also, console.log is basically the only side effect which does not need to be inside useEffect. If directly in the render function, it would be executed whenever the render function is called and would not depend on React committing changes to DOM...
Note that the first advice in my answer to the quoted question does not wrap console.log into useEffect - and to be clear again, this is how one would directly apply that advice:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
console.log(state)
return <SubComponent updateStateFunction = {updateStateFunction} />
}
The setting of the state is asynchronous in react.
An asynchronous function will be executed in parallel (well, kind of) as other instructions. Rather than console.logging after setting state, you could console.log state before the return function to know the new output.
There is nothing wrong with your first implementation. No need to use useEffect here
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
setState(data)
}
// try console logging here
console.log(state)
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
Think of it like this, whenever the state is set, your function which contains the state gets refreshed and re-run. You could console.log anywhere in the function to get the new state.
Use the react devtools to debug, even if the console.log() display null you see the state change in the devtools. The reason is that the state update is asynchronous.
If you still want to debug your react app using console.log() then call it just before the return statement or even in the jsx directly using curly braces.
I have a use-case where a page have to call the same fetch function on first render and on button click.
The code is similar to the below (ref: https://stackblitz.com/edit/stackoverflow-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
The problem for me is the fetch function always triggers on any input changed because eslint-plugin-react-hooks forces me to declare all dependencies (ex: selected state) in the useCallback hook. And I have to use useCallback in order to use it with useEffect.
I am aware that I can put the function outside of the component and passes all the arguments (props, setLoading, setError, ..etc.) in order for this to work but I wonder whether it is possible to archive the same effect while keeping the fetch function inside the component and comply to eslint-plugin-react-hooks?
[UPDATED]
For anyone who is interested in viewing the working example. Here is the updated code derived from the accepted answer.
https://stackblitz.com/edit/stackoverflow-question-bink-62951987-vxqtwm?file=index.tsx
Add all of your dependecies to useCallback as usual, but don't make another function in useEffect:
useEffect(query, [])
For async callbacks (like query in your case), you'll need to use the old-styled promise way with .then, .catch and .finally callbacks in order to have a void function passed to useCallback, which is required by useEffect.
Another approach can be found on React's docs, but it's not recommended according to the docs.
After all, inline functions passed to useEffect are re-declared on each re-render anyways. With the first approach, you'll be passing new function only when the deps of query change. The warnings should go away, too. ;)
There are a few models to achieve something where you need to call a fetch function when a component mounts and on a click on a button/other. Here I bring to you another model where you achieve both by using hooks only and without calling the fetch function directly based on a button click. It'll also help you to satisfy eslint rules for hook deps array and be safe about infinite loop easily. Actually, this will leverage the power of effect hook called useEffect and other being useState. But in case you have multiple functions to fetch different data, then you can consider many options, like useReducer approach. Well, look at this project where I tried to achieve something similar to what you wanted.
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
Let's talk about the model a bit
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
Here I fetched data in first rendering using the initial link, and on each button click instead of calling any method I updated a state that exists in the deps array of effect hook, useEffect, so that useEffect runs again.
I think you can achieve the desired behavior easily as
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
For details, navigate to the end of this official docs page.