React / Redux - Getting variable to update properly on change of global state - reactjs

I currently have a component (A) that has many local state variables, and also uses useSelector((state) => state.app.<var>. Some of the local state variables rely on that global state, and I need to render one local variable onto the screen.
code example:
const ComponentA = () => {
const globalState = useSelector((state) => state.app.globalState);
// CASE 1: WORKS
const localState1 = 'hello' + globalState + 'world';
// CASE 2: DOESN't WORK
const [localState1, setLocalState1] = useState(null);
const [lcoalState2, setLocalState2] = useState(null);
useEffect(() => {
}, [localState1]);
useEffect(() => {
setLocalState1('hello' + globalState + 'world')
}, [localState2]);
return (
.... code changes
<p>{localState1}</p>
);
}
Case 1 results in the localState1 properly being updated and rendered on the screen, but in Case 2 localState1 is not updated on the screen.
I have no idea why setting localState1 to a regular variable instead of a local state variable works. I thought that a change in local state would cause a re-render on the DOM, meaning I could visually see the change. Could someone explain why the local state case fails to update and how to fix it?

You need to make your useEffect aware of globalState change by adding it to dependencies array (anyway you should get a linting warning when you forget it, like in your case):
const ComponentA = () => {
const globalState = useSelector((state) => state.app.globalState);
const [localState1, setLocalState1] = useState(null);
useEffect(() => {
setLocalState1("hello" + globalState + "world");
}, [globalState]);
return <p>{localState1}</p>;
};
Moreover, you don't really need a state for it, just implement the selector according to your needs, it always will update on state.app.globalState change:
const ComponentA = () => {
const globalStateString = useSelector(
(state) => `hello ${state.app.globalState} world`
);
return <p>{globalStateString}</p>;
};

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

react function component issue with usEffect and useState

Sometimes I have to use some native js libaray api, So I may have a component like this:
function App() {
const [state, setState] = useState(0)
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1.innerHTML = 'h1h1h1h1h1h1'
container.append(h1)
h1.onclick = () => {
console.log(state)
}
}, [])
return (
<div>
<button onClick={() => setState(state => state + 1)}>{state}</button>
<div id="container"></div>
</div>
)
}
Above is a simple example. I should init the lib after react is mounted, and bind some event handlers. And the problem is coming here: As the above shown, if I use useEffect() without state as the item in dependencies array, the value state in handler of onclick may never change. But if I add state to dependencies array, the effect function will execute every time once state changed. Above is a easy example, but the initialization of real library may be very expensive, so that way is out of the question.
Now I find 3 ways to reslove this, but none of them satisfy me.
Create a ref to keep state, and add a effect to change it current every time once state changed. (A extra variable and effect)
Like the first, but define a variable out of the function instead of a ref. (Some as the first)
Use class component. (Too many this)
So is there some resolutions that solve problems and makes code better?
I think you've summarised the options pretty well. There's only one option i'd like to add, which is that you could split your code up into one effect that initializes, and one effect that just changes the onclick. The initialization logic can run just once, and the onclick can run every render:
const [state, setState] = useState(0)
const h1Ref = useRef();
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1Ref.current = h1;
// Do expensive initialization logic here
}, [])
useEffect(() => {
// If you don't want to use a ref, you could also have the second effect query the dom to find the h1
h1ref.current.onClick = () => {
console.log(state);
}
}, [state]);
Also, you can simplify your option #1 a bit. You don't need to create a useEffect to change ref.current, you can just do that in the body of the component:
const [state, setState] = useState(0);
const ref = useRef();
ref.current = state;
useEffect(() => {
const container = document.querySelector('#container');
// ...
h1.onClick = () => {
console.log(ref.current);
}
}, []);

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

how can we use redux state in useState to set initial values

I am trying to use redux value to set an initial state of react component using useState.when I am trying to set the state of setIsStar it says currentChannelName is null. How can I avoid this? or is there any other way
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
const [isStar, setIsStar] = useState({
[currentChannelName]: true
});
Possible solution is to combine it with useEffect:
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
useEffect(() => {
if(currentChannelName) {
setIsStar({[currentChannelName]: true});
}
}, [currentChannelName]); // listen only to currentChannelName changes
const [isStar, setIsStar] = useState(currentChannelName ? {
[currentChannelName]: true
}: {});
`
You should avoid this as it dilutes your state across two separate domains.
Keep app-wide state in your redux store, keep local component state in your components.
If you really do want to do this, you'll probably just have to deal with the initial case where your component has mounted but the store is not populated with the data you need.
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
const [isStar, setIsStar] = useState(currentChannelName && {
[currentChannelName]: true
});
The easiest solution would be:
// Redux state
const user = useSelector((state) => state.user);
const [firstName, setFirstName] = useState(user.firstName || '');

Resources