Should I ignore 'React Hook useEffect has a missing dependency' warning?
Usually when I am getting data from an API this is what I do:
const Component = () => {
const [data,setData] = useState([]);
const getData = () => {
//Getting data and set data code...
}
useEffect(()=>{
getData();
},[]);
}
and recently I am trying out use redux to do the same thing(getting data from API) and I got this 'React Hook useEffect has a missing dependency' warning...
action:
import {GET_POSTS} from './types';
const getPosts = () => (dispatch) => {
const url = 'https://jsonplaceholder.typicode.com/posts';
fetch(url)
.then(res => res.json())
.then(data => {
dispatch({
type: GET_POSTS,
payload: data
});
});
}
export default getPosts;
reducer:
import {GET_POSTS} from '../actions/types';
const initialState = {
posts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type){
case GET_POSTS:
return {
...state,
posts: action.payload
}
default:
return state;
}
}
export default postsReducer;
app.js:
import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import Hello from './components/Hello';
import getPost from './actions/postsAction';
import './App.css';
const App = ({getPost, dispatch}) => {
useEffect(() => {
getPost();
},[]);
return (
<div className='App'>
<Hello/>
</div>
);
};
const mapdispatchtoprops = (dispatch) => ({
dispatch,
getPost: () => {
dispatch(getPost());
}
});
export default connect(null, mapdispatchtoprops)(App);
Is there a way to fix this problem, I have tried to put dispatch inside the useEffect array but the warning still shows, like this:
useEffect(() => {
getPost();
},[dispatch]);
This is the full warning: React Hook useEffect has a missing dependency: 'getPost'. Either include it or remove the dependency array react-hooks/exhaustive-deps
Tried to remove the useEffect array but I'll get infinite loop, it'll just keeps getting the data from the api(I only need it to run once).
Should I ignore the warning? if not, whats the best practice way to handle this problem?
I never got this kind of warning before when I left the useEffect array empty but got it recently, why?
The error message is telling you what you to do. Just add getData to the dependencies array like so: [dispatch, getData]. Anything external you reference within your useEffect (like a function) should be part of the dependency list so it can trigger the effect whenever the value changes. In your case it likely won't, but React is warning you just to be safe. Hope that helps!
You may want to start thinking from a different perspective. You are apparently trying to do side effect of loading data after component got rendered. So just inject your data via redux or propagation props from parent and remove array altogether. I.e.
const Component = ({posts}) => {
const getData = () => {
//Getting data and set data code...
}
useEffect(() => {
if (!posts) {
getData();
}
});
....
}
Your posts will be loaded once and useEffect's function should only care about posts is there or not.
Related
This the code that is responsible for getting Data and updating the list:
import {createSlice} from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
log: []
}
let page = 1;
const cartSlice = createSlice({
name: 'cart',
initialState: initialState,
reducers: {
fetching: (state, payload) => {
state.log = axios.get(`http://localhost:5000/fetch/?${page = payload.payload}`)
// console.log(state)
}
}
})
export const {fetching} = cartSlice.actions;
export default cartSlice.reducer;
And this is the Home Page:
import React, {useEffect} from "react";
import {useDispatch, useSelector} from "react-redux";
import {fetching} from "../features/cardSlice";
import Cards from "./Cards";
export default function HomePage(){
const dispatch = useDispatch();
const {itemsList} = useSelector((store) => store.card)
console.log(itemsList)
const {pageNumber} = useSelector((store) => store.page);
useEffect(()=>{
dispatch(fetching(1)); **// This is where i call dispatch to update the state and get the data**
})
function cardMapper(items) {
return(
<Cards
name = {items.name}
key = {items.id}
cuisine={items.cuisine}
address={items.address}
/>
)
}
return(
<div>
{/*{itemsList.map(cardMapper)}*/}
</div>
)
}
When i run this on localhost i am not able to get data, the console.log(itemList) is showing undefined and also the dispatch(fetching(1)) is called infinite times.
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I am not able to understand why i'm getting an infinite loop and also why am i not getting the data.
I modified your code. To prevent infinite loop in using useEffect you should put [] as dependency
And for the useSelector,the variable should not enclosed by {}.
see the modified code below.
As I see, it should be cart not card
Try this code instead
export default function HomePage(){
const dispatch = useDispatch();
const itemsList = useSelector((store) => store.cart)
console.log(itemsList)
const {pageNumber} = useSelector((store) => store.page);
useEffect(()=>{
dispatch(fetching(1)); **// This is where i call dispatch to update the state and get the data**
},[])
function cardMapper(items) {
return(
<Cards
name = {items.name}
key = {items.id}
cuisine={items.cuisine}
address={items.address}
/>
)
}
return(
<div>
{/*{itemsList.map(cardMapper)}*/}
</div>
)
}
also in reducer
add a return
fetching: async (state, payload) => {
const newState = await axios.get(`http://localhost:5000/fetch/?${page = payload.payload}`)
return newState;
}
// this should end the infinite loop
useEffect(()=>{
dispatch(fetching(1)); // This is where i call dispatch to update the state and get the data
}, [ ])
You need added dependencies as an empty array. To remove the yellow
squiggly line you need to add dispatch inside that dependencies array.
however, react to ensure that dispatch dependencies never change and
that you are not responsible for re-render your component.
useEffect(()=>{
dispatch(fetching(1))
},[dispatch])
export const itemReducer = (state, action) => {
switch (action.type) {
default:
return state
}
}
import React, { useState, useReducer, createContext, useContext } from 'react'
import { useQuery } from '#apollo/client'
import { CURRENT_MONTH_BY_USER } from '../graphql/queries'
import { itemReducer } from '../reducers/ItemReducer'
const Items = createContext()
export const ItemProvider = ({ children }) => {
let items = []
const [state, dispatch] = useReducer(itemReducer, { items: items })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
items = [...result.data.getCurrentMonthByUser]
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
export const ItemsState = () => {
return useContext(Items)
}
export default ItemProvider
let items gets correct data from the useQuery, however nothing is passed into the state, therefore I am unable to transfer data into another components from the context. What am I doing wrong here?
When debugging both items and state they're initially empty because of the loading however then only the items receives correct data and state remains as empty array.
If i put static data into let items it works just fine, so maybe there can be something wrong with my useQuery as well?
It's easy to see your problem if you look at where items is used. That's only as the initial state to your useReducer call - but items is only set to a non-empty value after this. That has absolutely no effect on the component, because items is not used later in your component function, and the initial state is only ever set once, on the first render.
To solve this you need to embrace your use of a reducer, adding a new action type to set this initial data, and then dispatching that when you have the data. So add something like this to your reducer:
export const itemReducer = (state, action) => {
switch (action.type) {
case SET_INITIAL_DATA: // only a suggestion for the name, and obviously you need to define this as a constant
return { ...state, items: action.items };
/* other actions here */
default:
return state
}
}
and then rewrite your component like this:
export const ItemProvider = ({ children }) => {
const [state, dispatch] = useReducer(itemReducer, { items: [] })
const result = useQuery(CURRENT_MONTH_BY_USER)
if (result.data && result.data.getCurrentMonthByUser) {
dispatch({ type: SET_INITIAL_DATA, items: result.data.getCurrentMonthByUser });
}
return <Items.Provider value={{ state, dispatch }}>{children}</Items.Provider>
}
Also, while this is unrelated to your question, I will note that your ItemsState export appears to be a custom hook (it can't be anything else since it isn't a component but uses a hook) - that is perfectly fine but there is a very strong convention in React that all custom hooks have names of the form useXXX, which I strongly suggest you should follow. So you could rename this something like useItemsState (I would prefer useItemsContext to make clear it's just a useContext hook specialised to your specific context).
I have this React Component
import React, { useState, useEffect } from 'react';
import axios from "axios";
import "../../css/driversStandings.css";
function DriversStandingsComponent() {
const [data, setData] = useState([]);
var row = 1;
useEffect(() => {
axios.get("http://localhost:4000/api/standings").then(res => {
const driversChampionshipData = res.data[0].DriversChampionship
setData(driversChampionshipData);
console.log(data)
})
});
return (
//Here I return a mdbootstrap table, mapping the data array
)
}
export default DriversStandingsComponent;
I don't really understand why this happens, and if it affects the server performance.
Any idea for solving this? I don't even know if it's an error itself 😅
useEffect is called every time a component rerenders. You sholud add empty dependency array, that way useEffect calls only when component is mounted, like this:
useEffect(() => {
axios.get("http://localhost:4000/api/standings").then(res => {
const driversChampionshipData = res.data[0].DriversChampionship
setData(driversChampionshipData);
console.log(data)
})
}, []);
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch 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.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch 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.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}