I have a hook that is fetching data from an api and the result I am putting in the state of my component with setAllCommunitiesFromSponsor.
Now I need to use the exact same hook in another component.
In this case, what is the proper way to implement this besides copy paste? I need to create a custom hook? But what about the setState function?
the hook:
useEffect(() => {
const getAllCommunitiesFromSponsor = async () => {
try {
const result = await api.get(
'https://someurl.com'
)
const resultArray = Object.values(result.data.rows)
setAllCommunitiesFromSponsor(resultArray)
} catch (error) {
toast.error('Failed to fetch data from the server')
}
}
getAllCommunitiesFromSponsor()
}, [])
You can create your own hook, it's extremely simple. It's just a function that uses hooks. In your case:
const useCommunitiesForSponsor = () => {
const [allCommunitiesFromSponsor, setAllCommunitiesFromSponsor] = useState();
useEffect(() => {
const getAllCommunitiesFromSponsor = async () => {
try {
const result = await api.get(
'https://someurl.com'
)
const resultArray = Object.values(result.data.rows)
setAllCommunitiesFromSponsor(resultArray)
} catch (error) {
toast.error('Failed to fetch data from the server')
}
}
getAllCommunitiesFromSponsor()
}, [])
return allCommunitiesFromSponsor;
}
Now in your components simply use the hook like any other hook:
const allCommunitiesFromSponsor = useCommunitiesForSponsor();
Related
Below is my custom hook, I'm trying to handle everything from the hook. I have seen similar questions but none seems to work for my case and I have been made to believe there's a solution for this approach, jus can't figure it out.
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource)
setResources(resources.concat(response.data));
console.log(resources)
return response.data
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data)
return response.data
}
const service = {
create,
get
}
return [
resources, service
]
}
Here is my approach to use the custom hook, but request keeps looping nonstop, please how do I stop it running after every render?
const App = () => {
const content = useField('text');
const name = useField('text');
const number = useField('text');
const [notes, noteService] = useResource('http://localhost:3005/notes');
const [persons, personService] = useResource('http://localhost:3005/persons');
useEffect(() => {
noteService.get();
}, [noteService])
useEffect(() => {
personService.get();
}, [personService])
const handleNoteSubmit = (event) => {
event.preventDefault();
noteService.create({ content: content.value });
}
const handlePersonSubmit = (event) => {
event.preventDefault();
personService.create({ name: name.value, number: number.value});
}
Edit: I just had to disable ESLint for the dependency line, because I just need it to run once after every render. Works well!
useEffect(() => {
noteService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
personService.get();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
As correctly pointed out in comments, each time the component renders and calls your useResource hook, a new service object is created. If this service object is used as a dependency for any other hooks this will trigger their callbacks.
The solution is to memoize the service object so it's being provided as a stable reference. This can be accomplished via the useMemo hook. Because service will be memoized, the create callback will also be memoized and contain stale resources state. To address this update create to use a functional state update when appending new response data to the existing state.
Example:
import { useEffect, useMemo, useState } from 'react';
const useResource = (baseUrl) => {
const [resources, setResources] = useState([]);
const create = async (resource) => {
const response = await axios.post(baseUrl, resource);
// Functional update to update from previous state
setResources(resources => resources.concat(response.data));
return response.data;
}
const get = async () => {
const response = await axios.get(baseUrl);
setResources(response.data);
return response.data;
}
const service = useMemo(() => ({
create,
get
}), []);
return [resources, service];
};
I am getting data in an asynchronous way and consequently showing it inside the render() function using {data}.
Now my question is, how do I let the render() function wait until the variable is defined? Currently either the placeholder variable doesn't change or it never arrives.
This is my code,
let [data] = useState();
let storeData = async (value) => {
try {
await AsyncStorage.setItem('locatie', value)
} catch (e) {
console.log(e)
}
}
let getInfo = async () => {
data = await AsyncStorage.getItem('locatie');
return data;
}
useEffect(() => {
getInfo().then(r => console.log('getInfo called, data var = ' + data))
})
console.log('return() initiate, data var = ' + data)
And showing it here:
<TextInput value={data !== undefined ? data : 'placeholder'} onChangeText={(value) => storeData(value)}/>
Evidently, since it's asynchronous, render() happens before the function.
Any help would be appreciated. Please no flame I use React Native first time :)
Also, please note that it's inside a function, I am not using classes and will not use them.
You should learn more about using react hooks. Anyway, you can refer to below for your issue.
const [data, setData] = useState('');
const storeData = async (value) => {
try {
setData(value);
await AsyncStorage.setItem('locatie', value);
} catch (e) {
console.log(e);
}
};
const getInfo = async () => {
const res = await AsyncStorage.getItem('locatie');
setData(res);
};
useEffect(() => {
getInfo();
}, []);
Instead of let [data] = useState();
write const [data, setData] = useState();
and update the state inside the useEffect hook inside a then()
in your case, after the promise returns some data.
Below is the code to fetch some data
useEffect(async (id) => {
// const data = await redditFetch(id);
async function redditFetch(id){
try {
const {data:{data}} = await axios.get(`https://www.reddit.com/search.json?q=${id}&limit=50`);
const {children} = data;
return children.map(data=> data.data);
} catch (error) {
console.log(error);
}
}
const data = redditFetch(id);
setData(data)
},[setData])
console.log(Data);
And this is the promise returned:
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(50)
What I want is the Array inside the PromiseResult.
I've also tried another away using async await like this which returns a proper array.
const PostPage = () => {
const {id} = useParams();
const [Data, setData] = useState([])
useEffect(async (id) => {
// const data = await redditFetch(id);
async function redditFetch(id){
try {
const {data:{data}} = await axios.get(`https://www.reddit.com/search.json?q=${id}&limit=50`);
const {children} = data;
return children.map(data=> data.data);
} catch (error) {
console.log(error);
}
}
const data = await redditFetch(id);
setData(data)
},[setData])
console.log(Data);
But with this comes a bloody warning that I can't get rid of:
index.js:1 Warning: An effect function must not return anything
besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a
Promise. Instead, write the async function inside your effect and call
it immediately:
useEffect(() => { async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state
So, my dear coders, tell me what to do???
I did a sample demo with the post code:
CODESANDBOX: https://codesandbox.io/s/flamboyant-nobel-p87mt9?file=/src/App.js:257-265
In your code const data = redditFetch(id); the async functions will return a Promise. You have to use await, but React will warn you to not transform the useEffect callback in async function
You can remove the API function of your useEffect.
Don't put the setDate in your useEffect dependency array. In this case, you can leave this empty to run just one time. Also, don't put the date because will re-render in an infinite loop. If you need some conditional just remove the dependency array and put some conditional to run setDate.
I had to mock the id in the query parameter to be possible to work with API
CODE:
import axios from "axios";
import { useEffect, useState } from "react";
import "./styles.css";
async function redditFetch() {
try {
const {
data: { data }
} = await axios.get(`https://www.reddit.com/search.json?q=01&limit=50`);
const { children } = data;
return children.map((data) => data.data);
} catch (error) {
console.log(error);
}
}
export default function App() {
const [data, setData] = useState([]);
const Data = data.map((redditContent) => {
return (
<div key={redditContent.name}>
<h2>{redditContent.title}</h2>
<hr />
</div>
);
});
useEffect(() => {
(async function () {
const redditResponse = await redditFetch();
console.log(redditResponse);
setData(redditResponse);
})();
}, []);
return <div className="App">{Data}</div>;
}
ALTERNATIVE: CLEANER APPROACH (No anonymous function)
If you don't like of anonymous function approach, you can use this one:
async function updateRedditPosts() {
const redditResponse = await redditFetch();
console.log(redditResponse);
setData(redditResponse);
}
useEffect(() => {
updateRedditPosts();
}, []);
Anything return from useEffect supposed to be clean up while component unmount. Better you may invoke setData function there instead of return.
On the other hand, you may use immediate invoke function (IIF) then you do not need to resolve the promise that return from redditFetch function.
I'm working on a React Native app for a radio station which displays song & artist info from a JSON url. How can I get the info to update/fetch every minute or so? I've tried a few variations of using setInterval, but can't seem to get it to work in my functional component. I'm guessing it's because I'm trying to use setInterval with an async function..? Thanks in advance!
const [track, setTrack] = useState({});
async function fetchData() {
const res = await fetch(" my API url ");
res
.json()
.then(res => setTrack(res.data[0].track.title))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
});
<Text>{track}</Text>
You can use setTimeout to request every N period of time after first component mount, like:
import React from 'react';
const Component = () => {
const [track, setTrack] = React.useState('');
React.useEffect(() => {
let repeat;
async function fetchData() {
try {
const res = await fetch(" my API url ");
const json = await res.json();
setTrack(json.data[0].track.title);
repeat = setTimeout(fetchData, 60000); // request again after a minute
} catch (error) {
console.error(error.message)
}
}
fetchData();
return () => {
if (repeat) {
clearTimeout(repeat);
}
}
}, []);
return (
<Text>{track}</Text>
);
};
Within my React component, I have an async request which dispatches an action to my Redux store which is called within the useEffect hook:
const loadFields = async () => {
setIsLoading(true);
try {
await dispatch(fieldsActions.fetchFields(user.client.id));
} catch (error) {
setHasError(true);
}
setIsLoading(false);
}
useEffect(() => { if(isOnline) { loadFields() } }, [dispatch, isOnline]);
The action requests data via a fetch request:
export const fetchFields = clientId => {
return async dispatch => {
try {
const response = await fetch(
Api.baseUrl + clientId + '/fields',
{ headers: { 'Apiauthorization': Api.token } }
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const resData = await response.json();
dispatch({ type: SET_FIELDS, payload: resData.data });
} catch (error) {
throw error;
}
}
};
export const setFields = fields => ({
type : SET_FIELDS,
payload : fields
});
When this is rendered within the React app it results in the following warning:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function
I believe this occurs because the promise doesn't have a "clean-up" function. But I am unsure where to place this? Should I have some logic within LoadFields()? Or must this be done within the useEffect hook?
This tutorial which will help you to resolve your issue.
Quick example: with Promises
function BananaComponent() {
const [bananas, setBananas] = React.useState([])
React.useEffect(() => {
let isSubscribed = true
fetchBananas().then( bananas => {
if (isSubscribed) {
setBananas(bananas)
}
})
return () => isSubscribed = false
}, []);
return (
<ul>
{bananas.map(banana => <li>{banana}</li>)}
</ul>
)
}
Quick example: with async/await (Not the best one but that should work with an anonymous function)
function BananaComponent() {
const [bananas, setBananas] = React.useState([])
React.useEffect(() => {
let isSubscribed = true
async () => {
const bananas = await fetchBananas();
if (isSubscribed) {
setBananas(bananas)
}
})();
return () => isSubscribed = false
}, []);
return (
<ul>
{bananas.map(banana => <li>{banana}</li>)}
</ul>
)
}
First issue
If your useEffect() fetches data acynchronously then it would be a very good idea to have a cleanup function to cancel the non-completed fetch. Otherwise what could happen is like that: fetch takes longer than expected, meantime the component is re-rendered for whatever reason. Maybe because its parent is re-rendered. The cleanup of useEffect runs before re-render and the useEffect itself runs after re-render. To avoid having another fetch inflight it's better to cancel the previous one. Sample code:
const [data, setData] = useState();
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const apiData = await fetch("https://<yourdomain>/<api-path>",
{ signal: controller.signal });
setData(apiData);
} catch (err) {
if (err.name === 'AbortError') {
console.log("Request aborted");
return;
}
}
};
fetchData();
return () => {
controller.abort();
}
});
Second issue
This code
return async dispatch => {
will not work because neither dispatch nor Redux store support async actions. The most flexible and powerful way to handle this issue is to use middleware like redux-saga. The middleware lets you:
dispatch 'usual' sync actions to Redux store.
intercept those sync actions and in response make one or several async calls doing whatever you want.
wait until async call(s) finish and in response dispatch one or several sync actions to Redux store, either the original ones which you intercepted or different ones.