What are those depdendencies?
useEffect takes an optional second argument which is an array of dependencies. Dependencies of the useEffect hook tell it to run the effect whenever one if its dependency changes.
If you don't pass an optional second argument to useEffect hook, it will execute every time the component re-renders. Empty dependency array specifies that you want to run the effect only once, after the initial render of the component. In this case, useEffect hook will almost behave like componentDidMount in class components.
why is it advised to be exhaustive on them ?
Effects see props and state from the render they were defined in. So when you use something from the scope of functional component that participates in react's data flow like props or state, inside the callback function of useEffect hook, that callback function function will close over that data and unless new effect is defined with the new values of props and state, your effect will see the stale props and state values.
Following code snippet demonstrates what could go wrong if you lie about the dependencies of useEffect hook.
In the following code snippet, there are two components, App and User. App component has three buttons and maintains the id of the user which is displayed by the User component. User id is passed as a prop from App to User component and User component fetches the user with the id, passed as prop, from the jsonplaceholder API.
Now the problem in the following code snippet is that it doesn't works correctly. Reason is that it lies about the dependency of the useEffect hook. useEffect hook depends on userID prop to fetch the user from the API but as i skipped adding userID as a dependency, useEffect hook doesn't executes every time userID prop changes.
function User({userID}) {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
if (userID > 0) {
fetch(`https://jsonplaceholder.typicode.com/users/${userID}`)
.then(response => response.json())
.then(user => setUser(user))
.catch(error => console.log(error.message));
}
}, []);
return (
<div>
{user ? <h1>{ user.name }</h1> : <p>No user to show</p>}
</div>
);
}
function App() {
const [userID, setUserID] = React.useState(0);
const handleClick = (id) => {
setUserID(id);
};
return(
<div>
<button onClick={() => handleClick(1)}>User with ID: 1</button>
<button onClick={() => handleClick(2)}>User with ID: 2</button>
<button onClick={() => handleClick(3)}>User with ID: 3</button>
<p>Current User ID: {userID}</p>
<hr/>
<User userID={userID} />
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Above code snippet shows one of the few problems that could arise if you lie about the dependencies and this is why you must not skip or lie about the dependencies of the useEffect hook or any other hook that has a dependency array, for example useCallback hook.'
To fix the previous code snippet, you just have to add userID as a dependency to the dependency array of useEffect hook so that if executes whenever userID prop changes and new user is fetched with the id equal to userID prop.
function User({userID}) {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
if (userID > 0) {
fetch(`https://jsonplaceholder.typicode.com/users/${userID}`)
.then(response => response.json())
.then(user => setUser(user))
.catch(error => console.log(error.message));
}
}, [userID]);
return (
<div>
{user ? <h1>{ user.name }</h1> : <p>No user to show</p>}
</div>
);
}
function App() {
const [userID, setUserID] = React.useState(0);
const handleClick = (id) => {
setUserID(id);
};
return(
<div>
<button onClick={() => handleClick(1)}>User with ID: 1</button>
<button onClick={() => handleClick(2)}>User with ID: 2</button>
<button onClick={() => handleClick(3)}>User with ID: 3</button>
<p>Current User ID: {userID}</p>
<hr/>
<User userID={userID} />
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In your case, if you skip adding props.userID in the dependency array of useEffect hook, your effect will not fetch the new data when the prop userID changes.
To learn more about the negative impacts of omitting the dependencies of useEffect hook, see:
How can I solve my case cleanly ?
Since your effect depends on the prop value userID, you should include it in the dependency array to always fetch the new data whenever the userID changes.
Adding props.userID as a dependency to useEffect hook will trigger the effect every time props.userID changes but the problem is that you are unnecessarily using username inside the useEffect. You should remove it because that is not needed since username value doesn't and shouldn't decide when new user data should be fetched. You just want the effect to run whenever props.userID changes.
You could also decouple the action from the state update by using the useReducer hook to manage and update the state.
Edit
Since you only want to run the effect even when userID is used by useEffect hook, in your case it is ok to have an empty array as the second argument and ignore the eslint warning. You could also not omit any dependencies of the useEffect hook and use some condition in useEffect hook that evaluates to false after the effect runs for the first time and updates the state.
Personally i would suggest you to try to change how your components are structured so that you don't have to deal with this kind of problem in the first place.