How to stop useSelector from invoking multiple times - reactjs

I have a component like below -
const MyComponent = () => {
const bankingAccounts = useSelector(state => state.dogs);
useEffect(() => {
console.log(dogs);
}, [dogs]);
return <div>Demo</div>;
};
Here dogs returns an array of object. There are some other things going on in my app which updating the some properties of dog in the store and its is resulting in reinvoking of the above useSelector.
I have tried passing a custom equality function in useSelector, but no luck.
useSelector(state, areEqual)
How to fix this issue?

If you don't want the reactivity, then you can use useStore [will only return the current state] instead of useSelector() [ is called every time there is an update in the state.]
const { getState } = useStore()
const bankingAccounts = getState().dogs;

Related

Use React State in a hook with two separate exports

So i have created a hook which will act as a bridge between two different components.
So the second function will take the data that the first function calculates and display them, however the first function will actually return a count of the displayed data.
These need to be connected with a state, since they should also update on change, those data can be deleted in the second function.
So what i want to do is, i want to setState in the second function and read it out in the first one. The way i did it now it won't work, since the useState is outside of a component.
How can i use this useState in both of these functions?
const [state, setState] = useState<string[]>([]);
export const thisUsesState = () => {
return countOfArrayState;
}
export const thisUpdatesStateReturnsComponent = () => {
return <Component></Component>;
}
Make a parent component for both the function and you can use state like a charm.
const Func: React.FC = () => {
const [state, setState] = useState<string[]>([]);
export const thisUsesState = () => {
return countOfArrayState;
}
export const thisUpdatesStateReturnsComponent = () => {
return <Component></Component>;
}
return something
}
export default Func

How can I memoize a variable derived from redux useSelector?

How can I memoize my rawTranscript variable so it doesn't trigger the useEffect below which subsequently triggers the expensive transcriptParser function? I've been trying a lot of different approaches, but the fact that I am using a redux-hook (useAppSelector) to capture the data from the store means I cannot use an empty dependency useEffect for the initial mount (hooks can't be inside of useEffect). I also can't seem to wrap the useAppSelector with a useMemo either for the same reason.
Any thought's on how I can memoize the rawTranscript variable so it doesn't re-trigger the useEffect?
error when using the redux-hook inside useMemo, useEffect, useCallback:
React Hook "useAppSelector" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
component
const TranscriptCardController = (): JSX.Element => {
const dispatch = useAppDispatch();
// how to memoize rawTranscript?
const rawTranscript = useAppSelector(selectRawTranscript);
const parsedTranscript = useAppSelector(selectParsedTranscript);
useEffect(() => {
const parsedResult = transcriptParser(rawTranscript, undefined, 0.9);
dispatch(updateParsedTranscript(parsedResult));
}, [dispatch, rawTranscript]);
// ...
};
selector
export const selectRawTranscript = createSelector(
(state: RootState) => state.transcript.rawTranscript,
(rawTranscript): RawTranscript => rawTranscript
);
There is no issue here if your selectRawTranscript function is purely selecting a value from the store, like state => state.transcript.raw. Your effect will only run when the value of rawTranscript changes -- as it should.
If your selectRawTranscript function returns a new object every time (like it if it involves array mapping, etc.) then this is a problem that you can address either in the selector itself or in the component.
Memoized Selectors
The best place to fix this is by using createSelector to create a memoized selector. For example:
import {createSelector} from '#reduxjs/toolkit';
export const selectRawTranscript = createSelector(
(state: RootState) => state.data.someRawValue,
(rawValue) => rawValue.map(entry => entry.data)
);
The second part of the selector is the "combiner" and it will only re-run when the value selected in the first part changes. So you get a consistent object reference.
Equality Comparisons
If you want to fix this in the component, the way to do that is by including a second argument on useAppSelector (which I'm assuming is just a typed version of useSelector).
This second argument allows you to specify a custom equality function so that you have more control over when the selected data is considered to be "changed". It's common to use a shallow equality comparison, so this is actually included in the react-redux package.
import { shallowEqual } from 'react-redux';
import { useAppSelector } from ...
const TranscriptCardController = (): JSX.Element => {
const rawTranscript = useAppSelector(selectRawTranscript, shallowEqual);
...
Note: it's impossible for me to know whether or not you really do have a problem with undesirable changes in rawTranscript because you haven't included your selector function. You might be overthinking this and it might be a non-issue.
Create a standalone useCallback where your dispatch will run on every store update but useEffect will only execute when the callback method is executed.
const TranscriptCardController = (): JSX.Element => {
const dispatch = useAppDispatch();
// how to memoize rawTranscript?
const rawTranscript = useAppSelector(selectRawTranscript);
const parsedTranscript = useAppSelector(selectParsedTranscript);
const callback = useCallback(() => {
const parsedResult = transcriptParser(rawTranscript, undefined, 0.9);
dispatch(updateParsedTranscript(parsedResult));
}, [rawTranscript])
useEffect(() => {
const unsubscribe = callback()
return unsubscribe
}, [callback]);
// ...
};

React/Redux How to set value from redux store to state of component

I need to change my received data from redux store to another variable (and then modify it).
At the moment, I receive data from store after API call and it is stored offices, but it is not set to my officeData variable. Does anyone how can I solve that?This is my code:
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices)
const [officeData, setOffices] = useState(offices);
debugger;
useEffect(()=> {
dispatch(getOffices());
setOffices(offices);
debugger
}, [dispatch])
If you don't even enter your useEffect i think it's because you give dispatch as a dependency.
Effect are triggered when component mount but also when component update (prop or state). So you could do something like that :
import { useEffect } from "react";
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices);
const [officeData, setOffices] = useState(undefined);
const [didMount, setDidmount] = useState(false);
// When component mount, load your Offices data
useEffect(() => {
if(!didMount){
dispatch(getOffices());
setOffices(offices);
} else {
setDidmount(true);
}
});
useEffect(() => {
if(didMount) {
// When you update officeData, do your thing
}
}, [officeData]);
I don't know the behavior of useSelector, but i guess it does not trigger a rendering. Maybe you could have a useEffect with offices as dependency, just be careful not to loop !

useEffect re-renders too many times

I have this component, that needs to fetch data, set it to state and then pass it to the children.
Some of the data also needs to be set in context.
My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute.
I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing.
At the moment the array is containg the set...functions to prevent eslint to throw warnings.
Is there a better way to avoid this many re-renders ?
const Home = (props) => {
console.log("TCL: Home -> props", props);
const classes = useStyles();
const [value, setValue] = React.useState(0);
//CONTEXT
const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
const {
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
} = useContext(UserContext);
// STATE
const [searchSettings, setSearchSettings] = useState([]);
const [oppData, setOppData] = useState([]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = index => {
setValue(index);
};
//API CALLS
useEffect(() => {
const triggerAPI = async () => {
setIsFullView(false);
const oppResponse = await API.getOpportunity();
if(oppResponse){
setOppData(oppResponse.response);
}
const profileResponse = await API.getUserProfile();
if(profileResponse){
setUserName(profileResponse.response.first_name);
setUserLastName(profileResponse.response.last_name);
setUserEmail(profileResponse.response.emailId);
}
const profileExtData = await API.getUserProfileExt();
if(profileExtData){
setAvatarProfile(profileExtData.response.avatar);
setListSavedJobs(profileExtData.response.savedJobs);
setSearchSettings(profileExtData.response.preferredIndustry);
}
};
triggerAPI();
}, [
setOppData,
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
setListSavedJobs,
setIsFullView,
]);
...```
Pass just an empty array to second parameter of useEffect.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
Source
Edit: Try this to avoid rerenders. Use with caution
Only Run on Mount and Unmount
You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.
Prevent useEffect From Running Every Render
If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)
const [value, setValue] = useState('initial');
useEffect(() => {
// This effect uses the `value` variable,
// so it "depends on" `value`.
console.log(value);
}, [value])
For more clarification useEffect
If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html

React get state from Redux store within useEffect

What is the correct way to get state from the Redux store within the useEffect hook?
useEffect(() => {
const user = useSelector(state => state.user);
});
I am attempting to get the current state within useEffect but I cannot use the useSelector call because this results in an error stating:
Invariant Violation: Hooks can only be called inside the body of a function component.
I think I understand why as it breaks one of the primary rules of hooks.
From reviewing the example on the Redux docs they seem to use a selectors.js file to gather the current state but this reference the mapStateToProps which I understood was no longer necessary.
Do I need to create some kind of "getter" function which should be called within the useEffect hook?
Don't forget to add user as a dependency to useEffect otherwise your effect won't get updated value.
const user = useSelector(state => state.user);
useEffect(() => {
// do stuff
}, [user]);
You can place useSelector at the top of your component along with the other hooks:
const MyComponent = () => {
...
const user = useSelector(state => state.user);
...
}
Then you can access user inside your useEffects.
I found using two useEffects to works for me, and have useState to update the user (or in this case, currUser).
const user = useSelector(state=>state.user);
const [currUser, setCurrUser] = useState(user);
useEffect(()=>{
dispatch(loadUser());
}, [dispatch]);
useEffect(()=>{
setCurrUser(user);
}, [user]);
You have to use currUser to display and manipulate that object.
You have two choices.
1 - If you only need the value from store once or 'n' time your useEffect is called and don't want to listen for any changes that may occur to user state from redux then use this approach
//import the main store file from where you've used createStore()
import {store} from './store' // this will give you access to redux store
export default function MyComponent(){
useEffect(() =>{
const user = store.getState().user;
//...
},[])
}
2 - If you want to listen to the changes that may occur to user state then the recommended answer is the way to go about
const MyComponent = () => {
//...
const user = useSelector(state => state.user);
useEffect(() => {
//...
},[])
//...
}
const tournamentinfofromstore=useSelector(state=>state.tournamentinfo)
useEffect(() => {
console.log(tournamentinfofromstore)
}, [tournamentinfofromstore])
So the problem is that if you change the state inside the useEffect that causes a rerender and then again the useEffect gets called "&&" if that component is passing data to another component will result in infinite loops.and because you are also storing that data in the child component's state will result in rerendering and the result will be infinite loop.!!
Although it is not recommended, you can use store directly in your component, even in the useEffect.
First, you have to export store from where it is created.
import invoiceReducer from './slices/invoiceSlice';
import authReducer from './slices/authSlice';
export const store = configureStore({
reducer: {
invoices: invoicesReducer,
auth: authReducer,
},
});
Then you can import it to a React Component, or even to a function, and use it.
import React, { useEffect } from 'react';
import { store } from './store';
const MyComponent = () => {
useEffect(()=> {
const invoiceList = store.getState().invoices
console.log(invoiceList)
}, [])
return (
<div>
<h1>Hello World!</h1>
</div>
)
}
export default MyComponent
You can study the API for Store in here.
You can also see why this approach is not recommended in
here.
Or, if you are interested in using redux store outside a react component, take a look at this blog post.
To add on top of #Motomoto's reply. Sometimes you depend on store to be loaded before useEffect. In this case you can simply return in if the state is undefined. useEffect will rerender once the store is loaded
const user = useSelector(state => state.user);
useEffect(() => {
if(user === undefined){
return}else{
// do stuff
}}, [user]);
I'm having the same issue, The problem to the useSelector is that we cant call it into the hook, so I can't be able to update with the action properly. so I used the useSelector variable as a dependency to the useEffect and it solved my problem.
const finalImgData_to_be_assigned = useSelector((state) => state.userSelectedImg);
useEffect(()=>{
console.log('final data to be ready to assign tags : ', finalImgData_to_be_assigned.data);
}, [finalImgData_to_be_assigned ])

Resources