Best practises React hooks HTTP loading - reactjs

I recently started another project with react, as I had a little time to fiddle around, I used functional components with hooks. I had no problem whatsoever, there's just one thing I'm not sure I use correctly, here is an example :
function MyComponent() {
const [data, setData] = useState([]);
const [dataLoaded, setDataLoaded] = useState(false);
var getDataFromHTTP = async () { ... }
var loadData = async () => {
if (!dataLoaded) {
setDataLoaded(true);
setData(await getDataFromHTTP());
}
}
loadData();
return( ... );
}
If I like how everything is done, I suppose it's dirty to use loadData(); like in the preceding example, and I tried to useEffect with something like this :
useEffect(() => {
loadData();
}, []);
but then I got a warning like "loadData should be a dependency of useEffect". If I omit the the second argument to useEffect, it looks like it's the same as putting it directly in MyComponent. So basically, my question, in this example what is the best practise to load data once when the component is mounted ? and of course, when props/state change, what is the best practise to reload it if needed ?
EDIT:
The warning I have with useEffect is :
[Warning] ./src/list/main.js (1.chunk.js, line 25568)
Line 53: React Hook useEffect has a missing dependency: 'loadData'. Either include it or remove the dependency array react-hooks/exhaustive-deps

The way useEffect works is whenever something in the dependencies array change React will run that effect
useEffect(() => {
loadData();
}, [loadData]); // <-- dependencies array
But as you have declared loadData as a normal function it will get re-assigned to a new function on every render and it will trigger the effect.
Best way would be to wrap your loadData function in an useCallback hook
const loadData = useCallback(async () => {
if (!dataLoaded) {
setDataLoaded(true);
setData(await getDataFromHTTP());
}
}, [])

Related

Is secure to ignore the missing dependencies es-lint warning on react hooks?

I have a component which needs to call a function when is unmounted. This function is passed as a prop of the component. As far as I know, to call a function on component unmount you only need a useEffect with an empty dependency array:
useEffect(() => {
return () => { prop.onUnmount() }
}, []);
This works as expected, however ESLint complains about a missing dependency on the hook:
Line 156:6: React Hook useEffect has missing dependencies: 'props.onUnmount'. Either include them or remove the dependency array react-hooks/exhaustive-deps
However, neither solutions works,
If I remove the dependency array, the hook will be executed on each re render
If add the dependency, despite of prop.onUnmount() does not change on any rerender and the hook should not be triggered, it stills executing the return part. So the function gets called before the unmount.
So, is it safe to ignore the warning or is there any work around to execute the function and prevent ESLint to warn about it?
useEffect(() => {
return () => { prop.onUnmount() }
}, []);
This will run the prop.onUnmount function that existed on the very first render. So if that prop changes during the life of the component, your code will ignore that change, which is what the lint rule is warning you about.
If you want to run the prop.onUnmount that exists on the last render, then you'll need to do the following:
const cleanup = useRef();
// update the ref each render so if it changes the newest
// one will be used during unmount
cleanup.current = prop.onUnmount;
useEffect(() => {
return () => {
cleanup.current();
}
}, []);
If you find yourself doing this a lot, you may want to extract it to a custom hook, as in:
export const useUnmount = (fn) => {
const fnRef = useRef();
fnRef.current = fn;
useEffect(() => {
return () => {
fnRef.current();
}
}, []);
};
// used like:
useUnmount(props.onUnmount);

React useEffect hook missing dependencies linter warnings

I am using the React useEffect hook to obtain API data on component load, with the useAxios hook. The code is as below (simplified):
const [formData, setFormData] = useState<FormData>();
const [{ , executeGet] = useAxios('', {
manual: true,
});
const getFormData = async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
};
useEffect(() => {
const getData = async () => {
try {
let response = await getAPIData();
if (response) {
setFormData(response);
} catch (e) {
setFormError(true);
}
};
getData();
}, []);
This pattern is used frequently in the codebase, but I am getting the linter warning:
React Hook useEffect has missing dependencies: 'getFormData'. Either include them or remove the dependency array react-hooks/exhaustive-deps
I can suppress the warning successfully with:
// eslint-disable-line react-hooks/exhaustive-deps
but it feels wrong to do this!
I can add constants to the dependency list without a problem, however when I add the getFormData function, I get an infinite loop. I have read around the area a lot and understand why the dependencies are needed. I am not sure if the useEffect hook is the best way to obtain the data, or whether there is a way to fetch data.
The problem is that you are defining getFormData within the component. In each render, it is reassigned. As is, this would mean that your initial useEffect would only be bound to to first getFormData, not the one from the most recent render. This causes a warning because often this is not what you intend, particularly if your getFormData depended on state or props that could change.
The simplest solution in this case is to move the definition of your getFormData outside of your component, and use Axios directly instead of using a hook. That way it wouldn't need to be defined on every render anyways.
you should initiate getFormData function using useCallback hook and then put it in useEffect dependency list.
const getFormData = useCallback(async () => {
let r = await executeGet({ url: `http://blahblahblah/`});
return r.data;
}, [executeGet]);
you can read more about useCallback in reactjs site:
https://reactjs.org/docs/hooks-reference.html#usecallback

Custom react hook can not be called in useEffect with empty dependencies

Im new to react hook, Im doing a project with new feature "Hooks" of react.
I've faced a problem and I need an explain for it.
As document, to implement "componentDidMount", just pass empty array in dependencies argument.
useEffect(() => {
// some code here
}, []);
And I can call dispatch function to updateState inside this useEffect.
const [flag, setFlag] = useState(false);
useEffect(() => {
setFlag(true);
}, []);
Above code works perfectly without warning or any errors.
Now I have my custom hook, but I can not call my dispatch inside the effect.
const [customFlag, setCustomFlag] = useCustomHook();
useEffect(() => {
setCustomFlag(true);
}, []);
This is my custom hook.
function useCustomHook() {
const [success, setSuccess] = useState(false):
const component = <div>{ success ? "Success" : "Fail" }</div>;
const dispatch = useCallback(success => {
setSuccess(success);
}, []);
return [component, dispatch];
}
With above code, it requires me to put setCustomFlag inside the dependencies array.
I do not understand why. What is different between them?
Thanks for sharing.
Probably, your custom hook returns different instance of setCustomFlag on each call. It means, that useEffect() will always use first value (returned on first render). Try to memoize it by calling useCallback()/useMemo() hooks:
function useCustomHook() {
...
const setCustomFlag = useCallback(/* setCustomFlag body here */, []);
}
It would be nice to have your custom hook source to say more.
The reason is setFlag from useState is a known dependency, its value won't change between render, hence you don't have to declare it as a dependency
React eslint-plugin-react-hooks can't be sure about your custom hook, that's why you need to put that into the dependency list
This is taken from eslint-plugin-react-hooks
// Next we'll define a few helpers that helps us
// tell if some values don't have to be declared as deps.
// Some are known to be static based on Hook calls.
// const [state, setState] = useState() / React.useState()
// ^^^ true for this reference
// const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ true for this reference
// const ref = useRef()
// ^^^ true for this reference
// False for everything else.
function isStaticKnownHookValue(resolved) {
}

About infinite loop in useEffect

I am trying to build a redux process with react hooks, the code below is that I want to simulate a ComponentDidMount function with a getUsers(redux action) call in it which is a http request to fetch data.
The first version was like this
const { state, actions } = useContext(StoreContext);
const { getUsers } = actions;
useEffect(() => {
getUsers(); // React Hook useEffect has a missing dependency: 'getUsers'.
}, []);
but I got a linting warning "React Hook useEffect has a missing dependency: 'getUsers'. Either include it or remove the dependency array" in useEffect,
and then I added getUsers to dependency array, but got infinite loop there
useEffect(() => {
getUsers();
}, [getUsers])
Now I find a solution by using useRef
const fetchData = useRef(getUsers);
useEffect(() => {
fetchData.current();
}, []);
Not sure if this is the right way to do this, but it did solve the linting and the infinite loop (temporarily?)
My question is:
In the second version of the code, what exactly caused the infinite loop? does getUsers in dependency array changed after every render?
Your function have dependencies and React deems it unsafe not to list the dependencies. Say your function is depending on a property called users. Listing explicitly the implicit dependencies in the dependencies array won't work:
useEffect(() => {
getUsers();
}, [users]); // won't work
However, React says that the recommended way to fix this is to move the function inside the useEffect() function. This way, the warning won't say that it's missing a getUsers dependency, rather the dependency/ies that getUsers depends on.
function Example({ users }) {
useEffect(() => {
// we moved getUsers inside useEffect
function getUsers() {
console.log(users);
}
getUsers();
}, []); // users dependency missing
}
So you can then specify the users dependency:
useEffect(() => {
function getUsers() {
console.log(users);
}
getUsers();
}, [users]); // OK
However, you're getting that function from the props, it's not defined in your component.
What to do then? The solution to your problem would be to memoize your function.
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
You can't memoize it within your component as there will be the same warning:
const memoizedGetUsers = useCallback(
() => {
getUsers();
},
[], // same warning, missing the getUsers dependency
);
The solution is to memoize it right where the getUsers is defined and you will be then be able to list the dependencies:
// wrap getUsers inside useCallback
const getUsers = useCallback(
() => {
//getUsers' implementation using users
console.log(users);
},
[users], // OK
);
And in your component, you'll be able to do:
const { getUsers } = actions; // the memoized version
useEffect(() => {
getUsers();
}, [getUsers]); // it is now safe to set getUsers as a dependency
As to the reason why there was an infinite loop and why useRef worked. I'm guessing your function causes a rerender and at each iteration, getUsers was recreated which ends up in an endless loop. useRef returns an object { current: ... } and the difference between using useRef and creating this object { current: ... } yourself is that useRef returns the same object and doesn't create another one. So you were propbably using the same function.
You should declare getUsers inside useEffect it it's the only place you call getUsers
useEffect(() => {
const { getUsers } = props;
getUsers();
}, []);
By my information on React hooks the job of the second parameter is to be used as a variable of comparision as it would have been in shouldComponentUpdate.
By my understanding of the question getUsers is a function and not a variable that might changeon some condition and hence the infinite loop. Try passing a props that would change after getUsers is called.
import React, { useState,useEffect } from 'react';
function Input({getInputElement}) {
let [todoName, setTodoName] = useState('');
useEffect (() => {
getInputElement(todoName);
},[todoName]);
return (
<div>
<input id={'itemNameInput'} onChange={(e) => setTodoName(e.target.value)} value={todoName} />
</div>
);
}
export default Input;
useEffect : useEffect is a combination of componentDidMount, componentDidUpdate and shouldComponentUpdate. While componentDidMount runs after first render and componentDidUpdate runs after every update, useEffect runs after every render and thus covers both the scenarios. The second optional param to useEffect is the check for shouldComponentUpdate in our case [todoName].This basically checks if todoName has changed after rerender then only do whatever is inside useEffect function.
Hope this helps!

How to handle dependencies array for custom hooks in react

I'm creating a custom hook and would like to define an optional param so that I can pass in extra dependencies when needed. My code looks like the following snippet:
import { useEffect } from 'react';
function useCustomHook(param1, extraDeps) {
useEffect(() => {
// do something with param1 here
}, [param1, ...extraDeps])
}
The react-hooks/exhaustive-deps throws a warning saying
React Hook useEffect has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies
Anyone have an idea about how to address that warning? Or it's not a good practice to pass deps array to custom hook?
For those who are interested in why extraDeps is needed. here's an example:
const NewComponent = (props) => {
[field1, setField1] = useState()
[field2, setField2] = useState()
// I only want this to be called when field1 change
useCustomHook('.css-selector', [field1]);
return <div>{field1}{field2}</div>;
}
I've found a useful alternative to the solutions proposed here. As mentioned in this Reddit topic, the React team apparently recommends something like:
// Pass the callback as a dependency
const useCustomHook = callback => {
useEffect(() => { /* do something */ }, [callback])
};
// Then the user wraps the callback in `useMemo` to avoid running the effect too often
// Whenever the deps change, useMemo will ensure that the callback changes, which will cause effect to re-run
useCustomHook(
useMemo(() => { /* do something */ }, [a, b, c])
);
I've used this technique and it worked very nicely.
I had a similar issue, I wanted an effect to be executed whenever some extra dependencies were changed.
I didn't manage to give those extra dependencies, but instead I made it the way around by giving the caller the callback I wanted to be executed and let him use it when he needs.
Example :
// This hook uses extraDeps unknown by EsLint which causes a warning
const useCustomEffect = (knowDep, extraDeps) => {
const doSomething = useCallback((knownDep) => {/**/}, [])
useEffect(() => {
doSomething(knownDep)
}, [doSomething, knownDep, ...extraDeps]) // Here there is the warning
}
//Instead of this, we give the caller the callback
const useCustomEffect = (knownDep) => {
const doSomething = useCallback((knownDep) => {/**/}, [])
useEffect(() => {
doSomething(knownDep)
}, [doSomething, knownDep]) // no more warning
return { doSomething }
}
// Use it like this
const { doSomething } = useCustomEffect(foo)
useEffect(doSomething, [bar, baz]) // now I can use my callback for any known dependency
The way you have defined your custom hook makes sense to me. In this case eslint cannot check your dependencies, but that does not mean that they are wrong. Simply disable the rule for this line to get rid of the warning:
function useCustomHook(param1, extraDeps) {
useEffect(() => {
// do something with param1 here
}, [param1, ...extraDeps]) // eslint-disable-line react-hooks/exhaustive-deps
}
Depending on the type of param1, it makes sense to enable the dependency check for your custom hook by defining it in your .eslintrc.cjs:
'react-hooks/exhaustive-deps': ['warn', {
additionalHooks: '(useCustomHook|useAnotherHook|...)'
}]
Here's whay you could do:
Move the state to your custom hook, run effects on it and return it.
Something like:
Component.js
function Component() {
const [field,setField] = useCustomHook(someProps);
}
useCustomHook.js
import {useState, useEffect} from 'react';
function useCustomHook(props) {
const [field,setField] = useState('');
useEffect(()=>{
// Use props received and perform effect after changes in field 1
},[field1]);
return([
field,
setField
]);
}
If you want to provide extra-deps you can use useDeepCompareEffect instead of useEffect.
https://github.com/kentcdodds/use-deep-compare-effect
I think the problem lies in how you are creating the dependency array on your custom hook. Every time you do [param1, ... extraDeps] you are creating a new Array, so React always see them as different.
Try changing your custom hook to:
function useCustomHook(deps) {
useEffect(() => {
// do something with param1 here
}, deps)
}
And then use it like
const NewComponent = (props) => {
[field1, setField1] = useState()
[field2, setField2] = useState()
// I only want this to be called when field1 change
useCustomHook(['.css-selector', field1]);
return <div>{field1}{field2}</div>;
}
Hope it helps!

Resources