Correct way to use UseEffect? - reactjs

Is it a problem/bad habit to add codes that does conditional rendering based on states or values that update constantly outside the useEffect hook.
Like this
function Home() {
const { state } = useLocation();
const [validation, setValidation] = useState();
const [MainData, setMainData] = useState();
if(true)
do somthing....
else
do somthing....
React.useEffect(() => {
something else.....
})
return (
);
}
Or should i do it this way??
function Home() {
const { state } = useLocation();
const [validation, setValidation] = useState();
const [MainData, setMainData] = useState();
React.useEffect(() => {
if (true)
do somthing....
else
do somthing....
something else....
})
return (
);
}
But if it do it the second way sometimes i get warnings about dependencies. Is that like a major problem should i just ignore it??

Yes, both examples definitely are bad habits.
The statements in both will be run on every re-render since you didn't add a dependency array in your second example. You should use the useEffect hook with a dependency array and add the dependencies (or leave an empty array if you only want to run the code on the first mount of the component). In my experience there barely is any case where re-calculations on every re-render are necessary and I had to find out the hard way that such calculations can really hurt the performance of your app, especially when using concurrent react.
If you want to declare an arrow function in your component or calculate a value that you want to render I advice you to use the useCallback or useMemo hook.

Related

Logical understanding react hooks, difference between useState and useEffect (or state and lifecycle methods)

I cannot understand the difference between useState and useEffect. Specifically, the difference between state and lifecycle methods. For instance, I have watched tutorials and seen this example for useEffect:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
const add = () => {
setVal((x) => { return x + 1 })
}
useEffect(() => {
if (value > 0) {
document.title = `Title: ${value}`
}
},[value])
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
When we click the button, the title of the document shows us increasing numbers by one. When I removed the useEffect method and did this instead:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
document.title = `Title: ${value}`
const add = () => {
setVal((x) => { return x + 1 })
}
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
It worked same as the previous code.
So, how does useEffect actually work? What is the purpose of this method?
P.S. Do not send me links of documentation or basic tutorials, please. I have done my research. I know what I am missing is very simple, but I do not know what is it or where to focus to solve it.
Using useEffect to track stateful variable changes is more efficient - it avoids unnecessary calls by only executing the code in the callback when the value changes, rather than on every render.
In the case of document.title, it doesn't really matter, since that's an inexpensive operation. But if it was runExpensiveFunction, then this approach:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
runExpensiveOperation(value);
would be problematic, since the expensive operation would run every time the component re-renders. Putting the code inside a useEffect with a [value] dependency array ensures it only runs when needed - when the value changes.
This is especially important when API calls that result from state changes are involved, which is pretty common. You don't want to call the API every time the component re-renders - you only want to call it when you need to request new data, so putting the API call in a useEffect is a better approach than putting the API call in the main component function body.

useEffect on infinite loop using async fetch function

I am trying to understand why the following useEffect is running in an infinite loop. I made the fetchSchedule helper function to call the getSchedule service (using Axios to query the API endpoint). Reason I did not define this function inside the useEffect hook is because I would like to alternatively also call it whenever the onStatus function is invoked (which toggles a Boolean PUT request on a separate endpoint).
The eslinter is requiring fetchSchedule be added to the array of dependencies, which seems to be triggering the infinite loop.
The way it should work is fetching the data from the database on first render, and then only each time either the value prop is updated or the onStatus button is toggled.
So far my research seems to point that this may have something to do with the way useEffect behaves with async functions and closures. I’m still trying to understand Hooks and evidently there’s something I’m not getting in my code…
import React, { useEffect, useCallback } from 'react';
import useStateRef from 'react-usestateref';
import { NavLink } from 'react-router-dom';
import { getSchedule, updateStatus } from '../../services/scheduleService';
import Status from './status';
// import Pagination from './pagination';
const List = ({ value }) => {
// eslint-disable-next-line
const [schedule, setSchedule, ref] = useStateRef([]);
// const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
}, [value, setSchedule]);
const onStatus = (id) => {
updateStatus(id);
fetchSchedule();
console.log('fetch', ref.current[0].completed);
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
return (...)
Update March 2021
After working with the repo owner for react-usestateref, the package now functions as originally intended and is safe to use as a replacement for useState as of version 1.0.5. The current implementation looks like this:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(state);
var dispatch = React.useCallback(function(val) {
ref.current = typeof val === "function" ?
val(ref.current) : val;
setState(ref.current);
}, []);
return [state, dispatch, ref];
};
You would be fine if it weren't for this react-usestateref import.
The hook returns a plain anonymous function for setting state which means that it will be recreated on every render - you cannot usefully include it in any dependency array as that too will be updated on every render. However, since the function is being returned from an unknown custom hook (and regardless, ESLint would correctly identify that it is not a proper setter function) you'll get warnings when you don't.
The 'problem' which it tries to solve is also going to introduce bad practice into your code - it's a pretty way to avoid properly handling dependencies which are there to make your code safer.
If you go back to a standard state hook I believe this code will work fine. Instead of trying to get a ref of the state in onStatus, make it async as well and return the data from fetchSchedule as well as setting it.
const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
return data;
}, [value]);
const onStatus = async (id) => {
updateStatus(id);
const data = await fetchSchedule();
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
Alternatively, although again I wouldn't really recommend using this, we could actually write a safe version of the useStateRef hook instead:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(defaultValue);
ref.current = state;
return [state, setState, ref];
}
A state setter function is always referentially identical throughout the lifespan of a component so this can be included in a dependency array without causing the effect/callback to be recreated.

Custom hooks not working properly with useEffect

I wasn't sure on a title for this issue, but I can better explain it here. I have a custom hook that is relying on some information. Part of the information must rely on an async call.
I have three situations happening.
Tried conditionally rendering the custom hook, but react does not like that due to rendering more hooks on a different render.
The custom hook is only mounting once and not passing in the updated information it needs.
I tried passing the dependency to the custom hook and it causes an infinite loop.
Here is a small example of what I'm doing.
Custom Hook:
export function useProducts(options){
const [products, setProducts] = useContext(MyContext)
useEffect(() => {
// only gets called once with `options.asyncValue` === null
// needs to be recalled once the new value is passed in
const loadProducts = async () => {
const data = await asyncProductReq(options)
setProducts(data)
}
loadProducts()
}, []) // if I pass options here it causes the infinite loop
return [products, setProducts]
}
Inside function calling:
export function(props){
const [asyncValue, setValue] = useState(null)
useEffect(() => {
const loadValue = async () => {
const data = await asyncFunc()
setValue(data)
}
loadValue()
}, []}
const options = {...staticValues, asyncValue}
const [products] = useProducts(options)
return (
<h2>Hello</h2>
)
}
I know that I need to pass the options to be a dependency, but I can't figure out why it's causing an infinite reload if the object isn't changing once the async call has been made inside the func.
You were correct in adding options in the dependencies list for your custom hook.
The reason it is infinitely looping is because options IS constantly changing.
The problem is you need to take it one step further in the implementation and make use of the useMemo hook so options only changes when the async value changes, instead of the whole component changing.
So do this:
const options = React.useMemo(() => ({...staticValues, asyncValue}), [asyncValue])

useEffect re-renders too many times

I have this component, that needs to fetch data, set it to state and then pass it to the children.
Some of the data also needs to be set in context.
My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute.
I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing.
At the moment the array is containg the set...functions to prevent eslint to throw warnings.
Is there a better way to avoid this many re-renders ?
const Home = (props) => {
console.log("TCL: Home -> props", props);
const classes = useStyles();
const [value, setValue] = React.useState(0);
//CONTEXT
const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
const {
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
} = useContext(UserContext);
// STATE
const [searchSettings, setSearchSettings] = useState([]);
const [oppData, setOppData] = useState([]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = index => {
setValue(index);
};
//API CALLS
useEffect(() => {
const triggerAPI = async () => {
setIsFullView(false);
const oppResponse = await API.getOpportunity();
if(oppResponse){
setOppData(oppResponse.response);
}
const profileResponse = await API.getUserProfile();
if(profileResponse){
setUserName(profileResponse.response.first_name);
setUserLastName(profileResponse.response.last_name);
setUserEmail(profileResponse.response.emailId);
}
const profileExtData = await API.getUserProfileExt();
if(profileExtData){
setAvatarProfile(profileExtData.response.avatar);
setListSavedJobs(profileExtData.response.savedJobs);
setSearchSettings(profileExtData.response.preferredIndustry);
}
};
triggerAPI();
}, [
setOppData,
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
setListSavedJobs,
setIsFullView,
]);
...```
Pass just an empty array to second parameter of useEffect.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
Source
Edit: Try this to avoid rerenders. Use with caution
Only Run on Mount and Unmount
You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.
Prevent useEffect From Running Every Render
If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)
const [value, setValue] = useState('initial');
useEffect(() => {
// This effect uses the `value` variable,
// so it "depends on" `value`.
console.log(value);
}, [value])
For more clarification useEffect
If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html

useEffect props callback function causing infinite loop

I have a problem very similar to this - How do I fix missing dependency in React Hook useEffect.
There is one key difference - I am passing a fetch function to a child component to be called from useEffect, so I can't simply move the function into the body of the effect. The fetch function is re-created every render and causes an infinite loop. I have other local component state that I want to cause the effect to fire.
I basically have a Container Component and a Presentational component. MyPage is the parent of MyGrid and sets up all the redux state:
const MyPage = () => {
const dispatch = useDispatch();
const items= useSelector(selectors.getItems);
const fetching = useSelector(selectors.getFetching);
const fetchItems = opts => dispatch(actions.fetchItems(opts));
return (
<>
{fetching && <div>Loading...</div>}
<h1>Items</h1>
<MyGrid
items={items}
fetchItems={fetchItems}
fetching={fetching}
/>
</>
);
}
const MyGrid = ({ fetchItems, items, fetching }) => {
const [skip, setSkip] = useState(0);
const take = 100;
const [sorts, setSorts] = useState([]);
// when query opts change, get data
useEffect(() => {
const options = { skip, take };
const sortString = getSortString(sorts);
if (sortString) options['sort'] = sortString;
fetchItems(options);
}, [fetchItems, skip, sorts]);
In "MyGrid" "skip" and "sorts" can change, and should make the effect fire.
"fetchItems" is re-created everytime and causes an infinite loop. This
is my problem.
Now, the eslint react-hooks/exhaustive-deps rule is making me put fetchItems in the dependency list. I have prettier setup to autofix on save which makes it worse.
I know the Container/Presentational pattern is out of style with hooks, but it works good for my situation - I may allow swapping out MyGrid for MyList dynamically and don't want to repeat all the redux stuff in each child component.
I tried to useCallback and useMemo, but eslint just makes me put all the same dependencies in it's dependency array parameter.
Is there a way other than disabling the eslint rule
// eslint-disable-next-line react-hooks/exhaustive-deps
to make this work?
There are two ways, you can make it work.
Firstly, using useCallback for fetchItem like
const fetchItems = useCallback(opts => dispatch(actions.fetchItems(opts)), [dispatch, actions]);
Secondly using dispatch directly in child component
const dispatch = useDispatch();
useEffect(() => {
const options = { skip, take };
const sortString = getSortString(sorts);
if (sortString) options['sort'] = sortString;
dispatch(actions.fetchItems(options));
}, [dispatch, actions, skip, sorts]);

Resources