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

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]);

Related

usestate can change the state value after axios in useEffect

I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);

Electron/React cannot access component state changes in ipcRenderer listener

I am using Electron with React and I am facing a little problem.
I am creating a functional component and in the useEffect hook I subscribe to ipcRenderer to listen for when ipcMain replies.
When ipcRenderer event triggers I am unable to access the latest state updates. All state variables values inside ipcRenderer.on function contain data when the component was initially created.
In the code below customers is an array state variable. If I console.log its value every time the ipcRenderer.on is fired it is always empty. I am absolutely sure this variable is not empty inside the component's context because it contains information that is rendered in a grid. When ipcRenderer.on is triggered my grid is reset or cleared. All I am trying to do is refresh a row in the grid when ipcRenderer.on is triggered.
useEffect(() => {
// Actions triggered in response to main process replies
ipcRenderer.on(IPCConstants.UPDATE_SALE_CUSTOMER, (event: any, arg: IUpdateResponse) => {
setIsLoading(false);
if(!arg.success) {
notifyError(arg.message);
return;
}
setCustomers(
customers.map(cst => {
if(cst.CUS_CustomerId === arg.customer.CUS_CustomerId){
cst.RowId = `${generateRandomValue()}`;
cst.isActive = arg.customer.isActive;
}
return cst;
})
)
});
return () => {
ipcRenderer.removeAllListeners(IPCConstants.UPDATE_SALE_CUSTOMER);
};
}, []);
useEffect(() => {
// Actions triggered in response to main process replies
ipcRenderer.on(IPCConstants.UPDATE_SALE_CUSTOMER, (event: any, arg: IUpdateResponse) => {
setIsLoading(false);
if(!arg.success) {
notifyError(arg.message);
return;
}
setCustomers(
customers.map(cst => {
if(cst.CUS_CustomerId === arg.customer.CUS_CustomerId){
cst.RowId = `${generateRandomValue()}`;
cst.isActive = arg.customer.isActive;
}
return cst;
})
)
});
return () => {
ipcRenderer.removeAllListeners(IPCConstants.UPDATE_SALE_CUSTOMER);
};
}, [customers, otherState (if needed)]); // <= whichever state is not up to date
You need to include the proper dependencies.
the useEffect will keep up to date with these state values
alternatively if you don't need the functionality of useState (unlikely) you can use useRef and the useEffect will always have the up to date ref without passing it as a dependency.

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]);

How do I avoid trigger useEffect when I set the same state value?

I use react hook call an API and set the data to state and I still have some control view can change the value, but when I change it, my API function trigger again, it cause my view re render multiple times.
How do I use my fetchData function just like in class component componentDidMount function ?
const [brightness, setBrightness] = useState(0);
useEffect(() => {
fetchData();
});
const fetchData = async () => {
const value = await something(); // call API get the first value
setBrightness(value);
};
return (
<View>
<SomeView value={brightness} onComplete={(value) => setBrightness(value)}
</View>
);
Your useEffect will be triggered on every render because you haven't provided the 2nd argument.
The 2nd argument is what the effect is conditional upon. In your case you only want it to run once so you can provide an empty array.
useEffect(() => {
// Run once
}, [])
Lets say you wanted it to fetch anytime some prop changed, you could write
useEffect(() => {
// run every time props.myValue changes
}, [props.myValue])
See https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Not awaiting for data in useEffect

I have a useEffect in my component that is waiting for data from the context so that it can set it in state. But its not waiting for the state and is moving on to the next line of code to set isLoading to false.
I'd like it to wait for the data so that I can render the loading.
I tried setting the isFetchingData in the context but I had run into problems where if another component calls it first it would set the isFetchingData state to false.
First call to ReactContext is setting the isLoading sate to false
It is fine for results to come back with no records. The component would render 'No records found'. Therefore, I cannot check the length on state to say if length is zero then keep loading.
Following is my code:
Context
const [activeEmployees, setActiveEmployees] = useState([]);
const [terminatedEmployees, setTerminatedEmployees] = useState([]);
useEffect(() => {
getEmployees()
.then(response => {
/// some code...
setActiveEmployees(response.activeEmployees)
setTerminatedEmployees(response.terminatedEmployees)
});
});
Component
const EmployeesTab = () => {
const { activeEmployees, terminatedEmployees } = useContext(BlipContext);
//Component states
const [isFetchingData, setIsFetchingData] = useState(true);
const [newEmployees, setNewEmployees] = useState([]);
const [oldEmployees, setOldEmployees] = useState([]);
useEffect(() => {
async function getData() {
await setNewEmployees(activeEmployees);
await setOldEmployees(terminatedEmployees);
setIsFetchingData(false);
}
getData();
}, [activeEmployees, terminatedEmployees, isFetchingData]);
if(isFetchingData) {
return <p>'Loading'</p>;
}
return (
// if data is loaded render this
);
};
export default EmployeesTab;
Since you have useState inside your useContext, I don't see the point of storing yet again the activeEmployees in another state.
If you want to have a local loading variable it could something like:
const loading = !(activeEmployees.length && terminatedEmployees.length);
This loading will update whenever getEmployees changes.
And to answer you question, the reason await is not having an effect is because setState is synchronous.

Resources