useEffect missing dependency when using redux useDispatch - reactjs

I wanna fetch my categories whenever my component is mounted using react hooks useEffect and not on every re-render. But i keep on getting this warning React Hook useEffect has a missing dependency:'dispatch'.
Here's my code:
const categories = useSelector(state => state.category.categories);
const dispatch = useDispatch();
useEffect(() => {
console.log('effecting');
const fetchCategories = async () => {
console.log('fetching');
try {
const response = await axios.get('/api/v1/categories');
dispatch(initCategory(response.data.data.categories));
} catch (e) {
console.log(e);
}
}
fetchCategories();
}, []);

You can safely add the dispatch function to the useEffect dependency array. If you look the react-redux documentation, specially the hooks section, they mention this "issue".
The dispatch function reference will be stable as long as the same
store instance is being passed to the . Normally, that store
instance never changes in an application.
However, the React hooks lint rules do not know that dispatch should
be stable, and will warn that the dispatch variable should be added to
dependency arrays for useEffect and useCallback. The simplest solution is to do just that:
export const Todos() = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTodos())
// Safe to add dispatch to the dependencies array
}, [dispatch])
}

Add dispatch to your dependency array (which is currently empty).
useEffect(() => {
console.log('effecting');
const fetchCategories = async () => {
console.log('fetching');
try {
const response = await axios.get('/api/v1/categories');
dispatch(initCategory(response.data.data.categories));
} catch (e) {
console.log(e);
}
}
fetchCategories();
}, [dispatch]);

It might be a problem with ignored promise.
fetchCategories() returns a promise.
You can try
useEffect(() => {
const fetchCategories = async () => {
try {
const response = await axios.get('/api/v1/categories');
await dispatch(initCategory(response.data.data.categories));
} catch (e) {
console.log(e);
}
}
fetchCategories().then(res => (console.log(res());
}, []);

Related

React Hook useEffect has a missing dependency: 'tasks'. Either include it or remove the dependency array

I get data from backend and set to my state in componentdidmount but value not set after log state
const [tasks, setTasks] = useState([]);
const getTasks = async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
setTasks(response.data.data);
}
};
useEffect(() => {
getTasks();
console.log(tasks);
}, []);
My tasks is empty when i log it
So the title and the question itself are actually two questions.
React Hook useEffect has a missing dependency: 'tasks'. Either includes it or remove the dependency array
That's because you include a state (i.e. tasks) in the useEffect hook. And React is basically asking you, "Do you mean run console.log(tasks) every time tasks is updated?". Because what you are doing is run the useEffect hook once and only once.
And for your "actual" question
value not set after log state
In short, states are set in async manner in React. That means tasks is not necessary immediately updated right after you call setTasks. See #JBallin comment for details.
const [tasks, setTasks] = useState([]);
useEffect(() => {
setTimeout(async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
setTasks(response.data.data);
}
}, 1000);
console.log(tasks);
}, []);
The main problem is that useEffect -> is a sync method, getTasks() is asynchronous, and useEffect only works once when your component mounts. Shortly speaking, you got your data from the backend after useEffect worked.
For example, if you will add one more useEffect
useEffect(() => {
console.log(tasks);
}, [tasks]);
You will see log, after your data will have changed.
You can use self-calling async function inside useEffect as shown here:
const [tasks, setTasks] = useState([]);
const getTasks = async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
return response.data.data;
}
};
useEffect(() => {
(async () => {
const tasks = await getTasks();
setTasks(tasks);
})();
console.log(tasks);
}, [tasks]);

Need router.query variable passed to API URL call, but is undefined on first render

I have the following API Call:
const router = useRouter();
const { albumQuery } = router.query;
const [albums, setAlbums] = useState([]);
const fetchAlbumsHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const url = `http://ws.audioscrobbler.com/2.0/?method=album.search&album=${albumQuery}&api_key=MY_API_KEY&format=json`;
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
throw new Error("Something went wrong!");
}
const jsonAlbums = data.map(
// JSON business Logic
);
setAlbums(transformedAlbums);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
With the corresponding useEffect function:
useEffect(() => {
fetchAlbumsHandler();
}, [fetchAlbumsHandler]);
However, the API call takes ${albumQuery} as undefined on the first render due to NextJS implementation details. Is there a way for me to access the variable on the first render?
No, if the albumQuery isn't available on the initial render then the code should handle waiting for it to become available.
The existing code is assuming albumQuery is available on the initial render and attempts to close it over in the useCallback hook. After this the useEffect hook is called and since fetchAlbumsHandler is now a stable reference the useEffect hook won't be retriggered nor will fetchAlbumsHandler be re-memoized since the useCallback hook has an empty dependency array.
Minimally albumQuery appears to be a dependency for the useCallback hook and/or the useEffect hook. If fetchAlbumsHandler isn't passed as a prop to children there's no real benefit to memoizing it. I suggest moving it into the useEffect hook callback and using albumQuery as a dependency.
Example:
const router = useRouter();
const { albumQuery } = router.query;
const [albums, setAlbums] = useState([]);
useEffect(() => {
const fetchAlbumsHandler = async () => {
setIsLoading(true);
setError(null);
try {
const url = `http://ws.audioscrobbler.com/2.0/?method=album.search&album=${albumQuery}&api_key=MY_API_KEY&format=json`;
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
throw new Error("Something went wrong!");
}
const jsonAlbums = data.map(
// JSON business Logic
);
setAlbums(transformedAlbums);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}
fetchAlbumsHandler();
}, [albumQuery]);

React Hook useEffect has a missing dependency for redux action as parameters

I found many similar questions here about React Hook useEffect has a missing dependency. I have already checked them, but I didn't find solutions as I faced. I want to pass redux thunk function as a parameter to React custom hook.
Below is my code and it is working fine. But, I got dependency missing warning, I don't want to add ignore warning eslint. If I add dispatchAction to dependency array list, it is dispatching again and again because redux thunk asyn function has fulfilled, reject, pending.
Custom Hook
const useFetchData = (dispatchAction, page) => {
const dispatch = useDispatch();
const [loadMoreLoading, setLoadMoreLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState();
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
fetchData();
}, [dispatch, page]);
return [loadMoreLoading, errorMsg]; // it is asking for adding dispatchAction.
My component
const SomeListing = ({userId}) => {
const [page, setPage] = useState(1);
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}),
page,
);
}
So, is there any way to be able to add redux thunk function in react custom hook?
The function fetchPropertyByUserId, when called i.e. fetchPropertyByUserId({userId: userId, page: page}), returns an "actionCreator" function.
Hence, when you call this function at the place of first parameter of your hook useFetchData, it returns a new "actionCreator" function each time (we know that hooks are called at each render):
In SomeListing.jsx:
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}), // <-- Here: it returns a new "actionCreator" function at call (render)
page,
);
And, as soon as you put this function (first parameter of the hook i.e. dispatchAction) as a dependency of useEffect, it should cause an infinite execution of the effect because, now we know, that dispatchAction is getting created (hence, changed) at every render.
In useFetchData.js:
export const useFetchData = (dispatchAction, page) => {
// ...
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true)
const resultAction = await dispatch(dispatchAction)
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message)
}
setLoadMoreLoading(false)
}
fetchData()
}, [dispatch, dispatchAction, page]) // <-- "dispatchAction" added here
// ...
How to fix it?
Pass a memoized actionCreator function:
In SomeListing.jsx:
export const SomeListing = ({ userId }) => {
const [page, setPage] = useState(1)
// Here: "fetchPropertyByUserIdMemo" is memoized now
const fetchPropertyByUserIdMemo = useMemo(
() => fetchPropertyByUserId({ userId: userId, page: page }),
[page, userId]
)
const [loadMoreLoading, errorMsg] = useFetchData(fetchPropertyByUserIdMemo, page)
// ...
}
How about extracting the fetch method from useEffect?:
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
useEffect(() => {
fetchData();
}, [fetchData]);

How to save the response info in the Redux state after fetch into useEffect hook?

I have the following case:
export default function Names() {
const dispatch = useDispatch();
const [names, setNames] = useState([]);
const stateNames = useSelector(state => state.names);
const fetchNames = async () => {
try {
const response = await nameService.getNames();
dispatch(initNames(response.body));
setNames(response.body);
} catch (error) {
console.error('Fetch Names: ', error);
}
};
useEffect(() => {
fetchNames();
}, []);
return (
{ names.map((name, index) => (
<Tab label={ budget.label} key={index}/>
)) }
);
}
When my component is rendered in the browser console I get a warning: "React Hook useEffect has a missing dependency: 'fetchBudgets'. Either include it or remove the dependency array react-hooks / exhaustive-deps".
If I comment the line in which I write the names in Redux state, the warning does not appear.
I need the list of names in the state so that I can update the list when a new name is written to the list from the outside.
export default function AddNameComponent() {
const dispatch = useDispatch();
const [label, setLabel] = useState('');
const [description, setDescription] = useState('');
const onLabelChange = (event) => { setLabel(event.target.value); };
const onDescriptionChange = (event) => { setDescription(event.target.value); };
const handleSubmit = async (event) => {
try {
event.preventDefault();
const newName = {
label: label
description: description
};
const answer = await budgetService.postNewName(newName);
dispatch(add(answer.body)); // Adding new Name in to Redux state.names
} catch (error) {
setErrorMessage(error.message);
console.error('Create Name: ', error);
}
};
return (
<div>
// Create name form
</div>
)
}
This is how everything works, but I don't understand why I have a warning.
I tried to add a flag to the array with dependencies of usеЕffect.
I tried to pass the function 'fetchNames' through the parent component - in props and to add it as a dependency, but it is executed twice ...
Can you advise please!
It's just an eslint warning so you don't have to fix it. But basically any variables which are used in the useEffect function are expected to be included in the dependency array. Otherwise, the effect will never be re-run even if the function fetchBudgets were to change.
It is expecting your hook to look like
useEffect(() => {
fetchBudgets();
}, [fetchBudgets]);
Where the effect will run once when the component is mounted and run again any time that the fetchBudgets function changes (which is probably never).
If it's executing more than once, that means that fetchBudgets has changed and you should try to figure our where and why it has been redefined. Maybe it needs to be memoized?
Here are the docs on putting functions in the dependency array.
Thanks for your attention! I tried many options and finally found one solution.
useEffect(() => {
async function fetchNames() {
const response = await nameService.getNames();
dispatch(init(response.body));
setNames(response.body);
}
fetchNames();
}, [dispatch, props]);
I put 'props' in an array of dependencies for one useEffect execution.

How can I make react's useEffect to stop rerender in an infinite loop even with a dependency specified?

I am working on a small CRUD fullstack app with react and mongodb and I have this problem where I use useEffect to make an axios get request to the server to get all of my todos. The problem is that useEffect does it's job but it also rerenders to infinity. This is my component:
export default function () {
...
const [todos, setTodos] = useState([]);
const currentUser = JSON.parse(localStorage.getItem('user'))._id;
useEffect(() => {
async function populateTodos () {
try {
const res = await axios.get(`http://localhost:8000/api/all-todos/${currentUser}`);
setTodos(res.data);
} catch (err) {
if (err.response) {
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
} else if (err.request) {
console.log(err.request);
} else {
console.log('Error: ', err.message);
}
}
}
populateTodos();
}, [todos]);
console.log(todos);
return (
...
);
}
So what I was expecting to happen is that that console.log to get printed only when the todos changes, like when I add a new todo and so on, but instead it gets printed forever.
You said that you need to fetch todos at first, and whenever todos change. I can suggest you a different approach, using one more variable, something like this:
const TodosComponent = (props) => {
const [todos, setTodos] = useState([]);
const [updatedTodos, setUpdatesTodos] = useState(true);
const fetchFunction = () => {
// In here you implement your fetch, in which you call setTodos().
}
// Called on mount to fetch your todos.
useEffect(() => {
fetchFunction();
}, []);
// Used to updated todos when they have been updated.
useEffect(() => {
if (updatedTodos) {
fetchFunction();
setUpdatesTodos(false);
}
}, [updatedTodos]);
// Finally, wherever you update your todos, you also write `updateTodos(true)`.
}

Resources