Trouble chaining useEffects to use updated state from previous effects - javascript

I have an application that has some complex data fetching. Overall, here is a snapshot of the logic in my application
// dep1 is from redux, dep2 is local state
// useEffect 1
useEffect(() => {
// perform some state variable update to dep2
}, [dep1]);
// useEffect 2
useEffect(() => {
// use some values from deps to fetch data
}, [dep1, dep2]);
The issue I am facing is that when dep1 and/or dep2 update, the state change from useEffect 1 needs to reflect in the request url of the data fetching operation in useEffect 2. useEffect 2 ends up running twice, once with the dep1 update (without the dep2 update from useEffect 1 in the url) and once with the dep2 update. This issue is not specifically noticeable in most cases where we are just rendering, but we end up with double api fetches in cases where data fetching is used in the useEffect. What is some strategy that I can use to circumvent this double API call?
EDIT
Adding more code to allow more specifity for problem:
// useEffect 1
// when the user is changed (user is a prop that is from redux),
// option should be reset to "DEFAULT"
useEffect(() => {
setOption("DEFAULT");
}, [currentUser]);
// useEffect 2
// option is a value that can be set within the UI and is local state.
// setting option to a new value will trigger api call with new value
useEffect(() => {
const data = await getData(option);
}, [currentUser, option]);
The issue when option is not "DEFAULT" and currentUser changes, useEffect 2 will run twice. I would like to find some logic to allow it to run once with option set back to "DEFAULT" if currentUser changed. Is this possible using other react patterns, since it doesn't seem possible with useEffect?

I think you are complicating it alot. I would suggest using a single effect hook and compute the logic and perform data fetching in the same effect.
/ dep1 is from redux, dep2 is local state
// useEffect 1
useEffect(() => {
// perform some state variable update to dep2
// perform data fetching here and if you want to check some condition for dep2 you could do that here as well.
}, [dep1]);

Try setting dep2 inside a react component within the current component and passing it as a prop, so that you only have one useEffect in each component, with state being passed up.
const component = () => {
useEffect(() => {
// do something
}, [dep1]);
return (
<div> <ComponentTwo depTwo={depTwo}/> </div>
)
}
then in ComponentTwo ...
const componentTwo = ({ depTwo }) => {
// useEffect 2
useEffect(() => {
// use some values from deps to fetch data
}, [dep2]);
return (<div> something </div>
)
}
You'll need to import ComponentTwo inside the parent component.

Related

Repeat re-rendering causing multiple datalayer pushes

One of the journeys in my app kicks off several consecutive dispatches, one main dispatch with several side effects based on the result of the preceding one - with each API call making a Redux state change via the reducer.
After the API calls I am feeding data to a separate microservice to bring back logic that will dictate the sub-component to render. And it is in these sub-components that I am wanting to make a single datalayer push.
The issue I am having is that I am getting multiple renders/rerenders due to the constant data changes each time the reducer is hit, as you would imagine... And each time the main parent component is rendered/rerendered due to state change, I am sending a datalayer push as the sub-component is rendered again...
I'm wondering if there are any ways in which I can stop the rendering so much and the constant triggering of my sub-component and its datalayer push.
Note - I have tried wrapping these components with React.memo and using a custom prop checker using lodash.isEqual, however the Redux state changes slightly after each reducer call, so this doesn't really help.
SubComponent.jsx
const SubComponent = props => {
useEffect(() => {
// Do datalayer push here
}, []); // useEffect runs once on render
// Return html here
}
MyComponent.jsx
const mapStateToProps = state => ({...});
const mapDispatchToProps = dispatch => ({...});
const MyComponent = (props) => {
// Note: Runs on each render - Will be required to run when any redux state changes
useEffect(() => {
// Set up microservice wizard config
});
return (
<div>
{microserviceWizard.renderPage(props.step)}
<EcommerceHandler /> // This also makes Redux state changes
</div>
);
}

Setstate if fetch result is different

I am fetching data from some url each second. If data is different than data inside my state, I want to update that state and rerender, and if data is same I do not want to do anything
First I tried most obvious thing, to set interval in useEffect on mount, but it do not work since state always return initial value which is obvious.
Second I created two states, one that holds data and other temp one, then I update temp state and on its useEffect I compare values. It does work but I still got rerender when updating that temp state, and whole point was to not have unnecessary rerender.
Third thing I tried is holding that temp data inside variable or ref, but useEffect is not working on them.
Here is last code I tried with ref so you get idea of what I am trying to do:
const MyComp = () => {
const [data, setData] = useState([])
const tempDataRef = useRef([])
useEffect(() => {
apiFetch().then((returnedArray) => {
tempDataRef.current = returnedArray
})
}, [])
useEffect(() => {
// in this solution using ref, this useeffect is not firing
if(JSON.stringify(tempDataRef.current) != JSON.stringify(data)) {
setData(tempDataRef.current)
}
}, [tempDataRef.current])
return (
<div>
{JSON.stringify(data)}
</div>
)
}
whole point was to not have unnecessary rerender.
tl;dr - it's not possible. Component has to be aware that the data has changed.
setData(tempDataRef.current) code does not fire at all, since useEffect does not listen to useRef updates.
You have two options - either store the data in the state, or keep the useRef but then you will have to apply some interval to check if the data has changed and if so - re-render the component. But of course this is pointless, because you will end up with unnecessary re-renders.
If you are worried about performance drop caused by this "extra" re-render when fetching the data you can always memoize your children so they won't re-render unnecessarily.

Get state and update state in a non react component in nextjs

I am working on a nextjs project where i have a helpers folder in level with pages folder.
I have a ts file inside helpers folder and here i want to get the latest state and update state depending on the latest state
This is how im getting the state
store().getState()
where store is imported from store.js
Im updating state depending on the previous state
const state = store().getState()
if(!state.currentUser){ // here im checking if state has currentUser
store().dispatch(Action) // here im calling action which will update the state
}
do further operations
The problem here is I'm not getting the updated state from store().getState() after updating the state. Is the way I'm managing things correctly? How to get the updated state?
*EDIT* : Im sending a helper function as a prop to many if my page components. Now that i dont want to touch this , i somehow want to get the updated state and dispatch actions based on the state itself. Note that the hepler function is not a functional component
Thanks in advance
The problem is that this store you're using isn't part of React, so React doesn't know when the data changes. You have to create a way to let React know that the data changes so it could then rerender your component or trigger an action.
Does your store offer a way to subscribe to changes? If so you can do something like this in your component (assuming you're using hooks):
Edit: Reusable hook way:
export const useStore = () => {
const [storeState, setStoreState] = useState(store().getState());
useEffect(() => {
const subscribeFunc = (newState) => setStoreState(newState));
store().subscribe(subscribeFunc);
return () => {
store().unsubscribe(subscribeFunc);
}
}, [])
return [storeState, store().dispatch]
}
then in your component
const [storeState, dispatch] = useStore();
// listen to changes of the currentUser and fire actions accordingly
useEffect(() => {
if (!storeState.currentUser) {
dispatch(Action)
}
}, [storeState.currentUser])
Initial way:
// sync the store state with React state
const [storeState, setStoreState] = useState(store().getState());
useEffect(() => {
const subscribeFunc = (newState) => setStoreState(newState));
store().subscribe(subscribeFunc);
return () => {
store().unsubscribe(subscribeFunc);
}
}, [])
// listen to changes of the currentUser and fire actions accordingly
useEffect(() => {
if (!storeState.currentUser) {
store().dispatch(Action)
}
}, [storeState.currentUser])
By setting the state in the component on change, React now knows that data changed and will act accordingly.
This is a very local approach to explain the concept, but it would obviously be better to create a reusable hook to use throughout your app for any store.

React - UseEffect hook with Redux store in dependency array and which updates the state ends in an endless loop

I have an array of certain objects in my redux store and I retrieve it like so:
const storeExpenses = useSelector(({ expenses }: RootState) => expenses.items));
I then save those expense objects also in the components local state since I have to further filter them without wanting to change them in the store.
const [expenses, setExpensesInState] = useState<Expense[]>(storeExpenses);
Now, when my store expenses are updated somewhere else, I want to refresh the local state as well, like so:
useEffect(() => {
setExpensesInState(storeExpenses));
}, [storeExpenses]);
However this results in an endless loop of the useEffect hook.
My assumption is that when I use setExpensesInState, I trigger a redraw of the component which then sets the expensesInStore variable which in turn triggers again the useEffect and so on. Is this assumption correct or am I misunderstanding anything else? And how would I resolve this to achieve what I need?

React.js useState values do not match asynchronously

I'm having an odd issue where some times the value passed into useState is not the same as the variable for useState. This happens on the same UI component each time while others are not having the issue. Just wanted to double check if I'm doing anything wrong here.
// userData is from Redux store
const {userData} = props
const [installed, setInstalled] = useState(userData.installed) // installed: boolean
console.log(userData.installed) // returns true
console.log(installed) // returns false
console.log(userData) // installed: true
Reason I'm using useState is because I'm using it to render a button that will be toggled, as well as displaying an indicator whether it is toggled or not.
<Button onClick={() => setInstalled(!installed) />
I recommend to use useEffect to watch the state inside your Redux store then update the local state based on that changes :
const [userData] = props ;
const [installed, setInstalled] = useState(userData.installed)
useEffect(() => {
setInstalled(userData.installed)
},[userData])

Categories