I am getting this same error whether I use useEffect inside of my function component or out of it:
React Hook "useEffect" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function
import React, { useEffect } from "react";
const GetVerse = async () => {
useEffect(async () => {
const fetchVerse = await fetch(
"https://bhagavad-gita3.p.rapidapi.com/v2/chapters/1/verses/null/"
);
const verseBody = await fetchVerse.json();
console.log(verseBody);
});
return <div></div>;
};
export default GetVerse;
You component can't be async. But if you want to have an async useEffect, you have to do like this :
const GetVerse = () => { // component can't be async
useEffect(() => { // useEffect callback can't be async either
const fn = async () => { // declare an async function here
const fetchVerse = await fetch(url)
const verseBody = await fetchVerse.json()
console.log(verseBody);
}
fn() // call the async function
}, []) // don't forget to add dependency array here
return <div></div>
}
React Components and the useEfffect callback shouldn't be async. Here is a way to use await in the callback. The idea is to create a async function and then call it.
Also consider adding an empty array [] as the second parameter to useEffect as not putting this will cause infinite API calls.
import React, { useEffect } from "react";
const GetVerse = () => {
useEffect(() => {
async function getData() {
const fetchVerse = await fetch(
"https://bhagavad-gita3.p.rapidapi.com/v2/chapters/1/verses/null/"
);
const verseBody = await fetchVerse.json();
console.log(verseBody);
}
getData();
}, []);
return <div></div>;
};
export default GetVerse;
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 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>
}
I'm checking if a component is unmounted, in order to avoid calling state update functions.
This is the first option, and it works
const ref = useRef(false)
useEffect(() => {
ref.current = true
return () => {
ref.current = false
}
}, [])
....
if (ref.current) {
setAnswers(answers)
setIsLoading(false)
}
....
Second option is using useState, which isMounted is always false, though I changed it to true in component did mount
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
return () => {
setIsMounted(false)
}
}, [])
....
if (isMounted) {
setAnswers(answers)
setIsLoading(false)
}
....
Why is the second option not working compared with the first option?
I wrote this custom hook that can check if the component is mounted or not at the current time, useful if you have a long running operation and the component may be unmounted before it finishes and updates the UI state.
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted() {
const isMountedRef = useRef(true);
const isMounted = useCallback(() => isMountedRef.current, []);
useEffect(() => {
return () => void (isMountedRef.current = false);
}, []);
return isMounted;
}
Usage
function MyComponent() {
const [data, setData] = React.useState()
const isMounted = useIsMounted()
React.useEffect(() => {
fetch().then((data) => {
// at this point the component may already have been removed from the tree
// so we need to check first before updating the component state
if (isMounted()) {
setData(data)
}
})
}, [...])
return (...)
}
Live Demo
Please read this answer very carefully until the end.
It seems your component is rendering more than one time and thus the isMounted state will always become false because it doesn't run on every update. It just run once and on unmounted. So, you'll do pass the state in the second option array:
}, [isMounted])
Now, it watches the state and run the effect on every update. But why the first option works?
It's because you're using useRef and it's a synchronous unlike asynchronous useState. Read the docs about useRef again if you're unclear:
This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.
BTW, you do not need to clean up anything. Cleaning up the process is required for DOM changes, third-party api reflections, etc. But you don't need to habit on cleaning up the states. So, you can just use:
useEffect(() => {
setIsMounted(true)
}, []) // you may watch isMounted state
// if you're changing it's value from somewhere else
While you use the useRef hook, you are good to go with cleaning up process because it's related to dom changes.
This is a typescript version of #Nearhuscarl's answer.
import { useCallback, useEffect, useRef } from "react";
/**
* This hook provides a function that returns whether the component is still mounted.
* This is useful as a check before calling set state operations which will generates
* a warning when it is called when the component is unmounted.
* #returns a function
*/
export function useMounted(): () => boolean {
const mountedRef = useRef(false);
useEffect(function useMountedEffect() {
mountedRef.current = true;
return function useMountedEffectCleanup() {
mountedRef.current = false;
};
}, []);
return useCallback(function isMounted() {
return mountedRef.current;
}, [mountedRef]);
}
This is the jest test
import { render, waitFor } from '#testing-library/react';
import React, { useEffect } from 'react';
import { delay } from '../delay';
import { useMounted } from "./useMounted";
describe("useMounted", () => {
it("should work and not rerender", async () => {
const callback = jest.fn();
function MyComponent() {
const isMounted = useMounted();
useEffect(() => {
callback(isMounted())
}, [])
return (<div data-testid="test">Hello world</div>);
}
const { unmount } = render(<MyComponent />)
expect(callback.mock.calls).toEqual([[true]])
unmount();
expect(callback.mock.calls).toEqual([[true]])
})
it("should work and not rerender and unmount later", async () => {
jest.useFakeTimers('modern');
const callback = jest.fn();
function MyComponent() {
const isMounted = useMounted();
useEffect(() => {
(async () => {
await delay(10000);
callback(isMounted());
})();
}, [])
return (<div data-testid="test">Hello world</div>);
}
const { unmount } = render(<MyComponent />)
await waitFor(() => expect(callback).toBeCalledTimes(0));
jest.advanceTimersByTime(5000);
unmount();
jest.advanceTimersByTime(5000);
await waitFor(() => expect(callback).toBeCalledTimes(1));
expect(callback.mock.calls).toEqual([[false]])
})
})
Sources available in https://github.com/trajano/react-hooks-tests/tree/master/src/useMounted
This cleared up my error message, setting a return in my useEffect cancels out the subscriptions and async tasks.
import React from 'react'
const MyComponent = () => {
const [fooState, setFooState] = React.useState(null)
React.useEffect(()=> {
//Mounted
getFetch()
// Unmounted
return () => {
setFooState(false)
}
})
return (
<div>Stuff</div>
)
}
export {MyComponent as default}
If you want to use a small library for this, then react-tidy has a custom hook just for doing that called useIsMounted:
import React from 'react'
import {useIsMounted} from 'react-tidy'
function MyComponent() {
const [data, setData] = React.useState(null)
const isMounted = useIsMounted()
React.useEffect(() => {
fetchData().then((result) => {
if (isMounted) {
setData(result)
}
})
}, [])
// ...
}
Learn more about this hook
Disclaimer I am the writer of this library.
Near Huscarl solution is good, but there is problem with using these hook with react router, because if you go from example news/1 to news/2 useRef value is set to false because of unmount, but value keep false. So you need init ref value to true on each mount.
import {useRef, useCallback, useEffect} from "react";
export function useIsMounted(): () => boolean {
const isMountedRef = useRef(true);
const isMounted = useCallback(() => isMountedRef.current, []);
useEffect(() => {
isMountedRef.current = true;
return () => void (isMountedRef.current = false);
}, []);
return isMounted;
}
It's hard to know without the larger context, but I don't think you even need to know whether something has been mounted. useEffect(() => {...}, []) is executed automatically upon mounting, and you can put whatever needs to wait until mounting inside that effect.
I wrote a function to make an API call. Typically, I'd just wrap it in a useEffect and throw it in the same file that needs it, but I'm trying to write my code a little cleaner. So I did the following.
In my component.js file, I have the following:
import { apiCall } from '../../../framework/api.js';
import { useEffect, useState } from 'react';
export const Table = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
console.log(apiCall());
}, []);
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
in my api.js file, I have the following:
import axios from 'axios';
import { useState } from 'react';
export const apiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
This always returns an error (Invalid hook call. Hook calls can only be called inside the body of a function component.)
If I rewrite my component.js and include the axios call directly inside useEffect instead of calling the function apiCall() from the external file, it obviously works with no problems.
I think I know it has to do with the fact that I'm using hooks in my apiCall function, and wrapping that call in a useEffect in my component.js. However, if I don't wrap it in a useEffect, it'll just run continuously and I don't want that either.
You have to follow the custom hook naming convention for this to be able to work. You can check out the documentation for that here: https://reactjs.org/docs/hooks-custom.html
Anyway, I believe in this case this should work:
import axios from 'axios';
import { useState } from 'react';
export const useApiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
And then in component.js, you would call useApiCall()
Usually, we do it like this
export const useApiCall = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
}, []);
return resp;
}
and then use it like so
export const Table = () => {
const resp = useApiCall();
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
The prefix "use" in the function name is important, this is how we define a custom hook.
React Hook "useState" is called in function "apiCall" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
You can use following methods.
import { useState } from 'react';
export const ApiCall = () => {
const [state, setState] = useState();
};
or
import { useState } from 'react';
export const useApiCall = () => {
const [state, setState] = useState();
};
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}