React state still undefined after fetching inside useEffect - javascript

In my react app, I am making a request to an API inside useEffect. Once the promise is returned, I am setting state. I then need to use that state to subscribe to a websocket, but state is coming back as undefined so for now I am just logging the state. I only want to do this on initial page load. How can I make an API request, save that information to state, then use that state to do something all on initial page load only?
const fetchInstruments = () => {
return axios
.post('http://localhost:5050/0/public/AssetPairs')
.then(({ data }) => {
return data.result
})
.catch((error) => console.log(error))
}
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
})
.then(()=> console.log(instruments)) //this is undefined
}, [])
}

if I were in your place, I would not use the state to subscribe to a websocket, I will use the result of the HTTP request
checkout this
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments);
return fetchedInstruments; // <======
})
.then((fetchedInstruments) => {
console.log(fetchedInstruments);
//TODO : handle websocket subscription
})
}, [])
}

You can just make your second API call when you fetched your instruments:
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
secondApiCall(fetchedInstruments); // You can do your second API call here
})
}, [])
}

Related

Load props before using to set state in a useEffect hook

I am trying to pass a user address into this Fetch Function, set the value of a state variable equal to the address, and then use that address to make an api call. But as expected, everything runs at the same time and the api call fails because it does not receive the user address.
I am relatively new to useEffect, the below is how I assume a function like this should be written, but evidently I am missing something. It does not return any errors, just a undefined value in the log statement I have below.
const Fetch = (props) => {
const api_key = process.env.REACT_APP_API_KEY;
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
async function Get(){
setAddr(props.useraddress)
}
Get();
}, []);
async function GetNFT() {
useEffect(() => {
axios
.get(
`https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
)
.then(res=> {
setData(res.data.nfts);
})
.catch(err=> {
console.log(err);
})
},[]);
}
GetNFT();
console.log(data);
return (
<div>
<script>{console.log('Fetch'+addr)}</script>
{/*
<>
{data.map((dat,id)=>{
return <div key={id}>
<FetchData NFTData={dat} />
</div>
})}
</>
*/}
</div>
)
}
You need a single useEffect that would depend on useraddress that you can destructure from the props, and make an api call that uses the useraddress. You don't need to store useraddress in the state.
const api_key = process.env.REACT_APP_API_KEY
const createUrl = addr => `https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
const Fetch = ({ useraddress }) => {
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
axios.get(createUrlcreateUrl(useraddress))
.then(res=> {
setData(res.data.nfts)
})
.catch(err=> {
console.log(err)
})
}, [useraddress])
console.log(data)
return (
// jsx
)
}
Note that the useEffect would be triggered on component's mount, and whenever useraddress changes. If useraddress might be empty or undefined when the component mounts, add a condition inside that avoids the call:
useEffect(() => {
if(!useraddress) return // skip the api call if the address is empty/undefined/null
axios.get(createUrlcreateUrl(useraddress))
.then(res => {
setData(res.data.nfts)
})
.catch(err => {
console.log(err)
})
}, [useraddress])

React Hook for a POST call onClick

I have a button, onClick of that button I want to make a POST call with some data user has filled in an input field, stored in state, and then redirect the user to another page.
My current code looks like this, but I get an error:
React Hook "usePost" is called in function "onAccept" which is neither a React function component or a custom React Hook function
And the code doesn't work. I have created my own hook for POST calls.
What might a way to make the desired functionality work?
What I'm after is the ability to make a POST call and redirect.
Simplified example:
// my function
const onAccept = () => {
const { data, loading, error } = usePost(
"MY_URL",
{ DATA: MY_DATA }
);
if (data && !error) {
navigate(`/`);
}
};
// return
<button onClick={() => onAccept()}
Yes, You are calling usePost hook inside of onAccept function. You should follow react hook rule.
To solve your problem, you can do like that:
your custom hook file:
export const usePost = () => {
const [status, setStatus] = useState()
const handlePost = useCallback(async (url, data) => {
// your api request logic in here, bellow only show example
try {
const {data, status} = await apiCall(url, data)
if (data && status === 200) navigate(`/`)
} catch (error) {
console.log(error)
}
}, [])
return { handlePost }
// to return status to component, you can use bellow.
// return { status, handlePost }
}
then your component:
const YourComponent: React.FC = () => {
const { handlePost } = usePost()
// To get status: const { status, handlePost } = usePost()
// your other hooks in here
// Check status
useEffect(() => {
if (status === 200) {
// whatever you want to do
}
}, [status])
return (
<>
// Your component UI here
...
<button onClick={() => handlePost(url, data)}>
</>
)
}
You should call your custom hooks(for example: usePost) at the top level of component, not nested function body as like as you were doing in your code (onAccept function body).
I can suggest you do the following.
At first, you should create fetch function which will be returned from usePost hook.
example.
export const usePost = () => {
const [loading, setLoading] = useState(false)
const [data, setData] = useState([])
const fetch = () => {
setStatus(loading)
apiRequest({
url: 'my_url',
method: 'GET',
}).then(response => {
setStatus(false)
setData(response.data.data)
}).catch(e => {
setStatus(false)
})
}
return {
status,
data,
fetch
}
After all, you can call this hook in your component. It will return fetch function. You should call fetch inside onAccept.
Example.
const { data, loading, fetch } = usePost()
const onAccept = () => {
fetch()
}
// return
<button onClick={() => onAccept()}
PS. if you need you can return errors from usePost hook, as well.
First, call you hook from React Function. Read the docs: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions.
Second, you should have some sort of load method in your usePost hook, e.g.: const { load } = usePost(...), in order to make POST request on click.
So your handler will look like:
const onAccept = () => {
load();
// the following block move somewhere before you render the component or better use useEffect hook for that
// if (data && !error) {
// navigate(`/`);
// }
};
I hope this will help.

Using history.push() re-renders current component

I have a component that gets a value from current location state and passes it down to child components.
interface RequestProcessingLocationState {
request: RequestType;
}
function RequestProcessing() {
const location = useLocation<RequestProcessingLocationState>();
return (
<div>
<RequestDetails
processId={location.state.request.processId}
requestTitle={location.state.request.title}
/>
<RequestForm request={location.state.request} />
</div>
);
}
One of the child components has a form that calls a REST endpoint and processes the request.
function RequestForm ({ request}: RequestFormPropsType): ReactElement {
...
const history = useHistory();
useEffect(() => {
axiosInstance.get(/request/${request.id}/form)
.then((result) => {
setForm(result.data);
})
.catch((error) => {
console.log(error);
}); }, [request]);
const handleSubmit = ({ formData }: any) => {
axiosInstance.put(/request/process/${request.id}, formData)
.then(() => {
message.success(t("request-success"));
history.push("/requests);
})
.catch((error) => {
console.log(error.response);
});
};
return ( ...)
}
The problem occurs when the handleSubmit method is called, specifically when the history.push() is executed. For some reason the useEffect() method of RequestForm component is executed once again, and the axios call throws an error because the request has already been processed.
My guess is that when the location is changed, the component is immediately updated because the request in dependency array comes from location state.
Is there any way to avoid this behaviour?
I think because of your request dependency, you need not pass the dependency in useEffect because it's prop.
use Like this:
useEffect(() => {
request && axiosInstance.get(/request/${request.id}/form)
.then((result) => {
setForm(result.data);
})
.catch((error) => {
console.log(error);
// remove warning
// eslint-disable-next-line react-hooks/exhaustive-deps
}); }, []);
it just call once when component is mounted
This is because of the submission of form by default. To prevent this in your submit function handleSubmit you have to add event.preventDefault();.
try adding a second parameter to the useEffect hook an empty array like that .
useEffect(() => {
// Run! Like go get some data from an API.
}, []);

"ComponentDidMount" hook triggers memory-leaks if i'm requesting data from the server

I'm slowly trying to remove "Redux" from my workflow, lets say part of components receives required data from the server, when they are be rendered on the screen (in ComponentDidMounth hook), rendering of that components controlled by html5 history router,so if the user starts jumping quickly between routes, in the console comes memory lick warning
can you please tell me some smart way how to fix it. thanks ))
also please show examples with latest react api (HOOKS)
const Posts = () => {
const [posts,setPosts] = useState([]);
useEffect(() => {
getPosts()
.then(posts => setPosts(posts))
},[])
return (
<div>
{/* rendering of posts */}
</div>
)
}
const Personal = () => {
const [profileInfo,setProfileInfo] = useState({});
useEffect(() => {
getProfileInfo()
.then(profileInfo => setProfileInfo(profileInfo))
},[])
return (
<div>
{/* rendering of personal info.... */}
</div>
)
}
const Content = () => {
return (
<div className="Content">
<Switch>
<Route path="/posts" component={Posts} />
<Route path="/personal" component={Personal} />
</Switch>
</div>
)
}
another Component called sidebar controlls navigation between this components
That happens when you component is unmounted and you are trying to set the state. A simple way is to modify your useEffect to the following code to get rid of this error:
useEffect(() => {
let mounted = true;
getPosts().then(posts => {
if(mounted) setPosts(posts)
)
return () => { mounted = false };
},[])
So there are a couple of ways you could use for your situation and i have listed them starting with the simplest option.
The first option allows the browser to complete the request but only updates the state with response from the latest request;
useEffect(() => {
let cancelled = false;
function fetchData() {
fetch(url).then(response => response.json())
.then(result => {
if(!cancelled){
setData(result)
}
});
}
fetchData();
return () => {
cancelled = true;
};
}, [your deps])
Ignoring responses from former api calls
You might also want to ignore responses from former api calls.
// A ref to store the last issued pending request
const lastPromise = useRef();
useEffect(() => {
function fetchData(){
const currentPromise = fetch(url)
.then(response => response);
// store the promise to the ref
lastPromise.current = currentPromise;
// handle the result with filtering
currentPromise.then(result => {
if (currentPromise === lastPromise.current) {
setData(result);
}
});
}
// fire the api request
fetchData();
}, [your deps]);
Cancelling and ignoring
Sometimes it is better to cancel former api requests in-flight: the browser can avoid parsing the response and prevent some useless CPU/Network usage. fetch and axios support cancellation using AbortSignal:
useEffect(() => {
// Create the current request's abort controller
const abortController = new AbortController();
function fetchData(){
fetch(url, { signal: abortController.signal })
.then(data => data)
// Set the result, if not aborted
.then(
result => {
// IMPORTANT: we still need to filter the results here,
// in case abortion happens during the delay.
// In real apps, abortion could happen when you are parsing the json,
// with code like "fetch().then(res => res.json())"
// but also any other async then() you execute after the fetch
if (abortController.signal.aborted) {
return;
}
setState(newState);
},
);
}
fetchData();
// Trigger the abortion in useEffect's cleanup function
return () => {
abortController.abort();
};
}, [your deps]);
So you can just chose what suits your use case. Hope that helped.

useEffect spamming requests

State
const [user, setUser] = useState({});
checkIfUserIsEnabled()
async function checkIfUserIsEnabled() {
const res = await fetch("http://localhost:8080/users/finduserbytoken?id=" +
getTokenIdFromURL);
res.json()
.then(res => setUser(res))
.catch(err => setErrors(err));
}
useEffect When i call my checkIfUserIsEnabled() in the useEffect below it gets rendered once and displays the false version in the return method.
useEffect(() => {
verifyEmail(getTokenIdFromURL);
checkIfUserIsEnabled();
return () => {
/* cleanup */
};
}, [/* input */])`
useEffect (2th) If i do it like this instead, it keeps spamming the requests towards my API and displays true.
useEffect(() => {
checkIfUserIsEnabled();
});
Return
return (
<div className="emailVerificationWrapper">
{user.enabled
? <h1>Thank you for registrating, {user.firstName}. Account is verified!</h1>
: <h1>Attempting to verify account...</h1>}
</div>
)
To my question(s): Why does the second useEffect spam the request? and is there a way i can make the request being rendered every ~2-3 second instead of the spam? and could i make it stop doing the request once it actually returns true?
The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again.
It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array(or something which doesn't change) as second argument to the effect hook to avoid activating it on component updates(or only when that parameter changes) but only for the mounting of the component.
let URL = `http://localhost:8080/users/finduserbytoken?id=`;
async function checkIfUserIsEnabled() {
const res = await fetch(`$(URL)` +
getTokenIdFromURL);
res.json()
.then(res => {setUser(res); return Promise.resolve()})
.catch(err => {setErrors(err); return Promise.reject()});
}
useEffect(() => {
const abortController = new AbortController();
const fetchData = async() => await checkIfUserIsEnabled();
fetchData();
return () => {
abortController.abort();
};
}, [URL]);
useEffect(() => {
checkIfUserIsEnabled();
}); <-- no dependency
As your useEffect doesn't have any dependency it will run on every render, so every time you change some state and your component re-renders it will send requests.

Categories

Resources