I am now trying to call an API using data from the redux store.
Let say I got 2 API calls, Api A and Api B Inside the parent component I already called the API A and save the data inside the redux already.
Now I am in another component. I need to call Api B. But API B has a params, which I will get from API A. So Inside the Second component, I am using useEffect hook to call the data.
To get the params from the redux, I am using useSelector Hook.
Inside the second component, UseEffect Hook is something like this:
useEffect(() => {
let splitText = cartList?.OrderDTO?.DeliveryCountry;
let deliveryAddressId = splitText?.split(',');
if (cartList.OrderDTO?.DeliveryCountry !== '') {
dispatch(getShippingMethodById(token.access_token, deliveryAddressId));
} else {
dispatch(
getShippingMethodById(
token.access_token,
cartList.OrderDTO?.CustomerAddressId,
),
}
}, []);
So in the useEffect hook, I got the deliveryAddressId from redux. To draw in data from the redux into component, I am using useSelector hook
let cartList = useSelector((state) => state.productReducer.cartList);
The problem is that I always get undefined for cartlist when ever I tried to access it inside the useEffect hook
So the dispatch called are always getting undefined. So What can I do to make this hooks works?
You should add cartList to your dependency array, so the useEffect hook watches for updates to that piece of state. As it is written now, the useEffect only runs on the first render, where cartList is probably undefined.
React - useEffect Docs
useEffect(() => {
let splitText = cartList?.OrderDTO?.DeliveryCountry;
let deliveryAddressId = splitText?.split(',');
if (cartList.OrderDTO?.DeliveryCountry !== '') {
dispatch(getShippingMethodById(token.access_token, deliveryAddressId));
} else {
dispatch(
getShippingMethodById(
token.access_token,
cartList.OrderDTO?.CustomerAddressId,
),
}
}, [cartList]); // Add 'cartList' to your dependency array here
Solution is that you add cartList inside the dependency array.
useEffect(() => {
// All your logic inside
}, [cartList]);
I don't have info about the complete parent-child component structure, but from what I understood with the error I can explain the issue.
You are using [], for useEffect dependency, which means the callback inside useEffect will be triggered only once when the component mounts.
It is possible that when your component mounted, the API call in the parent was not complete, and you have still have undefined in store for cartList.
To check this hypothesis you can add console.log in API response and
inside the useEffect.
What else you can do?
You can not render the child component until you have data from the API call.
Why adding cartList in the dependency array fixed the issue?
By dependency array inside useEffect, your useEffect call back will be
called whenever the values in the dependency array change + on the
mount.
So, at the time of mount of the child component, useEffect's callback will trigger (cartList as undefined), then when the API call is successful and after that data is pushed in state, your child component will rerender and will retrigger the callback inside useEffect with the actual(which you got from API and pushed in store) cartList data.
Related
Working with a functional component which has the following two useEffects:
//update the state when props changed
useEffect(() => {
const newState = mapPropsToState(props);
if (!_.isEqual(newState, state)) {
setState(newState);
}
}, [props]);
//make an API call
useEffect(() => {
preferencesChanged();
}, [state]);
State is derived from props, so the purpose of the first useEffect is to respond to a change in props and update the state.
The purpose of the second useEffect is to make an API call when state has changed. However, this API call can result in the props of this component changing (since preferencesChanged() updates the state of a parent component).
What I really want is for the setState in the first useEffect to be done "quietly" and not to trigger the 2nd useEffect.
Is this possible? Or am I thinking about this design in completely the wrong way?
State is derived from props.
You can just do your logic inside of component (in render phase), instead of calling it in an effect:
//...
const newState = mapPropsToState(props);
// ...
and you can use useMemo if that is an expensive calculation:
const newState = useMemo(() => mapPropsToState(props), [props])
And to answer your question,
What I really want is for the setState in the first useEffect to be done "quietly" and not to trigger the 2nd useEffect.
You can store the relevant info (dependencies of 2nd useEffect) in a seperate state varable, and then use that.
Refs:
You might not need an effect (React docs)
Summing up some of nuances of useEffect
I want to trigger An API to do some actions when the value in the react state be empty, Is there a way in react state hook to achieve that?
If you are using a functional component you can use the "useEffect" hook with a proper dependency.
Class base components you might choose (if I understand your situation properly) something like the "componentWillUnmount()" method.
You could have a useEffect hook with the state as a dependency, in-which you can check if the state is empty and act accordingly.
Example:
const [state, setState] = useState([]);
useEffect(() => {
if (state.length !== 0) {
return;
}
// Can make your API call here (preferably call a function that does it)
// and then set the state.
setState(...);
}, [state]);
useEffect is a hook that accepts a callback as the first argument, and a dependency array as the second.
The callback will execute upon re-render whenever any of the dependencies in the array change.
Note: this is relevant only for functional components, for class-based component we have the componentDidUpdate lifecycle hook.
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
This one below doesn't work:
//Uncaught TypeError: Cannot read property 'name' of undefined
console.log(countries[1].name)
This one below does work:
<ul>
{countries.map(country => (
<li>{country.name}</li>
))}
</ul>
Any ide why one method of printing name does work, while the other doesn't?
Coz you can loop through the empty array, but you can't access the index which is not available yet
// So if
countries = []
// this will not throw error
{countries.map(country => (
<li>{country.name}</li>
))}
// but this will
console.log(countries[1].name)
// if you want to check try to run this
console.log(countries.length ? countries[1].name : "not available yer");
The usage of useEffect hook notifies React that component has to perform some side-effects(passed as a callback function to the hook) after it has been rendered, The default behavior of useEffect will run both after the first render and after every update, but when an empty array is passed as a dependency the side-effect will be performed only once after the component has been mounted for the first time.
In the case above useEffect(hook, []) the callback hook will be called after the component has mounted for the first time, which means the component will render with the initial state on it's first render which is an empty array ([]).
That is why when you try to access countries[1].name it errors out, because the value of countries is still an empty array on the first render.
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
// can not use index expression to get the first element because
// the value of countries is still an empty array on first render
// it only gets populated when axios.get call is succesful inside the
// callback in useEffect hook after the component has mounted for the first time
console.log(countries[1].name)
Solution
Check for the length of the array before trying to get the first element,
if (countries.length) {
console.log(countries[1].name)
}
P.S.- You should be using a .catch block for handling the error when the API call fails.
There is an example solution for a type of request like this in the React document:
https://reactjs.org/docs/hooks-effect.html
The hooks provided by React are for the most part, asynchronous functions provided by React, to help manage the loading of data, presenting it to the DOM, and dealing with updates. The useEffect behaves in a similar way to componentHasLoaded, where the hook is triggered once the functional component has rendered, and the DOM has been loaded, but it may not have been presented to the user yet. It's important to remember this when working with useEffect. useState is another asynchronous hook, but it provides access to the state property of the hook after it has been instantiated, and won't immediately trigger a re-render of the component, unless the data is updated.
The reason you get an undefined error when you attempt to access console.log(countries[1].name) is because the array at that point is still empty.
I'll explain in code:
const myComponent = () => {
// initialise countries: []
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
// This is allow you to see the DOM change after the effect has run
setTimeout(() => setCountries(response.data), 5000);
})
}
// Tell react to run useEffect once the component is loaded
useEffect(hook, [])
// Display data
return (
<p>Countries: {countries.length}<p>
);
};
Because useEffect is an asynchronous function, it doesn't block the execution of the function and the rendering of the DOM, but refreshes the DOM once useEffect is completed. In this case, you are setting the country list, based on the result of the useEffect function.
The useEffect function will still trigger, you will have access to the state, and the function will re-render when the state is updated.
See codepen example:
https://codepen.io/jmitchell38488/pen/OJMXZPv
I'm using a functional component as a child with props from parent. In the props i have a value array in which i'm getting as empty([]) inside useEffect and after some time, the same is getting rendered in the UI. I'm using useEffect to call a function and set the state only once as below and i want it to be like that. Here value is the props and checkEmpty is the function i'm checking and setting the state, completed. Is there any way to invoke the function inside useEffect once the value array is filled. But i want the useEffect to be invoked only once and needs to change the state completed with out letting it to an infinite loop.
useEffect(() => {
checkEmpty(value)?setCompleted(false):setCompleted(true)
}, [])
You can pass the value as a dependency of useEffect, and the hook only run when the value is changed.
useEffect(() => {
checkEmpty(value) ? setCompleted(false) : setCompleted(true);
}, [value])
I have a redux action that fetches all data and stores it into a global Redux store.
I want to store that state in a local state using Hooks so that the actual state doesn't get changed when I change it locally.
What I am doing right now is,
const [filteredTable, setFilteredTable] = useState([])
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
setFilteredTable(props.filtered_table_data);
}, [])
In useEffect, the props.fetchDatabase() gets the props.filtered_table_data and I can see that when I console.log it out.
However, when I use Hooks to store it into a local state and check if it's in there,
console.log(filteredTable, 'filteredTable')
just gives me [].
What am I doing wrong?
I believe the props.fetchDatabase() call is asynchronous, so by the time you are attempting to setFilteredTable the props.filtered_table_data has not updated yet.
You can try something like this:
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
}, [])
useEffect(() => {
setFilteredTable(props.filtered_table_data);
}, [props.filtered_table_data]);
Note that this effect will run every time filtered_table_data changes, so you may need to wrap around the code in the callback with some sort of condition if you want to restrict setting the local state.
useEffect's callback with [] as hook's second argument is only being called once when component just mounted. Inside it fetchDatabase, and fetchOptions callbacks are called, and right after that (when data isn't yet fetched) you call setFilteredTable, that's why there are empty array occurs in filteredTable.
Not sure if this answers your question, but React-Redux provides some hooks for accessing the redux store.
The one that might be of interest to you is the useSelector() hook.
Here's an example usage:
import { useSelector } from 'react-redux';
const App = () => {
const tableData = useSelector(state => state.tableData);
...
}