Using history.push() re-renders current component - javascript

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.
}, []);

Related

reactjs passing value to useEffect from useSelector

I am building a reactjs app
I want to get my user id from redux state
using useSelector and pass it to useEffect
My code
UserReducer
export function userReducer(state = { token: "", user: {} }, action) {
switch (action.type) {
case "LOGIN":
return action.payload;
case "LOGOUT":
return action.payload;
default:
return state;
}
}
ViewTransaction.jsx
const ViewTransaction = () => {
const user_id = useSelector((state) => state.user.user.id);
const params = useParams();
useEffect(() => {
setLoading(true);
PendingServices.getPendingTransaction(
`localhost:8000/user/transaction/pending/get-transaction/${params.id}?user_id=${user_id}`
)
.then((response) => {})
.catch((error) => {})
.finally(() => {
setLoading(false);
});
}, []);
return (
<div>transaction</div>
)
}
export default ViewTransaction;
user_id is showing undefined.
You need to pass user_id in useEffects dependency array and check if its defined or nod... because on first render its undefined because redux didnt provided it yet
useEffect(() => {
if(user_id){
setLoading(true);
PendingServices.getPendingTransaction(
`localhost:8000/user/transaction/pending/get-transaction/${params.id}?user_id=${user_id}`
)
.then((response) => {})
.catch((error) => {})
.finally(() => {
setLoading(false);
});
}
}, [user_id]);
Like Drew wrote in the comment above your dependency array is missing variables. React tries to be smart about not calling hooks in vain, and by passing it no dependencies you let it assume that the call to the method in useEffect will always be the same, and that it can cache and reuse the result of the invocation. Since user_id is undefined initially, when no one have logged in, the result of useEffect with an undefined user is cached.
By adding user_id and params.id in the dep array you're telling React that "each time these variables change, the result of the method should also change", so it will invalidate the cached useEffect result and run the method again.
I'd recommend using this eslint plugin to automatically help catch these cases: https://www.npmjs.com/package/eslint-plugin-react-hooks

React state still undefined after fetching inside useEffect

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
})
}, [])
}

"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.

Functional component is not rerendering after state is changed

I would like to ask why my component is not rerendering after my state is changed and I need to refresh it (switch between routes) to see changes. Well, the interesting fact is that the first time when I click the delete button page (component) does not rerender but after I switch routes and come back the item is deleted and when I try to delete other items, it gets deleted instantly not like the first time.
This is my code:
import React, {useEffect, useState} from 'react';
import ApiFactory from '../mock';
import Editor from '../Editor';
import ProductCard from '../components/product-card/product-card';
import ProductEdit from '../components/product-edit/product-edit';
export default function Admin() {
const [items, setItems]= useState([]);
useEffect(() => {
getProducts();
}, [items]);
function getProducts() {
ApiFactory.getInstance().get('/api/products')
.then((res) => {
if(res.status == 200) {
setItems(res.data);
}
})
.catch((error) => { console.log(error)})
}
function handleDelete (productId) {
ApiFactory.getInstance().delete(`/api/products/${productId}`)
.then(()=> getProducts()
);
}
return (
<>
{
items.map((item, index) => {
console.log(item.id)
return <>
<div key={index}>
<ProductCard product={item}></ProductCard>
<button onClick={() => handleDelete(item.id)}>Delete</button>
</div>
</>
})
}
</>
);
}
I am quite new in React can anybody explain why it happens and how can I fix it?
I believe it's because of how you have useEffect set up.
change the useEffect to only make the GET API call once (on initial load):
useEffect(() => {
getProducts();
}, []); // remove the dependency here. You may have made an infinite loop here.
const getProducts = () => {
ApiFactory.getInstance().get('/api/products')
.then((res) => {
if(res.status == 200) {
setItems(res.data);
}
})
.catch((error) => { console.log(error)})
}
If you confirmed that the API call is handling your errors / successes (are you getting non 200 status codes ? those may not be handled)
Add error catching to handleDelete to make sure this call works.
const handleDelete = (productId) => {
ApiFactory.getInstance().delete(`/api/products/${productId}`)
.then(getProducts())
).catch((error) => { console.log(error)})
}
You may additionally do as another user suggested and move even more logic away from the API calls (not required though) to have state locally and not re-fetch data from the API.

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