Avoid stale data prop in React child component - reactjs

From my parent component, I am passing down the state variable data as a prop to the child component. The data is fetched via fetch/UseEffect. The URL params can be modified by clicking a button, and depending on the param, a completely different object is fetched, which needs to be handled differently in the child component.
export default function ParentComponent() {
const [data, setData] = useState({})
const [param, setParam] = useState('')
const [isLoading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const data= await (await fetch(`https://myapi.com/?param=${param}`)).json()
setData(data)
setLoading(false)
}
fetchData()
}, [param])
return (<div>
{ isLoading ?
(<span>Loading...</span>)
:
(<div>
<button onClick={() => setParam('something')}>Click me</button>
<ChildComponent data={ data } />
</div>
</div>)
}
}
My problem is that whenever I click the button (set the state) in the parent, which triggers a rerender, the components rerender twice (causing below console.log to print twice), which I'd dare say is expected behavior, once because of setParam, once when fetchData and setData is completed).
export default function ChildComponent(props) {
const { data } = props
return (
<div>
// Prints twice, first stale data, then when fetchData has completed.
{ console.log(data )}
</div>)
}
My question to you guys, as I have been struggling with this for a couple of hours now, having read ChrisW's post here (Avoid old data when using useEffect to fetch data) and the React.js Hooks FAQ (https://reactjs.org/docs/hooks-faq.html), is how on God's green earth (sorry any atheist!) I only ever get to access the newly fetched, non-stale data prop in the child component, and ignore the stale one? Is it through refs? UseEffect? (I know I can make a UseEffect in the child component with data as a dependency, however, what should I do with it, I am after all trying to control what is being returned?)
Thanks in advance for any answers!

I would suggest to consider useMemo API to solve this issue:
const MemoizedChildComponent = useMemo(({ data }) => <ChildComponent data={data} />, [data]);

Related

How can I stop a function mutate from executing in ReactJS?

I am trying to stop this mutate function from executing. ater I call mutate() in List.js when I check the network in my browser I can see that the mutate always executing. I need this to execute once.
This is my List.js which I output the data.
import React, {useEffect} from 'react';
import ListItem from './ListItem'
import FetchData from './FetchData';
function List() {
const {
data,
loading,
mutate,
} = FetchData();
//this needs to stop using interval how?
mutate();
return (
<ul>
{loading && <div>Loading</div>}
{!loading && (
<>
{data.map(item => (<ListItem key={item.id} id={item.id} name={item.name} complete={item.complete} />))}
</>
)}
</ul>
)
}
export default List
And this is the FetchData.js where the mutate came.
import { useEffect, useState} from 'react';
import axios from 'axios';
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
const fetchData = async () => {
try {
const { data: response } = await axios.get(
"http://localhost/todolistci/backend/index.php/todos/view",
{ crossDomain: true }
);
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
const mutate = () => fetchData();
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
mutate,
};
};
export default FetchData;
I cant stop mutate from executing, I need it to stop using interval how?
This is the image of the app. enter image description here
Not sure what you're trying to do with this mutate function, but I can explain the issue:
Because mutate() is just declared inside your component instead of in a useEffect, it's going to get called any time the component renders & re-renders.
So what happens here, in order (kinda), is:
your component renders with data being empty {};
your component has a useEffect() to fetch the data, so that gets fired when it first renders. Which is good.
At the same time, mutate() gets called, which also does the same exact fetch call as in the use effect. Bad.
You now have two concurrent API calls being sent off.
Some time later, one of the two API calls responds first. Doesn't matter which for our purposes.
On that response, we call setData(...) and change the state data value.
Because the data changed, the component rerenders. Which is good.
Because our component rerenders, the mutate() gets called AGAIN. Oh no. we're now fetching data again, will again setData(...), data state value gets changed, and our component rerenders again.... We're stuck in a loop.
On top of all of that, your component has an extra rerender in there when the 2nd of the two fetchData() calls gets returned. Which doesn't matter because it's all broken anyway.
If you get rid of the mutate(), this works fine? I think?
I have no clue what the purpose of the mutate is, other than to cause ridiculous behavior, so I can't advise on how to solve this. Lol.

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

React, how to make state work between childs?

I am trying to figure out how to make a searchFilter work. Here is my situation:
In App.js I hold a state for my items that i get from an api.
I also hold a state for the SearchFilter.
The items arrive and I can render them just fine.
Further, in App.js, I render the items and also a search component. So my code looks something like this:
const App = () => {
const [items, setItems] = useState([])
const [searchFilter, setSearchFilter] = useState("")
useEffect(() => {
const fetchItems = async () => {
// FETCHING ITEMS AND SETTING VIA setItems...
// This part works as expected
}
fetchItems()
},[])
return (
<>
<SearchBar setSearchFilter={setSearchFilter} />
<RenderItems items={items} searchFilter={searchFilter} />
</>
)
}
The problem I face is, that the searchFilter remains undefined in the RenderItems component. Why?
It gets updated correctly in App.js, but somehow doesn't make it's way to RenderItems
Inside component SearchBar:
const SearchBar = ({setSearchFilter}) => {
return (
<>
<input type="text" placeholder="Search" onChange={(e) => setSearchFilter(e.target.value) }/ >
</>
)
{
Any clues?
Thank you all for the replies #Mandeep Kaur and #KcH
I found the problem was in the data that came from the api when trying this scenario out in a codesandbox.
I keep the link here for future reference: https://codesandbox.io/s/nostalgic-booth-p1tqsv?file=/src/App.js
Closed from my side.
I think this happens because RenderItems component is not re-render after updating the state in SearchBar component.
You can try with adding one useEffect that makes it re-render and it gives the latest data to RenderItems
useEffect(() => {
},[searchFilter])
I am assuming that the updated value you getting in the App.js file.

Clone an object passed from a parent component in a child component - React Hooks

I used spread operator and useState to clone an object passed from the parent component but it returned an empty object. Any helps would be appreciated. Thanks :)
const Parent = () =>{
//I fetch data from my custom hook
const {data} = useFetch(url)
return(
<Child data={data}/>
)
}
const Child = ({data}) =>{
const [copyData, setCopyData] = useState({...data});
//it returns an empty object here
const testing = () =>{
console.log(copyData)
}
return(
<button onClick={testing}>Testing</button>
)
}
The first time your component renders the fetch request won't have completed yet so data will be null (use const {data, loading} = useFetch(url); to see this)
Your child component then uses that null to set the default value for copyData, and you never update it.
You need to add a useEffect to call setCopyData every time data changes. Something like this maybe:
useEffect(() => {
setCopyData({...data});
}, [data]);

In React, how to call function based on const value change

I have 3 components in react. One of them is the parent component and the remaining two are the child components (EziSchedule & EziTransaction), where each component is getting its own data by calling the API. however data to show in child component EziTransaction depends upon what record I click on table in schedule class.
when user click on record in EziSchedule, it goes back to parent component followed by EziTransaction and can print correct id in EziTransaction.
I need to refresh data in EziTransaction component which I am unable to do so. I believe I need to use state changed in order to call getEziTransactionData in EziTransaction component to refresh data but not sure how to do it.
Parent component
const EziTrackerParrent = () =>{
const [data, setData] = useState('schedule');
useEffect(() =>{
},[]);
return (
<div>
<h3>EziSchedule Table</h3>
<EziSchedule change={setData} ></EziSchedule>
<h3>EziTransaction Table</h3>
<EziTransaction data={data}></EziTransaction>
</div>
)
Child Component A - EziSchedule
Capture click event and pass it to parent
const EziSchedule = ({change}) =>{
Child Component B - EziTransaction
Get data from EziSchidule on click event via parent component. I need help here to ensure very time 'data' value changes, It call getEziTransactionData() and refresh html
const EziTransaction = ({data}) =>{
const [eziTransactionData, setEziTransactionData] = useState<IEziTransaction[]>();
useEffect(() => {
getEziTransactionData(); // this method call API to get data
},[]);
const getEziTransactionData = ()=>{ // I need to call this everytime user click on record in EziSchedule???
(async () =>{
try{
const result = //API Call...
setEziTransactionData(result);
........
return(
<div>
<div>I have received "{data}" from parent</div> // this values does change every click in EziSchedule
to trigger getEziTransactionData everytime dataupdates you need to pass data as dependency at useEffect
useEffect(() => {
getEziTransactionData();
},[data]);
You can run the useEffect hook on props/state change by adding that variable in dependency array if the useEffect hook.
In your case you can add data to dependency of useEffect and useEffect will run every time the variable data changes.
If you are using data in your method to make api call, you can make that function a callback and just add that function to the dependency of useEffect, so every time data will change, your callback function will change, which will trigger the useEffect hook.
const EziTransaction = ({data}) => {
const [eziTransactionData, setEziTransactionData] = useState<IEziTransaction[]>();
// wrap this function in a useCallback hook
const getEziTransactionData = useCallback(async () => {
try {
const result = await fetchApiData(data) // assuming you are using `data` in your API Call, if not add it to useEffect dependency and remove from this callback's dependency
setEziTransactionData(result);
} catch (err) {
console.log(err)
}
}, [setEziTransactionData, data]); // set dependency array of callback properly
useEffect(() => {
getEziTransactionData();
},[getEziTransactionData]); // set dependency array properly
return(
<div>
<div>I have received "{data}" from parent</div>
</div>
);
}
in addition to the answer below i would recomend to define you fetch function inside useEffect with data dependency to avoid extra memoization
useEffect(() => {
const getEziTransactionData = async () => {
try {
const result = await fetchApiData(data);
setEziTransactionData(result);
} catch (err) {
console.error(err);
}
});
getEziTransactionData();
}, [data]);

Resources