I wrote a simple code that requests data from a local json server when the page loads. I have this code repeated in several places and I want to put it in the custom hook.
Tell me how to write and apply a custom hook correctly?
const [data, setData] = useState()
useEffect(() => {
const getPosts = async () => {
try {
setData(await getData('http://localhost:3001/posts'))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getPosts()
}, [])
I tried to write like this, but it doesn't work:
import { useEffect, useState } from "react"
import { getData } from './../helpers'
const useData = url => {
const [currentData, setCurrentData] = useState()
useEffect(() => {
const getCurrentData = async () => {
try {
setCurrentData(await getData(url))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getCurrentData()
}, [url])
return currentData
}
export default useData
Please check this sandbox, this seems to load data once every page loads. I just don't pass the URL inside useEffect because I only want to call it once on page load.
https://codesandbox.io/s/dank-sky-6fs4rq?file=/src/useData.jsx
Related
Origin code
const getData = useCallback(async () => {
dispatchAnimation(actionSetAnimationState(true));
try {
await Promise.all([getGroupData(), getPolicyData()]);
} catch (err) {
notifyError('error...');
}
dispatchAnimation(actionSetAnimationState(false));
}, [dispatchAnimation, getGroupData, getPolicyData, localize]);
useEffect(() => {
getData();
}, [getData]);
I want to moving the try catch to another file
import { useCallback } from 'react';
import { actionSetAnimationState } from '../reducer/animation/animationAction';
import { useAnimation } from '../reducer/useStore';
import doSleep from './doSleep';
import { notifyError } from './Toast';
async function CustomCallbackWithAnimation(argv: Array<Promise<void>>): Promise<void> {
const { dispatchAnimation } = useAnimation();
dispatchAnimation(actionSetAnimationState(true));
useCallback(async () => {
try {
await Promise.all(argv);
} catch (err) {
notifyError('error');
}
}, [argv]);
dispatchAnimation(actionSetAnimationState(false));
}
export default CustomCallbackWithAnimation;
In .tsx file that need to getData just calling
useEffect(() => {
CustomCallbackWithAnimation([getGroupData(), getPolicyData()]);
}, [edit]);
But I am receiving this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component
Do you have any solution that better to split this function(meaning I want to reused it any where to avoid duplicate code)?
I want to reused it any where to avoid duplicate code
Instead directly call CustomCallbackWithAnimation that runs hooks inside, make it return callback that you can invoke where you want later.
Something like that
export function useCustomCallback() {
const { dispatchAnimation } = useAnimation();
return useCallback(async (argv: Array<Promise<void>>) => {
dispatchAnimation(actionSetAnimationState(true));
try {
await Promise.all(argv);
} catch (err) {
notifyError('error');
}
dispatchAnimation(actionSetAnimationState(false));
}, [argv])
}
const App = () => {
const callback = useCustomCallback()
useEffect(() => {
callback([firstPromise, secondPromise])
}, [])
return <div></div>
}
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 use React for fetching voting objects of a GraphQL API, provided by AWS Amplify. Therefore I created following function that works with async/await:
import { useState, useEffect } from 'react';
import { API } from 'aws-amplify';
import { getVote } from 'src/graphql/queries';
const asGetVoting = (id) => {
const [vote, setVote] = useState([]);
const fetchVoting = async () => {
try {
const voteData = await API.graphql({
query: getVote, variables: { id }
});
setVote(voteData.data.getVote);
} catch (error) {
console.log('Fetching error: ', error);
}
};
useEffect(() => {
fetchVoting();
}, []);
return vote;
};
export default asGetVoting;
In my component I call the function above and I want to wait until the whole object is fetched - without success:
import asGetVoting from 'src/mixins/asGetVoting';
const Voting = () => {
const fetchVoting = asGetVoting(id);
fetchVoting.then((voting) => {
console.log('Voting completely loaded and ready to do other stuff');
}).catch((error) => {
console.log(error);
});
return (
<div>
some code
</div>
);
};
export default Voting;
Any idea what I am doing wrong? Respectively how can I wait until the object is loaded for querying its content? Or is my fetching function (asGetVoting) built in a wrong way? Do I mix async/await stuff with promises?
Thank you for your appreciated feedback in advance.
I think this is a little more complex than it needs to be. If API is returning a promise, you could set your state using .then to ensure the promise has resolved (I didn't included it but should probably add a catch statement as well). Something like:
const asGetVoting = (id) => {
const [vote, setVote] = useState([]);
useEffect(() => {
API.graphql({
query: getVote, variables: { id }
}).then(result => setVote(result.data.getVote))
}, []);
return (
// Whatever logic you are using to render vote state
<div>{vote}</div>
)
};
I am working on a small CRUD fullstack app with react and mongodb and I have this problem where I use useEffect to make an axios get request to the server to get all of my todos. The problem is that useEffect does it's job but it also rerenders to infinity. This is my component:
export default function () {
...
const [todos, setTodos] = useState([]);
const currentUser = JSON.parse(localStorage.getItem('user'))._id;
useEffect(() => {
async function populateTodos () {
try {
const res = await axios.get(`http://localhost:8000/api/all-todos/${currentUser}`);
setTodos(res.data);
} catch (err) {
if (err.response) {
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
} else if (err.request) {
console.log(err.request);
} else {
console.log('Error: ', err.message);
}
}
}
populateTodos();
}, [todos]);
console.log(todos);
return (
...
);
}
So what I was expecting to happen is that that console.log to get printed only when the todos changes, like when I add a new todo and so on, but instead it gets printed forever.
You said that you need to fetch todos at first, and whenever todos change. I can suggest you a different approach, using one more variable, something like this:
const TodosComponent = (props) => {
const [todos, setTodos] = useState([]);
const [updatedTodos, setUpdatesTodos] = useState(true);
const fetchFunction = () => {
// In here you implement your fetch, in which you call setTodos().
}
// Called on mount to fetch your todos.
useEffect(() => {
fetchFunction();
}, []);
// Used to updated todos when they have been updated.
useEffect(() => {
if (updatedTodos) {
fetchFunction();
setUpdatesTodos(false);
}
}, [updatedTodos]);
// Finally, wherever you update your todos, you also write `updateTodos(true)`.
}
I have a React component using hooks like this:
const myComponent = (props) => {
useEffect(() => {
FetchData()
.then(data => {
setState({data: data});
}
// some other code
}, []);
//some other code and render method...
}
fetchData is in charge to use axios and get the data from an API:
const FetchData = async () => {
try {
res = await myApiClient.get('/myEndpoint);
} catch (err) {
console.log('error in FetchData');
res = err.response
}
}
and finally myApiClient is defined externally. I had to use this setup in order to be able to use different APIs...
import axios from "axios";
axios.defaults.headers.post["Content-Type"] = "application/json";
const myApiClient = axios.create({
baseURL: process.env.REACT_APP_API1_BASEURL
});
const anotherApiClient = axios.create({
baseURL: process.env.REACT_APP_API2_BASEURL
});
export {
myApiClient,
anotherApiClient
};
with this setup I am getting the 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 a useEffect cleanup function.
I googled a bit and I saw some suggestions on how to clean up requests from useEffect, like this, but my axios is defined externally. So how can I send the cancellation using this setup?
Also, the application is using redux, not sure if it is in some way involved.
Any other suggestion to avoid the error is welcome.
You can use defer from rxjs for this:
const FetchData = () => {
try {
return myApiClient.get("/myEndpoint");
} catch (err) {
console.log("error in FetchData");
return err.response;
}
};
const myComponent = (props) => {
useEffect(() => {
const subscription = defer(FetchData()).subscribe({
next: ({
data
}) => {
setState({
data: data
});
},
error: () => {
// error handling
},
complete: () => {
// cancel loading state etc
}
});
return () => subscription.unsubscribe();
}, []);
}
Alway check if you are dealing with fetch or any long operations.
let _isMounted = false;
const HooksFunction = props => {
const [data, setData] = useState({}); // data supposed to be object
const fetchData = async ()=> {
const res = await myApiClient.get('/myEndpoint');
if(_isMounted) setData(res.data); // res.data supposed to return an object
}
useEffect(()=> {
_isMounted = true;
return ()=> {
_isMounted = false;
}
},[]);
return (
<div>
{/*....*/}
<div/>
);
}