I am calling useEffect hook multiple times in a component
useEffect(() => {
getData(id);
}, [getData, id]);
const reqBody = useMemo(
() => ({
item: data?.item,
}), [data?.item]
);
useEffect(() => {
if (data?.item) {
getAnotherData(reqBody);
}
}, [getAnotherData]);
As seen in the code snippet, the first useEffect hook calls a function called getData which stores the data in redux store and the reqBody of the function getAnotherData in the second hook depends on the first hook.
Therefore, the second hook runs even if the data in the store is not updated yet resulting in multiple api calls.
How can I avoid this so that the function getAnotherData is called only when the data in the store is updated?
Use data in the dependency array instead of getAnotherData in the second useEffect hook.
useEffect(() => {
getData(id);
}, [getData, id]);
const reqBody = useMemo(
() => ({
item: data?.item,
}), [data?.item]
);
useEffect(() => {
if (data?.item) {
getAnotherData(reqBody);
}
}, [data]);
Related
I have an API that needs to be fetched every 30 seconds.The UseEffect written below in the code is for a component that gets rendered on home component. It's working well but if I navigate to any another page I need the API to not be fetched.
I'm using react-router and redux.
useEffect(() => {
dispatch(loadCurrencyList())
setInterval(() => {
dispatch(loadCurrencyList())
}, 30000
)
}, [dispatch])
Do a cleanup in the return of useEffect :
useEffect(()=>{
const timer = setInterval(...)
return ()=> clearInterval(timer)
}, [dispatch])
Store timer id in a mutable variable created with useRef() hook,
then use it in the component unmount code. Don't return clean up function from useEffect() dependent on [dispatch] as it will be executed every time dispatch variable changes.
const timer = useRef();
useEffect(() => {
dispatch(loadCurrencyList());
timer.current = setInterval(() => {
dispatch(loadCurrencyList())
}, 30000);
}, [dispatch]);
useEffect( () => () => {
if (timer.current) {
clearInterval(timer.current);
}
}, []);
I have a useEffect() that fetches the data through axios, I want that to render only one time so I passed an array. Everything works fine, but the problem is whenever I try to sort the items, the second useEffect fires just followed by the first useEffect, which is causing the component to fetch the items all over again and again.
const [products, setProducts] = useState([]);
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, [products]);
This is because you passed an array containing your products state, rather than an empty array, which will fire useEffect on state change (for products state specifically). Try changing your code to an empty array:
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, []);
As #skyboyer mentioned below, it is good to note that state is not updated in a synchronous manner. Therefor, console.log(products) will not reflect an accurate value for your state when useEffect runs.
It is okay to use multiple useEffect hooks. If you would like to view your updated state in the console, or do some other work with it, you could add another useEffect hook and pass your state into the array:
useEffect(() => {
console.log(products);
}, [products]);
Since products is in the useEffect dependency array, it is going to run every time there are changes made to the products state. getProducts() runs setProducts which then in turn is going to trigger the use effect again. Using an empty array in the useEffect will tell it to only run when the component is mounted.
Like this:
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, []);
i have five a snapshot listener in useEffect and i have another call api to get data from firestore and update state
but I am facing a problem is every initial mount all listener got called , my goal is i want to all listener called only when document changed
i tried with useRef it works but listener do not trigger
As you can see in the example below, onSnapshot is printed during the initial mounted
useEffect(() => {
if (isFirstMount.current) return;
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
}, []);
useEffect(() => {
if (isFirstMount.current) {
isFirstMount.current = false;
return;
}
}, []);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
let snap = await someFirestoreAPICall.get();
setData(snap.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setLoading(false);
};
fetchData();
}, []);
Codesandbox
You can use a condition inside your useEffect block as you are doing, I think. But perhaps useState would be more appropriate here than useRef.
eg:
const [state, setState] = useState(null)
useEffect(()=>{
if (state) {
// do something
}
}, [state])
The useEffect will run on mount and every time you change the value of state, but code inside the condition will only run if you change the state to a truthy value.
I am using zustand for state management and am trying to update a component in real time only without refreshing the page when there are changes in the state of the component after retrieving the data once.
Here is my store
export const useStore = create((set) => ({
reservations: [],
getReservations: async () => {
const response = await axios.get(baseUrl);
set({ reservations: response.data });
},
setRev: (reservations) => {
set((state) => ({
...state,
reservations,
}));
},
addReservation: (reservation) => {
set((state) => ({ reservations: [...state.reservations, reservation] }));
},
removeReservation: (id) => {
set((state) => ({
reservations: state.reservations.filter(
(reservation) => id !== reservation._id
),
}));
},
}));
I have tried using the useEffect hook to retrieve the data as shown in the code below, I have another component which calls the addReservation function. The code below results in calling useEffect infinitely instead of updating only when there are changes to const reservations, when another component calls the addReservation function.
const getAllReservation = useStore((state) => state.getReservations);
const reservations = useStore((state) => state.reservations);
const reservationsRef = useRef(useStore.getState().reservations);
useEffect(() => {
getAllReservation()
useStore.subscribe(
(reservations) => (reservationsRef.current = reservations),
(state) => state.reservations
);
}, [reservations]);
I have tried splitting the useEffects as such but the page needs to be refreshed again to show the updated data. (desired outcome is without refreshing)
useEffect(() => {
getAllReservation()
}, [])
useEffect(() => {
useStore.subscribe(
(reservations) => (reservationsRef.current = reservations),
(state) => state.reservations
);
}, [reservations]);
I have tried putting [] as the dependency array as the second argument in the useEffect hook but it does not work as well.
Thank you for your help.
React can't detect changes inside of Ref. So need to pass updated reservations as props or save in local state.
Zustand docs
The subscribe function allows components to bind to a state-portion
without forcing re-render on changes. Best combine it with useEffect
for automatic unsubscribe on unmount. This can make a drastic
performance impact when you are allowed to mutate the view directly.
const useStore = create(set => ({ scratches: 0, ... }))
function Component() {
// Fetch initial state
const scratchRef = useRef(useStore.getState().scratches)
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
useEffect(() => useStore.subscribe(
scratches => (scratchRef.current = scratches),
state => state.scratches
), [])
I have 2 actions in redux (both async) and I'm calling them both within my functional component via dispatch; the first using useEffect and the second via a button click. What I want to do is dispatch the actions to retrieve them from an async function, then use them within my component via useState. But using the useState is not rendering.
Here is my component:
export default function Hello()
{
const { first, second } = useSelector(state => state.myReducer);
const dispatch = useDispatch();
const fetchFirst = async () => dispatch(getFirst());
const fetchSecond = async () => dispatch(getSecond());
const fetchFixturesForDate = (date: Date) => dispatch(getFixturesForDate(date));
const [superValue, setSuperValue] = useState('value not set');
useEffect(() => {
const fetch = async () => {
fetchFirst();
setSuperValue(first);
};
fetch();
}, []);
const getSecondOnClickHandler = async () =>
{
console.log('a')
await fetchSecond();
setSuperValue(second);
}
return (
<div>
<p>The super value should first display the value "first item" once retrieved, then display "second value" once you click the button and the value is retrieved</p>
<p>Super Value: {superValue}</p>
<p>First Value: {first}</p>
<p>Second Value: {second}</p>
<button onClick={async () => await getSecondOnClickHandler()}>Get Second</button>
</div>
)
}
The superValue never renders even though I am setting it, although the value from first and second is retrieved and displayed.
StackBlitz.
Any help?
The value of first and second inside your two useEffects is set when the component mounts (I guess at that point they are undefined). So in both cases you will be setting superValue to that initial value.
You have two options:
Return the first/second values back from fetchFirst and fetchSecond, so that you can retrieve them directly from the executed function, and then set superValue:
useEffect(() => {
const fetch = async () => {
const newFirst = await fetchFirst();
setSuperValue(newFirst);
};
fetch();
}, []);
Add separate useEffects that listen for changes to first and second
useEffect(() => {
setSuperValue(first)
},[first])
useEffect(() => {
setSuperValue(second)
},[second])
The value in the reducer is not necessarily set when the action is dispatched, e.g. after fetchFirst() is called. Also the await that you do in await fetchSecond();
doesn't help since the reducer function is not executed.
You could add useEffect hooks and remove the setSuperValue from the other methods, but I think the code gets quite complicated.
What problem are you trying to solve in the first place?
useEffect(() => setSuperValue(first), [first]);
useEffect(() => setSuperValue(second), [second]);
useEffect(() => {
const fetch = async () => {
fetchFirst();
};
fetch();
}, []);
const getSecondOnClickHandler = async () => {
console.log('a');
await fetchSecond();
};
https://stackblitz.com/edit/react-ts-hsqd3x?file=Hello.tsx