I'm semi-new to React and learning.
I implemented a useEffect() hook as follows:
import { fetchDetails } from "../../ApiServices/Details";
export interface RemoveModalProps {
id: number;
isVisible: boolean;
onHide: () => void;
handleRemove: (resp: JsonResponseModel<ApiResponse>) => void;
}
const RemoveModal= ({ id, isVisible, onHide, handleRemove }: RemoveModalProps) => {
const [details, setDetails] = React.useState<DetailsModel>();
React.useEffect(() => {
console.log(id);
async function getDetails() {
var details= await fetchDetails(id);
console.log(details.Data);
setDetails(details.Data);
}
getDetails();
}, []);
However, for reasons I'm not quite able to understand, the console.log(id) is returning null. However, I have the following function just below the useEffect()
const handleSubmit = async () => {
const isValid = formRef.current.checkValidity();
setValidated(true);
if (isValid) {
console.log(id);
setSaving(true);
const resp = await removeDetails(id);
}
};
and THIS console.log(id) is logging the correct id, so obviously the id is being properly passed as a prop. What am I doing wrong with my useEffect hook that it's not able to use the prop?
useEffect accepts array of dependencies as second parameter. changes in values for dependencies will trigger function in side useeffect again and again.
In your case you need id as dependant value in useEffect. So
useEffect needs to look like this
React.useEffect(() => {
console.log(id);
async function getDetails() {
var details= await fetchDetails(id);
console.log(details.Data);
setDetails(details.Data);
}
getDetails();
}, [id]);
You may need to handle null/undefined value in id. I suggest you to do something like this.
React.useEffect(() => {
if (!id) {
return
}
... rest of hook
}, [id]);
I suggest you to use eslint to warn about useEffect missing dependencies.
References :
useEffect documenation
Related
I having problem trying to get data from backend using axios. The function returns a Promise and whenever I call the function my component keeps rendering non-stop. Here is the code.
import { useState } from "react";
import Axios from "axios";
const DashBoard = () => {
const [student, setStudent] = useState<{ [key: string]: string }[]>([]);
const studentId = JSON.parse(localStorage.getItem("studentId") as string);
const examResult: { [key: string]: string }[] = JSON.parse(
localStorage.getItem("englishAnswers") as string
);
const getStudent = async () => {
const { data } = await Axios.get(
`http://localhost:3500/students/${studentId}`
);
setStudent(data);
};
getStudent(); //this line here keeps triggering re-render non stop;
The function getStudent is being invoked on every render:
getStudent();
Since the operation within that function updates state, it triggers a re-render. So every render triggers a re-render, indefinitely.
If the intent is only to execute this on the first render, wrap it in a useEffect with an empty dependency array:
useEffect(() => {
getStudent();
}, []);
If studentId might change and you want to re-invoke this if it does, add that to the dependency array:
useEffect(() => {
getStudent();
}, [studentId]);
You might take it a step further and put the function itself into a useCallback:
const getStudent = useCallback(async () => {
const { data } = await Axios.get(
`http://localhost:3500/students/${studentId}`
);
setStudent(data);
}, [studentId]);
This will only create a new instance of the function if/when studentId changes. Then the function itself can be the dependency for executing it:
useEffect(() => {
getStudent();
}, [getStudent]);
You need to wrap your getStudent() call in an useEffect hook. Because currently you are calling the getStudent function on each render, and as it triggers a setState method this leads into an infinite rendering.
useEffect(() => {
const getStudent = async () => {
const { data } = await Axios.get(
`http://localhost:3500/students/${studentId}`
);
setStudent(data);
};
getStudent();
}, []);
I created a custom react hook to fetch data. Unfortunately when the useGetData gets called from a component, the component will render for each useState that is performed inside the hook. How can I prevent the additional renderings?
export default function useGetData(
setData: (fetchData) => void
): [(id: string) => void, boolean, boolean] {
const [loadingData, setLoading] = useState(false)
const [successData, setSuccess] = useState(false)
const getData = (id: string) => {
if (!id || !Number(id)) {
setData(null)
return
}
setSuccess(false)
setLoading(true)
Api.getData(Number(id))
.then((response) => {
setSuccess(true)
setData(response)
})
.finally(() => {
setLoading(false)
})
}
return [getData, loadingData, successData]
}
React < 18.x does not do automatical batching for promise callbacks, timeouts, intervals, .... It does it only for event handlers registered via JSX props and in lifecycle methods / effects.
Solution 1: You can use "unstable" API ReactDOM.unstable_batchedUpdates which, despite being marked as unstable, has been working for years just fine:
Api.getData(Number(id))
.then((response) => ReactDOM.unstable_batchedUpdates(() => {
setSuccess(true)
setData(response)
}))
.finally(() => {
setLoading(false)
})
The function will batch all state updates that are executed inside of passed function (synchronous); in the example setSuccess and setData; so that that they will be merged into single re-render, just like React does it in onClick and other event handlers.
Other possibilities: I can think of 2 other options that I don't really like but might work for you:
You can merge all your state into single state variable so that you have single setter.
Instead of calling setSuccess(true); setData(response); in promise callback, you can do it in an effect by introducing another state variable.
const [response, setResponse] = useState();
useEffect(() => {
if (!response) return;
setSuccess(true);
setData(response);
}, [response]);
...
Api.getData(Number(id))
.then(setResponse)
.finally(() => {
setLoading(false)
})
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.
I'm having trouble understanding how to write a test for a hook without the following warning when using renderHook from "#testing-library/react-hooks".
"Warning: An update to TestHook inside a test was not wrapped in act(...)."
Basically the hook sets initial value in state using useState and then within a useEffect hook I do something asynchronously which ends up updating the state value.
import React from "react";
// fake request
const fetchData = () => Promise.resolve("data");
export const useGetData = () => {
const initialData = { state: "loading" };
const [data, setData] = React.useState(initialData);
React.useEffect(() => {
fetchData()
.then(() => setData({ state: "loaded" }));
}, []);
return data;
};
The hook simply returns the state value at all times.. so I've written a test to assert that it returns the initial value at first and eventually returns the new state value.
import { renderHook } from "#testing-library/react-hooks";
import { useGetData } from "./useGetData";
describe("useGetData", async () => {
it('Should initially return an object with state as "loading"', () => {
const { result } = renderHook(() => useGetData());
expect(result.current).toEqual({ state: "loading" });
});
it('Should eventually return an object with state as "loaded"', async () => {
const { result, waitForNextUpdate } = renderHook(() => useGetData());
await waitForNextUpdate();
expect(result.current).toEqual({ state: "loaded" });
});
});
I've created a sandbox that replicates this:
https://codesandbox.io/s/dazzling-faraday-ht4cd?file=/src/useGetData.test.ts
I've looked into what this warning means and what act is.. but for this particular scenario I'm not sure whats missing.
You can fix it by doing this:
await act(async () => {
await waitForNextUpdate();
});
You need to wrap any function that's going to update the state by the act function
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());
}, []);