We have a short question for our application (NextJS 11.0.0 + next-translate 1.0.7)
The library contains a function to make an API call (/lib/mylib.js) :
export const getDataExample = async (lang) => {
return fetch(_apiurl_/example/{lang});
};
And my component in react (/components/myComponent.js) call this function with a useEffect:
import { useEffect, useState } from 'react';
import useTranslation from 'next-translate/useTranslation';
import { getDataExample } from '/lib/mylib';
export default function MyComponent() {
const [data, setData] = useState(false);
const { lang } = useTranslation();
useEffect(() => {
const fetchData = async () => {
const response = await getDataExample(lang);
setData(response);
};
fetchData();
}, []);
[...]
}
I don't want to call getDataExample() directly with the lang parameter.
Is it possible to get the current language in the function (/lib/mylib.js) ?
Thank you for your reply !
But now imagine that my library (/lib/mylib.js) is also used to fetch data into a getServerSideProps :
export async function getServerSideProps({ locale }) {
const response = await getDataExample(locale);
[...]
}
React Hooks are not available here, so what do you do ?
You can create your custom hook. This is an example:
const useFetchWithLang = (func) => {
const { lang } = useTranslation()
return useCallback((args) => func({ ...args, lang }), [lang])
}
const fetchDataExample = ({ otherParam, lang }) => {
return { test: 'test1' }
}
const fetchDataExampleWithLang = useFetchWithLang(fetchDataExample)
After for example, you could use it in a useEffect.
useEffect(() => {
const fetchData = async () => {
const response = await fetchDataExampleWithLang({ otherParam: 'test' });
setData(response);
};
fetchData();
}, []);
Related
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)
}
}
I have a custom hook named "useFetch" which makes an AJAX request and stores the result in the state. I simply want to format the data received from the ajax using a function in my component but not sure how to do this since the function needs to be called only after the data is received.
An example is below:
import React, { Component, useState } from "react";
import useFetch from "../../../Hooks/useFetch";
const Main = () => {
const { data, isPending, error } = useFetch(
"http://127.0.0.1:8000/api/historic/1"
);
function formatData(data){
//Do some processing of the data after it's been received
}
//This doesn't work of course because it runs before the data has been received
const formatted_data=formatData(data);
return (
//Some display using the formatted data
);
};
export default Main;
This is the custom hook, useFetch, which is used in the above component. I'd prefer to not have to do the formatting in here because the formatting is specifically related to the above component and this custom hook is designed to have more universal utility.
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setisPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (res.ok) {
return res.json();
} else {
throw Error("could not fetch data for that resource");
}
})
.then((data) => {
setData(data);
setisPending(false);
setError(null);
})
.catch((er) => {
if (er.name === "AbortError") {
console.log("fetch aborted");
} else {
setError(er.message);
setisPending(false);
}
});
return () => abortCont.abort();
}, [url]);
return { data, isPending, error };
};
export default useFetch;
You should wrap it with useEffect hook with data as it's deps.
const [formattedData, setFormattedData] = useState();
useEffect(() => {
if (!data) return;
const _formattedData = formatData(data);
setFormattedData(_formattedData);
}, [data]);
I have this component:
import React, { Component } from 'react';
import useFetch from "../useFetch";
export class Patient extends Component {
static displayName = Patient.name;
constructor(props) {
super(props);
}
componentDidMount() {
alert("fgggg");
const { isLoading, serverError, apiData } = useFetch(
"/Patient/GetPatients"
);
}
render() {
return (
<div>
</div>
);
}
}
I want to call the useFetch, here is my useFetch:
import React, { useEffect, useState } from "react";
function useFetch(url){
const [isLoading, setIsLoading] = useState(false);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
alert("dddd");
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
try {
fetch(url)
.then(response => response.json())
.then(data => setApiData(data));
//const resp = await axios.get(url);
//const data = await resp?.data;
setIsLoading(false);
} catch (error) {
alert(error);
setServerError(error);
setIsLoading(false);
}
};
fetchData();
}, [url]);
return { isLoading, apiData, serverError };
};
export default useFetch;
Erro:
Attempted import error: 'useFetch' is not exported from '../useFetch'.
Can anybody advise?
UPDATE
Thanks for the resource in the answer, but i found this https://blog.bitsrc.io/fetching-data-in-react-using-hooks-c6fdd71cb24a
and now i have changed my code to this:
import React, { useEffect, useState } from "react";
export default function useFetch(url, opts){
const [response, setResponse] = useState(null)
const [loading, setLoading] = useState(false)
const [hasError, setHasError] = useState(false)
useEffect(() => {
setLoading(true)
fetch(url, opts)
.then((res) => {
setResponse(res.data)
setLoading(false)
})
.catch(() => {
setHasError(true)
setLoading(false)
})
}, [url])
return [response, loading, hasError]
}
and
import React, { Component } from 'react';
import useFetch from "../useFetch";
export class Patient extends Component {
static displayName = Patient.name;
constructor(props) {
super(props);
}
componentDidMount() {
alert("fgggg");
const [ response, loading, hasError ] = useFetch("", "");
}
render() {
return (
<div>
</div>
);
}
}
I still get this error
×
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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Instead of exporting at the end of the file you could export when defining the function/hook.
export default function useFetch(url) {
const [isLoading, setIsLoading] = useState(false);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
alert("dddd");
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
try {
fetch(url)
.then((response) => response.json())
.then((data) => setApiData(data));
//const resp = await axios.get(url);
//const data = await resp?.data;
setIsLoading(false);
} catch (error) {
alert(error);
setServerError(error);
setIsLoading(false);
}
};
fetchData();
}, [url]);
return { isLoading, apiData, serverError };
}
As well double check your import path is correct.
Good reference for when creating custom hooks and using them: https://www.freecodecamp.org/news/how-to-create-react-hooks/
UPDATED: As Hozeis commented. You cannot use hook inside class components. Just noticed you were using a class component
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>
)
}
I would like to get global information from Github user and his repos(and get pinned repos will be awesome). I try to make it with async await but It's is correct? I've got 4 times reRender (4 times console log). It is possible to wait all component to reRender when all data is fetched?
function App() {
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(`https://api.github.com/users/${username}`);
const respRepos = await axios(`https://api.github.com/users/${username}/repos`);
setData(respGlobal.data);
setRepos(respRepos.data);
};
fetchData()
}, []);
if (data) {
console.log(data, repos);
}
return (<h1>Hello</h1>)
}
Multiple state updates are batched but but only if it occurs from within event handlers synchronously and not setTimeouts or async-await wrapped methods.
This behavior is similar to classes and since in your case its performing two state update cycles due to two state update calls happening
So Initially you have an initial render and then you have two state updates which is why component renders three times.
Since the two states in your case are related, you can create an object and update them together like this:
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
Working demo
Figured I'd take a stab at it because the above answer is nice, however, I like cleanliness.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Test = () => {
const [data, setData] = useState([])
useEffect(() => {
(async () => {
const data1 = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
const data2 = await axios.get('https://jsonplaceholder.typicode.com/todos/2')
setData({data1, data2})
})()
}, [])
return JSON.stringify(data)
}
export default Test
Using a self invoking function takes out the extra step of calling the function in useEffect which can sometimes throw Promise errors in IDEs like WebStorm and PHPStorm.
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
he made some mistake here:
setGitData({ data: respGlobal.data, repos: respGlobal.data(respRepos.data //it should be respRepos.data});
For other researchers (Live demo):
import React, { useEffect, useState } from "react";
import { CPromise, CanceledError } from "c-promise2";
import cpAxios from "cp-axios";
function MyComponent(props) {
const [error, setError] = useState("");
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
console.log("mount");
const promise = CPromise.from(function* () {
try {
console.log("fetch");
const [respGlobal, respRepos] = [
yield cpAxios(`https://api.github.com/users/${props.username}`),
yield cpAxios(`https://api.github.com/users/${props.username}/repos`)
];
setData(respGlobal.data);
setRepos(respRepos.data);
} catch (err) {
console.warn(err);
CanceledError.rethrow(err); //passthrough
// handle other errors than CanceledError
setError(err + "");
}
}, []);
return () => {
console.log("unmount");
promise.cancel();
};
}, [props.username]);
return (
<div>
{error ? (
<span>{error}</span>
) : (
<ul>
<li>{JSON.stringify(data)}</li>
<li>{JSON.stringify(repos)}</li>
</ul>
)}
</div>
);
}