Explanation needed: getting data from API with useEffect hook and get name - reactjs

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

Related

How combine 3 lifecycles in one in useEffect in the following case?

I have a useEffect where currently get from the redux the data and also clean up it during the unmounting:
useEffect(
dispatch(getData...)
() => {
dispatch(cleanData...)
},
[url]
);
When I set the url as dependency for using as componentDidupdate and updating the component if url is changed it throws some warnings in the console about not being able to work with unmounted component while functionality seems to work. What is the ideal way to have these 3 lifecycle methods in the same place for the useEffect?
useEffect expects two arguments. Callback Function that will get called at the initial render and when the dependencies gets changed. Second argument is dependency array.
So you can call dispatch function on initial render and on change of url . You have to return a function that is a cleanup function.
useEffect(() => {
dispatch(getData)
return () => {
dispatch(cleanData)
}
},[url]);

React with TypeScript - React has detected a change in the order of Hooks called by ComponentName

I am working with a project with users. Right now I am working with the UserProfile
This is the error I am recieving.
React has detected a change in the order of Hooks called by UserProfile
Previous render Next render
------------------------------------------------------
1. useState useState
2. useContext useContext
3. useEffect useEffect
4. undefined useContext
Let me show some code of the UserProfile component.
export const UserProfile = () => {
document.title = `${title} - My Profile`;
const [profile, setProfile] = useState<UserDetails>();
const {claims} = useContext(AuthContext);
const getUserEmail = (): string => {
return claims.filter(x => x.name === "email")[0]?.value.toString();
}
useEffect(() => {
axios.get(`${urlAuth}?userName=${getUserEmail()}`)
.then((response: AxiosResponse<UserDetails>) => {
setProfile(response.data);
})
}, [getUserEmail]);
return (
profile ?
<article>
<h1>This profile belongs to {UserName()}</h1>
<h2>{profile.name}</h2>
</article>
: <div>Loading...</div>
)
}
I get a warning at the getUserEmail function,
It says
The 'getUserEmail' function makes the dependencies of useEffect Hook (at line 26) change on every render.
Move it inside the useEffect callback.
Alternatively, wrap the definition of 'getUserEmail' in its own useCallback() Hook.
I am not sure on how this should be done.
Any ideas on what I could do?
Thanks
Wrap getUserEmail's value in a useCallback.
On every render, getUserEmail essentially becomes a 'new' function.
When there's a function in the deps array of a useEffect or other such hooks, React checks it by reference. Since each component function execution/rerender leads to the creation of a new function, your useEffect hook will actually run every single time, sending you into a re-render loop (because it'll run the useEffect, update the state with setProfile, which in turn will trigger another execution, where getUserEmail is different again, leading to the useEffect to run again and so on).
const getUserEmail = useCallback((): string => {
return claims.filter(x => x.name === "email")[0]?.value.toString();
}, [claims]);
This should give you a memoized callback that will only be recreated if claims changes. Since claims comes from your context, this should be safe as a dependency.
The reason why you are getting error about order of hooks is I think because of this:
profile ?
<article>
<h1>This profile belongs to {UserName()}</h1>
<h2>{profile.name}</h2>
</article>
: <div>Loading...</div>
If UserName is a component you should not call it as function, rather as element <UserName/>. When you call it as function react thinks some of the hooks which you call inside it belong to the parent component - this combined with condition profile ? could give you the error.

React Useeffect running when page loads

Am using useEffect in a react functional component to fetch data from an external API but it keeps calling the API endpoint on render on the page .
Am looking for a way to stop the useeffect from running on render on the component
Use the dependency array (second argument to the useEffect), so you can specify when you need to run the useEffect.
The problem here is that you have not used the dependency array, so that it executes every time. By adding a dependency array, you specify the changes where you want useEffect to run.
useEffect(()=>{
},[<dependency array: which contains the properties>]);
If you leave the dependency array empty, it will run only once. Therefore if you want the API call to run only once, add an empty array as the second argument to your useEffect. This is your solution.
Like this:
useEffect(()=>{
//Your API Call
},[]);
useEffect is always meant to run after all the changes or render effects are update in the DOM. It will not run while or before the DOM is updated. You may not have given the second argument to useEffect, which if u do not provide will cause the useEffect to execute on each and every change. Assuming you only want to call the API just once when on after the first render you should provide an empty array.
Runs on all updates, see no second argument to useEffect:
useEffect(() => { /* call API */ });
Runs when the prop or state changes, see the second argument:
useEffect(() => { /* call API */ }, [prop, state]);
Runs only once, see the empty second argument:
useEffect(() => { /* call API */ }, []);
I recommend you to read the full documentation about the React useEffect hook.
Here is a easy example of using useEffect
function functionalComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const url = 'https://randomuser.me/api/?results=10';
fetch(url)
.then(data => {
setData(data);
})
.catch(error => console.error(error))
}, []); // it's necessary to use [] to avoid the re-rendering
return <React.Fragment>
{data !== null && (
<React.Fragment>
{data.results.map(data => (
<div>
{data.gender}
</div>
))}
</React.Fragment>
)}
</React.Fragment>;
}
Maybe in your useEffect implementation you are avoiding the [] dependencies, this is a bit hard to understand if you come from class states. This on hooks review when a state element inside the hook change, for example if you are using an element that always change like a prop that you pass throught another component you might be setting inside the dependencies or another state, if you do not need any dependency just use it empty like the example above. As you can see in the documentation sometimes the dependencies are not used, this might generate an infinite loop.

Using redux data in the useEffect hooks

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.

What should be the dependencies of useEffect Hook?

As it's said, the useEffect hook is the place where we do the side-effects related part. I'm having a confusion of what dependencies should be passed in the dependency array of useEffect hook?
The React documentation says
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Consider an example:
export default function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("component mounted");
}, []);
const update = () => {
for(let i=0; i<5;i++){
setCount(count+i)
}
};
return (
<div>
{console.log(count)}
{count}
<button type="button" onClick={update}>
Add
</button>
</div>
);
}
Going with the above statement, we should pass the count variable as a dependency to useEffect, it will make the useEffect re run.
But in the above example, not passing the count dependency doesn't create any problem with the UI. There is not stale value. The variable updates as expected, UI re-renders with exact value, logs the current value.
So my question is, why should we pass the count variable as dependency to useEffect. The UI is working correctly?
UPDATE
I know the useEffect callback is triggered everytime when the value in dependency array changes. My question is what should go in to the dependency array? Do we really need to pass state variables?
Thanks in advance.
To demonstrate and understand the issue print the count variable inside
React.useEffect(() => {
console.log("component updated ", count);
}, []); // try to add it here
click again the button and see how it logs to the console
EDIT:
If you want to get notified by your IDE that you are missing dependencies then use this plugin
https://reactjs.org/docs/hooks-rules.html#eslint-plugin
Keep in mind that it will still complain if you want to simulate onMount
What should go into the dependency array?
Those things (props/state) that change over time and that are used by the effect.
In the example, the UI works correctly because setState re-renders the component.
But if we do some side-effect like calling an alert on change of count, we have to pass count to the dependency array. This will make sure the callback is called everytime the dependency (count) in our case, changes.
React.useEffect(() => {
alert(`Count ${count}`); // call everytime count changes
}, [count]); // makes the callback run everytime, the count updates.
If property from second argument in useEffect change - then component will rerender.
If you pass the count -> then component rerender after count change - one time.
React.useEffect(() => {
console.log("component mounted");
}, [count]);
Quick answer:
Pass everytime you need to trigger refreshing component.
useEffect will get called in an infinite loop unless you give it dependencies.
React.useEffect(() => {
console.log("Looped endlessly");
}); // dependencies parameter missing
Adding an empty dependency list will cause it to get called just once on component did mount
React.useEffect(() => {
console.log("Called once on component mount");
}, []); // empty dependency list
Add a state to the dependency list to get called when state gets updated
React.useEffect(() => {
console.log("Called once on component mount and whenever count changes");
console.log("Count: " + count);
}, [count]); // count as a dependency

Resources