useState variable is one step behind its assignment - javascript

Sorry in advance if the question is a bit vague, still quite new to JS and react. Anyways, my problem is that in the following code the newFilter state hook is one step behind the event.target.value, which should have been assigned to newFilter at onChange, could anyone enlighten me why the newFilter gets updated one step later?
Output in console from console.log, when input change happens:
The code:
function App() {
const [countries, setCountries] = useState([]);
const [newFilter, setNewFilter] = useState('');
const [allCountries, setAllCountries] = useState([]);
useEffect(() => {
axios.get("https://restcountries.com/v3.1/all").then((response) => {
setAllCountries(response.data);
});
}, []);
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
console.log("this is event.target.value", event.target.value)
console.log("this is the newFilter", newFilter)
if (event.target.value) {
let countriesToShow = allCountries.filter((country) =>
country.name.common.toLowerCase().match(event.target.value.toLowerCase())
);
setCountries(countriesToShow);
}
};
return (
<div>
<strong>
<p>Find countries</p>
</strong>{" "}
<input value={newFilter} onChange={handleFilterChange} />
</div>
);
}
export default App;

React state updates are asynchronous & are not run immediately (kind of like setTimeout(func , 0).
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous.
Hence when you update a state property using its previous value, you should use the callback argument for the state setter.
handleFilterChange = (event) => {
this.setState((state)=> {
newFilter: event.target.value,
countries: (event.target.value)?allCountries.filter(...):allCountries
});
}

Related

How to avoid a function to be called twice when the state changes?

I have this react component
const [filter, setFilter] = useState(valueFromProps);
const [value, setValue] = useState(valueFromProps);
const initialRender = useRef(true);
useEffect(() => {
if (initialRender.current) {
initialRender.current = false;
} else {
console.log("set new value");
const newValue = calculateNewValue(filter);
setvalue(() => newValue);
}
}, [filter]);
const getReports = (value) => {
console.log(value);
//generate some html
};
return (
<>
<div>
{getReports(value)}
</div>
</>
);
pretty standard. It works as expected, the only problem is that getReports is executed twice every time the state filter changes. The first time with the old value and the second time with the new value.
I put some console.log and I can see the function is called twice despite the log in useEffect is printed only once.
What can I do to make it run only once please?
not sure but maybe because of setvalue(() => newValue);, the callback here is used to make updates after the state is changed so setFilter ran and after that setvalue(() => newValue) <-- this
try: setvalue(calculateNewValue(filter));
React runs return statement after filter state update and only then runs useEffect, which itself updates state and triggers re-render.
I would suggest update your state in same place you update your filter or use useMemo for value if it only depends on filter state.
const [filter, setFilter] = useState(valueFromProps);
const [value, setValue] = useState(valueFromProps);
const getReports = (value) => {
console.log(value);
//generate some html
};
cons updateFilter = (filter) => {
setFilter(filter);
setValue(calculateNewValue(filter);
}
return (
<>
<div>
{getReports(value)}
</div>
</>
);
Despite the solution proposed by #Andyally doesn't work, thanks to his suggestion I came up with a solution using useMemo
let value = props.value;
const [filter, setFilter] = useState(valueFromProps);
value = useMemo(() => calculateNewValue(filter), [filter]);
const getReports = (value) => {
console.log(value);
//generate some html
};
return (
<>
<div>
{getReports(value)}
</div>
</>
);

State not updating until I add or remove a console log

const BankSearch = ({ banks, searchCategory, setFilteredBanks }) => {
const [searchString, setSearchString] = useState();
const searchBanks = (search) => {
const filteredBanks = [];
banks.forEach((bank) => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
console.log(bank[searchCategory].toLowerCase());
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
};
const debounceSearch = useCallback(_debounce(searchBanks, 500), []);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory]);
const handleSearch = (e) => {
setSearchString(e.target.value);
};
return (
<div className='flex'>
<Input placeholder='Bank Search' onChange={handleSearch} />
</div>
);
};
export default BankSearch;
filteredBanks state is not updating
banks is a grandparent state which has a lot of objects, similar to that is filteredBanks whose set method is being called here which is setFilteredBanks
if I add a console log and save or remove it the state updates
Adding or removing the console statement and saving the file, renders the function again, the internal function's state is updated returned with the (setState) callback.
(#vnm)
Adding filteredBanks to your dependency array won't do much because it is part of the lexical scope of the function searchBanks
I'm not entirely sure of the total context of this BankSearch or what it should be. What I do see is that there are some antipatterns and missing dependencies.
Try this:
export default function BankSearch({ banks, searchCategory, setFilteredBanks }) {
const [searchString, setSearchString] = useState();
const searchBanks = useCallback(
search => {
const filteredBanks = [];
banks.forEach(bank => {
if (bank[searchCategory].toLowerCase().includes(search.toLowerCase())) {
filteredBanks.push(bank);
}
});
setFilteredBanks(filteredBanks);
},
[banks, searchCategory, setFilteredBanks]
);
const debounceSearch = useCallback(() => _debounce(searchBanks, 500), [searchBanks]);
useEffect(() => {
if (searchString?.length) {
debounceSearch(searchString);
} else setFilteredBanks([]);
}, [searchString, searchCategory, setFilteredBanks, debounceSearch]);
const handleSearch = e => {
setSearchString(e.target.value);
};
return (
<div className="flex">
<Input placeholder="Bank Search" onChange={handleSearch} />
</div>
)}
It feels like the component should be a faily simple search and filter and it seems overly complicated for what it needs to do.
Again, I don't know the full context, however, I'd look into the compont architecture/structuring of the app and state.

is it possible to change the useState value without rendering? React

I need to change the useState without rendering the page.
First is it possible?
const UsersComponent = ({valueProp}) => {
const [users, setUsers] = useState(valueProp);
const [oldUsers, setoldUsers] = useState(value);
const allUsers = useSelector((state) =>
state.users
);
useEffect(() => {
dispatch(getUsersData());
}, [dispatch]);
useEffect(() => {
// assign users to state oldUsers
}, [dispatch]);
const onClickMergeTwoArrayOfUsers = () => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
console.log("filteredUsers", filteredUsers); // not changed
};
I tried everything nothing helps me.
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, [users]); // RETURN INFINITIVE LOOP
I am also try ->
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, []);
Load only one and that doesn't mean anything to me..
I am try with useRef ,but that doesn't help me in this case.
I will try to explain the basis of the problem.
I need to get one get data. After that get on the click of a button, I need to merge oldUsers and users without rendering, change the state. That is problem.
If there is no solution to this problem, tell me what I could do to solve the problem?
I am googling but without succes ... I am also try this solution from interent ->
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
no work.
I am also try with ->
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
Here is problem because I need to asynchronous get only after that can I looping.
Using a ref is probably a better option for whatever it is you're ultimately trying to do.
Yes, it is possible, but it violates one of the core rules of React state: Do Not Modify State Directly.
React compares state values using Object.is equality, so if you simply mutate an object in state instead of replacing it with a new value that is not object-equal, then the state "update" will not cause a re-render (but this is considered a bug in your program!). Anyway, this is how you'd do it:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.1/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function Example () {
const [state, setState] = useState([1]);
const logState = useCallback(() => console.log(state.join(', ')), [state]);
// Don't actually do this!!!
const mutateState = () => {
setState(arr => {
arr.push(arr.at(-1) + 1);
return arr;
});
};
return (
<>
<div>{state.join(', ')}</div>
<button onClick={mutateState}>Mutate state</button>
<button onClick={logState}>Log state</button>
</>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>

Why is calling setState twice causing a single state update?

This is probably a beginner React mistake but I want to call "addMessage" twice using "add2Messages", however it only registers once. I'm guessing this has something to do with how hooks work in React, how can I make this work?
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(messages.concat(message));
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
I'm using React 17.0.2
When a normal form of state update is used, React will batch the multiple setState calls into a single update and trigger one render to improve the performance.
Using a functional state update will solve this:
const addMessage = (message) => {
setMessages(prevMessages => [...prevMessages, message]);
};
const add2Messages = () => {
addMessage('Message1');
addMessage('Message2');
};
More about functional state update:
Functional state update is an alternative way to update the state. This works by passing a callback function that returns the updated state to setState.
React will call this callback function with the previous state.
A functional state update when you just want to increment the previous state by 1 looks like this:
setState((previousState) => previousState + 1)
The advantages are:
You get access to the previous state as a parameter. So when the new state depends on the previous state, the parameter is helpful as it solves the problem of stale state (something that you can encounter when you use normal state update to determine the next state as the state is updated asynchronously)
State updates will not get skipped.
Better memoization of handlers when using useCallback as the dependencies can be empty most of the time:
const addMessage = useCallback((message) => {
setMessages(prevMessages => [...prevMessages, message]);
}, []);
import React from "react";
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(messages => [...messages, message]);
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
This is because messages still refers to the original array. It will get the new array at the next re-render, which will occur after the execution of add2Messages.
Here are 2 solutions to solve your problem :
Use a function when calling setMessages
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (message) => {
setMessages(prevMessages => prevMessages.concat(message));
};
const add2Messages = () => {
addMessage("Message1");
addMessage("Message2");
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => add2Messages()}>Add 2 messages</button>
</div>
);
}
Modify addMessage to handle multiple messages
export default function MyFunction() {
const [messages, setMessages] = React.useState([]);
const addMessage = (...messagesToAdd) => {
setMessages(prevMessages => prevMessages.concat(messagesToAdd));
// setMessages(messages.concat(messagesToAdd)); should also work
};
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
<button onClick={() => addMessage("Message1", "Message2")}>
Add 2 messages
</button>
</div>
);
}
Changing addMessage function as below will make your code work as expected
const addMessage = (message) => {
setMessages(messages => messages.concat(message));
};
Your code didn't work because in case of synchronous event handlers(add2Messages) react will do only one batch update of state instead of updating state after every setState calls. Which is why when second addMessage was called here, the messages state variable will have [] only.
const addMessage = (message) => {
setMessages(messages.concat(message));
};
const add2Messages = () => {
addMessage('Message1'); // -> [].concat("Message1") = Message1
addMessage('Message2'); // -> [].concat("Message2") = Message2
};
So if you want to alter the state value based on previous state value(especially before re-rendering), you can make use of functional updates.

ResizeObserver API doesn't get the updated state in React

I am using ResizeObserver to call a function when the screen is resized, but I need to get the updated value of a state within the observer in order to determine some conditions before the function gets invoked.
It's something like this:
let [test, setTest] = React.useState(true)
const callFunction = () => {
console.log('function invoked')
setTest(false) // => set 'test' to 'false', so 'callFunction' can't be invoked again by the observer
}
const observer = React.useRef(
new ResizeObserver(entries => {
console.log(test) // => It always has the initial value (true), so the function is always invoked
if (test === true) {
callFunction()
}
})
)
React.useEffect(() => {
const body = document.getElementsByTagName('BODY')[0]
observer.current.observe(body)
return () => observer.unobserve(body)
}, [])
Don't worry about the details or why I'm doing this, since my application is way more complex than this example.
I only need to know if is there a way to get the updated value within the observer. I've already spent a considerable time trying to figure this out, but I couldn't yet.
Any thoughts?
The problem is, you are defining new observer in each re render of the component, Move it inside useEffect will solve the problem. also you must change this observer.unobserve(body) to this observer..current.unobserve(body).
I have created this codesandbox to show you how to do it properly. this way you don't need external variable and you can use states safely.
import { useEffect, useState, useRef } from "react";
const MyComponent = () => {
const [state, setState] = useState(false);
const observer = useRef(null);
useEffect(() => {
observer.current = new ResizeObserver((entries) => {
console.log(state);
});
const body = document.getElementsByTagName("BODY")[0];
observer.current.observe(body);
return () => observer.current.unobserve(body);
}, []);
return (
<div>
<button onClick={() => setState(true)}>Click Me</button>
<div>{state.toString()}</div>
</div>
);
};
export default MyComponent;

Categories

Resources