I have a functional component Pets
that shows all of the pets of the current logged in user. Inside of my Pets
component I am using useEffect
and useState
to achieve this.
const [pets, setPets] = useState([]);
useEffect(() => {
const fetchPets = async () => {
try {
const { data } = await axios.get('/pet/mypets');
setPets(data.pets);
} catch (error) {
console.log(error);
}
};
fetchPets();
}, []);
I then render all the pets in a table row by row. Problem is that when I tried doing this using useContext
it ended up in an infinite loop. For instance, I have a file called petsContext
with the following code...
import React from 'react';
const PetsContext = React.createContext({
pets: [],
onRegister: (first_name, last_name, breed, age, weight) => {},
onUpdate: (first_name, last_name, breed, age, weight) => {},
onDelete: () => {},
onFetch: () => {},
});
export default PetsContext;
I have a petsProvider
file with the following...
import React, { useReducer } from 'react';
import PetContext from './pets-context';
import axios from 'axios';
const initialState = {
pets: [],
message: '',
messageType: '',
};
const petReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_PETS_SUCCESS':
return {
...state,
pets: action.payload,
};
case 'REGISTER_PET_SUCCESS':
if (state.pets === undefined) {
state.pets = [];
}
return {
...state,
pets: [action.payload.pet, ...state.pets],
message: action.payload.message,
};
case 'REGISTER_PET_FAIL':
return {
...state,
message: 'Unable to register pet!',
};
default:
return initialState;
}
};
const PetProvider = (props) => {
const [petState, dispatchPetAction] = useReducer(petReducer, {
petReducer,
initialState,
});
const registerHandler = async (first_name, last_name, breed, age, weight) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
age = parseInt(age);
weight = parseFloat(weight);
const body = JSON.stringify({ first_name, last_name, breed, age, weight });
try {
const { data } = await axios.post('/pet/register', body, config);
dispatchPetAction({ type: 'REGISTER_PET_SUCCESS', payload: data });
} catch (error) {
console.log(error.response);
// dispatchPetAction({
// type: 'REGISTER_PET_FAIL',
// payload: {
// message: error.response.data.message,
// messageType: 'danger',
// },
// });
}
};
const fetchHandler = async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
};
return (
<PetContext.Provider
value={{
pets: petState.pets,
onRegister: registerHandler,
onFetch: fetchHandler,
}}
>
{props.children}
</PetContext.Provider>
);
};
export default PetProvider;
and in my Pets
component instead of having what I showed before I had the following...
const petsContext = useContext(PetsContext);
useEffect(() => {
petsContext.onFetch();
}, [petsContext]);
// now I want to just access my pets by `petsContext.pets`
// which works but ends up in an infinite loop and I am not sure why.
How can I fix this infinite loop and why is it happening?
The infinite loop start from your context.
To fix it:
// wrap fetchHandler in useCallback
const fetchHandler = useCallback(async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
}, [dispatchPetAction]);
const { onFetch } = useContext(PetsContext);
// this effect should depend on onFetch, not petsContext
// now, it will only being call if dispatchPetAction is changed
// dispatchPetAction -> fetchHandler -> useEffect
useEffect(() => {
onFetch();
}, [onFetch]);