Missing or unused dependency array in useEffect - reactjs

When implementing the hook code from here
https://blog.logrocket.com/patterns-for-data-fetching-in-react-981ced7e5c56/
I get the following warning, what does this actually mean?
./src/components/Users.tsx
Line 20:6: React Hook useEffect has a missing dependency: 'data.users'. Either include it or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setData' needs the current value of 'data.users' react-hooks/exhaustive-deps
code:
import React, { useEffect, useState } from "react";
import axios from "axios";
const USER_SERVICE_URL = "https://jsonplaceholder.typicode.com/users";
export function List() {
const [data, setData] = useState({ users: [], isFetching: false });
useEffect(() => {
const fetchUsers = async () => {
try {
setData({ users: data.users, isFetching: true });
const response = await axios.get(USER_SERVICE_URL);
setData({ users: response.data, isFetching: false });
} catch (e) {
console.log(e);
setData({ users: data.users, isFetching: false });
}
};
fetchUsers();
}, []);
console.log(data)

It means that since you use data.users in the code inside the useEffect block, and you've define an empty dependencies array, data.users might be stale.
To solve that problem use the setState updater function. Which allows you to use the previous state, while creating the new one:
setData(data => ({ users: data.users, isFetching: true }));
Or
setData(data => ({ users: data.users, isFetching: false }));
Another options is to create a simple reducer, that will work like setState(), but will override changed items, and not the entire state:
const reducer = (state, payload) => ({ ...state, ...payload });
export function List() {
const [data, setData] = useReducer(reducer, { users: [], isFetching: false });
useEffect(() => {
const fetchUsers = async () => {
try {
setData({ isFetching: true });
const response = await axios.get(USER_SERVICE_URL);
setData({ users: response.data, isFetching: false });
} catch (e) {
console.log(e);
setData({ isFetching: false });
}
};
fetchUsers();
}, []);
console.log(data)

Related

How to test the useEffect hook using Jest

How can we initite the useEffect using jest to wite the test cases.
let initailState = {
loading: false,
card: [],
welcomd: true
}
const helloWorld = () => {
const [state, setState] = useState(initialState);
useEffect(() => {
axios.get(url)
.then(res => {
setState(...state, card: res.data, loading: true);
})
.catch(error => {
setState(error.respone.data);
});
},[]);
return(
{
state.loading && <h1> Welcome to Stackoverflow </h1>
}
);
}
I am not able to write the test case for this sceniarion based on hook

Infinity loop when using custom hook and reducer

I have infinity loop when using custom hook and reducer in React Hook. Custom hook has been infinity loop when moving dispatch setLoading into async function. But when I calling dispatch action out of async function scope, I have related issue with dispatch action. setLoading(false) will called before async function has been completed. Please help me clarify it
That is my code
reducer:
import { SET_ERROR, SET_LOADING } from "./common.type";
export const commonInitial = {
isLoading: false,
isError: {
message: "",
status: "",
},
};
const commonReducer = (state: any, action: any) => {
switch (action.type) {
case SET_LOADING:
return { ...state, isLoading: action.payload };
case SET_ERROR:
return { ...state, isError: action.payload };
default:
return { ...state };
}
};
export default commonReducer;
useFetch (custom hook)
export const useFetch = (url: string) => {
const [response, setResponse] = useState([]);
const [error, setErr] = useState({ message: "", status: "" });
//fetching state handler
const { dispatch } = useContext(CommonContext);
useEffect(() => {
const fetchDataFromUrl = async () => {
//show loading
dispatch(setLoading(true));
try {
const { data } = await axios.get(url);
setResponse(data);
dispatch(setLoading(false));
} catch (err: any) {
//handle error
dispatch(setLoading(false));
setErr(err);
}
};
fetchDataFromUrl();
}, [url]);
useEffect(() => {
dispatch(setError({ message: error.message }));
}, [error]);
return [response];
};
hook called
// using custom hook fetch data
const [data] = useFetch("https://jsonplaceholder.typicode.com/posts");
return (
<div>
<ul>
{!!data?.length &&
data.map((item: Post) => {
return (
<div key={item.id}>
<Link to={`/post/${item.id}`}>Moves to Post {item.id}</Link>
</div>
);
})}
</ul>
</div>
);
};
export default DemoHookA;
I am not sure, but the issue could be because of useEffect hook. As per React 18 in invoked multiple types. For getting rid of this rendering issue, you can create a ref that would check if the component is mounted or not and updating the ref once it is mounted.
You could achive this by following way.
import React, {
useEffect,
useRef
} from 'react';
function YourComponent() {
const isRendered = useRef(false);
// UseEffect
useEffect(() => {
if (!isRendered.current) {
// Your code goes here...
}
return () => {
// Update isRendered
isRendered.current = true;
}
})
}
export default YourComponent;
Put all the code that you have inside useEffect inside the if condition.
Hope it will work for you.

Using state in axios interceptors

I'm trying to optimize performance of my app and was thinking about implementing axios interceptors.
And trying to do so, I run into problem that I can not understand.
Here is my App.js file:
function App() {
const Stack = createStackNavigator();
const initialLoginState = {
isLoading: true,
userToken: null,
};
const loginReducer = (prevState, action) => {
switch (action.type) {
case 'RETRIEVE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'LOGIN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'LOGOUT':
return {
...prevState,
userToken: null,
isLoading: false,
};
}
};
const [loginState, dispatch] = useReducer(loginReducer, initialLoginState);
const authContext = useMemo(
() => ({
signIn: async (userToken) => {
await setStoredItem('token', userToken);
dispatch({type: 'LOGIN', token: userToken});
},
signOut: async () => {
await deleteStoredItem('token');
dispatch({type: 'LOGOUT'});
},
}),
[],
);
useEffect(() => {
setTimeout(async () => {
let userToken = null;
let token = await getStoredItem('token');
if (token != undefined && token.length > 0) {
userToken = token;
}
dispatch({type: 'RETRIEVE_TOKEN', token: userToken});
}, 1000);
}, []);
axios.interceptors.request.use((config) => {
console.log(
'loginState.userToken INSIDE intercetor',
loginState.userToken,
);
// request.headers.Authorization = loginState.userToken ? `Bearer ${userToken}` : '';
return config;
});
console.log('state OUTSIDE intercetor', loginState.userToken);
As you can see I'm using useReducer react hook for Authentication state management and then I conditionally render routes depending if loginState.userToken !== null or not.
I was trying to add loginState.userToken to interceptor (commented out code) and then something weird happens - State value seem to change and I dont know why.
See screenshot below:
At first it accurate (actual token), but then it changes to null?
What am I missing here?
Thank you!

use callback with useState hook in reactjs

i'm trying to update the user info on my database
on the handleChange function every time there is a new change but the problem that im facing is that i have to to wait for the setdata till it finish then updateUserInfo how i can solve that
const [mydata, setData] = useState({
user_gender: "",
user_relationship: "",
user_birth_day: "",
user_birth_month: "",
user_gender_interest: "",
user_birth_year: "",
user_interests: {
dancing: false,
family: false,
art: false,
photography: false,
friends: false,
travel: false
}
});
const handleChange = event => {
setData({
...mydata,
[event.target.name]: event.target.value
});
async function update() {
await updateUserInfo(mydata[event.target.name], stableDispatch);
}
update();
};
Call updateUserInfo() as a callback.
You can pass a function as a 2nd parameter to setState() which will automatically be called when the state is set.
useEffect(() => {
updateUserInfo(mydata[event.target.name], stableDispatch));
}, [mydata]);
The solution here is to copy the state in a variable which you can use to update state and the userInfo
const handleChange = event => {
const data = {
...mydata,
[event.target.name]: event.target.value
}
setData(data);
async function update() {
await updateUserInfo(data[event.target.name], stableDispatch);
}
update();
};

How can I access current redux state from useEffect?

I have a list of objects ("Albums" in my case) fetched from the database. I need to edit these objects.
In the editing component in the useEffect hook I fire up the action for getting the needed album using it's ID. This action works. However in the same useEffect I am trying to fetch the changed by before fired action redux state. And now I face the problem - all I am fetching is the previos state.
How can I implement in the useEffect fetching of current redux state?
I've seen similar questions here, however none of the answers were helpfull for my use case.
I am using redux-thunk.
Editing component. The problem appears in setFormData - it's fetching previous state from the reducer, not the current one. It seems that it fires before the state gets changed by the getAlbumById:
//imports
const EditAlbum = ({
album: { album, loading},
createAlbum,
getAlbumById,
history,
match
}) => {
const [formData, setFormData] = useState({
albumID: null,
albumName: ''
});
useEffect(() => {
getAlbumById(match.params.id);
setFormData({
albumID: loading || !album.albumID ? '' : album.albumID,
albumName: loading || !album.albumName ? '' : album.albumName
});
}, [getAlbumById, loading]);
const { albumName, albumID } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = e => {
e.preventDefault();
createAlbum(formData, history, true);
};
return ( //code );
};
EditAlbum.propTypes = {
createAlbum: PropTypes.func.isRequired,
getAlbumById: PropTypes.func.isRequired,
album: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
album: state.album
});
export default connect(
mapStateToProps,
{ createAlbum, getAlbumById }
)(withRouter(EditAlbum));
Action:
export const getAlbumById = albumID => async dispatch => {
try {
const res = await axios.get(`/api/album/${albumID}`);
dispatch({
type: GET_ALBUM,
payload: res.data
});
} catch (err) {
dispatch({
type: ALBUMS_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
reducer
const initialState = {
album: null,
albums: [],
loading: true,
error: {}
};
const album = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_ALBUM:
return {
...state,
album: payload,
loading: false
};
case ALBUMS_ERROR:
return {
...state,
error: payload,
loading: false
};
default:
return state;
}
};
Will be grateful for any help/ideas
You should split up your effects in 2, one to load album when album id changes from route:
const [formData, setFormData] = useState({
albumID: match.params.id,
albumName: '',
});
const { albumName, albumID } = formData;
// Only get album by id when id changed
useEffect(() => {
getAlbumById(albumID);
}, [albumID, getAlbumById]);
And one when data has arrived to set the formData state:
// Custom hook to check if component is mounted
// This needs to be imported in your component
// https://github.com/jmlweb/isMounted
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
// In your component check if it's mounted
// ...because you cannot set state on unmounted component
const isMounted = useIsMounted();
useEffect(() => {
// Only if loading is false and still mounted
if (loading === false && isMounted.current) {
const { albumID, albumName } = album;
setFormData({
albumID,
albumName,
});
}
}, [album, isMounted, loading]);
Your action should set loading to true when it starts getting an album:
export const getAlbumById = albumID => async dispatch => {
try {
// Here you should dispatch an action that would
// set loading to true
// dispatch({type:'LOAD_ALBUM'})
const res = await axios.get(`/api/album/${albumID}`);
dispatch({
type: GET_ALBUM,
payload: res.data
});
} catch (err) {
dispatch({
type: ALBUMS_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
Update detecting why useEffect is called when it should not:
Could you update the question with the output of this?
//only get album by id when id changed
useEffect(() => {
console.log('In the get data effect');
getAlbumById(albumID);
return () => {
console.log('Clean up get data effect');
if (albumID !== pref.current.albumID) {
console.log(
'XXXX album ID changed:',
pref.current.albumID,
albumID
);
}
if (getAlbumById !== pref.current.getAlbumById) {
console.log(
'XXX getAlbumById changed',
pref.current.getAlbumById,
getAlbumById
);
}
};
}, [albumID, getAlbumById]);

Resources