Why I can't use dispatch a thunk inside useEffect? - reactjs

Inside thunks.js
export const displayAlert = (text) => () => { alert(`${text}`); }
Inside another file
const dispatch = useDispatch();
const example = () => {
useEffect(
()=>{dispatch(displayAlert('Hello'))}
) }
Shows me
Uncaught Error: Invalid hook call

Move the useDispatch hook into the component, and rename it with capital letter (to not trigger another lint warning):
const Example = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(displayAlert("Hello"));
});
};
See rules of hooks.

Related

Invalid Hook Call for custom hook

I have written a function for API calls. I want to reuse this function from a different page.
FetchData.js
export const FetchData = (url, query, variable) => {
const [fetchData, setFetchData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const queryResult = await axios.post(
url, {
query: query,
variables: variable
}
)
const result = queryResult.data.data;
setFetchData(result.mydata)
};
fetchData();
})
return {fetchData, setFetchData}
}
Here is my main page from where I am trying to call the API using the following code
mainPage.js
import { FetchData } from './FetchData'
export const MainPage = props => {
const onClick = (event) => {
const {fetchData, setFetchData} = FetchData(url, query, variable)
console.log(fetchData)
}
}
It is returning the following error -
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
If you need to fetch data on response to an event, you don't need a useEffect.
const useData = (url, query, variable) => {
const [data, setData] = useState([]);
const fetchData = async () => {
const queryResult = await axios.post(url, {
query: query,
variables: variable,
});
setData(queryResult.data.data);
};
return {data, fetchData}
};
export const MainPage = (props) => {
const {data, fetchData} = useData(url, query, variable);
const onClick = (event) => {
fetchData()
};
};
Hooks can't be used inside handler functions.
Do this instead:
import { FetchData } from './FetchData'
export const MainPage = props => {
const {fetchData, setFetchData} = FetchData(url, query, variable)
const onClick = (event) => {
console.log(fetchData)
}
}

Why React detect the function reference as new?

I have theorical question about custom hooks and use effect when redux is involved.
Let`s assume I have this code:
//MyComponent.ts
import * as React from 'react';
import { connect } from 'react-redux';
const MyComponentBase = ({fetchData, data}) => {
React.useEffect(() => {
fetchData();
}, [fetchData]);
return <div>{data?.name}</data>
}
const mapStateToProps= state => {
return {
data: dataSelectors.data(state)
}
}
const mapDispatchToProps= {
fetchData: dataActions.fetchData
}
export const MyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponentBase);
This works as expected, when the component renders it does an async request to the server to fetch the data (using redux-thunk). It initializes the state in the reduces, and rerender the component.
However we are in the middle of a migration to move this code to hooks. Se we refactor this code a little bit:
//MyHook.ts
import { useDispatch, useSelector } from 'react-redux';
import {fetchDataAction} from './actions.ts';
const dataState = (state) => state.data;
export const useDataSelectors = () => {
return useSelector(dataState);
}
export const useDataActions = () => {
const dispatch = useDispatch();
return {
fetchData: () => dispatch(fetchDataAction)
};
};
//MyComponent.ts
export const MyComponent = () => {
const data = useDataSelectors()>
const {fetchData} = useDataActions();
React.useEffect(() => {
fetchData()
}, [fetchData]);
return <div>{data?.name}</data>
}
With this change the component enters in an infite loop. When it renders for the first time, it fetches data. When the data arrives, it updates the store and rerender the component. However in this rerender, the useEffect says that the reference for fetchData has changed, and does the fetch again, causing an infinite loop.
But I don't understand why the reference it's different, that hooks are defined outside the scope of the component, they are not removed from the dom or whateverm so their references should keep the same on each render cycle. Any ideas?
useDataActions is a hook, but it is returning a new object instance all the time
return {
fetchData: () => dispatch(fetchDataAction)
};
Even though fetchData is most likely the same object, you are wrapping it in a new object.
You could useState or useMemo to handle this.
export const useDataActions = () => {
const dispatch = useDispatch();
const [dataActions, setDataActions] = useState({})
useEffect(() => {
setDataActions({
fetchData: () => dispatch(fetchDataAction)
})
}, [dispatch]);
return dataActions;
};
first of all if you want the problem goes away you have a few options:
make your fetchData function memoized using useCallback hook
don't use fetchData in your useEffect dependencies because you don't want it. you only need to call fetchData when the component mounts.
so here is the above changes:
1
export const useDataActions = () => {
const dispatch = useDispatch();
const fetchData = useCallback(() => dispatch(fetchDataAction), []);
return {
fetchData
};
};
the 2nd approach is:
export const MyComponent = () => {
const data = useDataSelectors()>
const {fetchData} = useDataActions();
React.useEffect(() => {
fetchData()
}, []);
return <div>{data?.name}</data>
}

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]);

useEffect missing dependency when using redux useDispatch

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());
}, []);

How can I use custom method in UseEffect?

How can I use custom method in useEffect??
If I create many components and they use same fetch function, Shoud I declare fetch function in every component's effect?? The function does same work??
As far as I know, If I want to use component's state in useEffect, I should declare and call that function in useEffect likes example 1.
But I want to declare the function other js file. Because it was called other components.
According to Dan Abramov (https://overreacted.io/a-complete-guide-to-useeffect/), If I want to move function, I must use useCallback method.
But I didn't understand well. Please give me any advice this issue.
1. Component.js
const Component = () => {
const [id,setId] = useState(0);
const dispatch = useDispatch();
useEffect(() => {
fetch(`url/${id}`).then(res => dispatch({type: success, payload: res}))
},[id])
}
2. Component.js
const Component = () => {
const [id, setId] = useState(0);
useEffect(()=> {
callApi(id)
},[id])
}
Api.js
const callApi = (id) => {
const dispatch = useDispatch();
return fetch(`url/${id}`).then(res => dispatch({type:success, payload:res})
}
Shoud I declare fetch function in every component's effect?
Extract a custom hook, useFetch(), with the same fetch functionality.
// custom hook
const useFetch = (id) => {
const [data, setData] = useState(null);
useEffect(
() => {
async function fetchData() {
const res = await fetch(`url/${id})
setData(res);
}
fetchData();
}, [id] // id as dependency
)
return data;
}
// sample component using custom hook
const Component = (props) => {
const dispatch = useDispatch();
const data = useFetch(props.id); // use custom hook
useEffect(
() => {
if (data) {
dispatch({type: success, payload: data});
}
}, [data] // dispatch every time data changes
)
}
Since multiple of your components perform the same action within useEffect, you can extract out the code into a custom hook and use it in all the components
useFetch.js
export const useFetch = () => {
const dispatch = useDispatch();
useEffect(() => {
fetch(`url/${id}).then(res => dispatch({type: success, payload: res}))
},[id])
}
Now in the component you can write
const Component = () => {
const [id, setId] = useState(0);
useFetch(id);
}

Resources