useReducer not updating correctly - javascript

I have a Select custom component, that only have a select, options and listen for a onChange, then i have a useReducer code, that initialize with some variables, after select one option my state still have the initialized value
When i select ANOTHER the value in performSearch is ALL
const reducer = (state, newState) => ({ ...state, ...newState });
const [state, setState] = useReducer(reducer, {
filterStatus : 'ALL'
});
const performSearch = () => {
console.log(state.filterStatus) //<= first time is ALL, second time same value ALL, third, is another value
}
useEffect(() => {
performSearch()
},[])
<Select
onChange={(e) => {
const {value} = e.target
setState({filterStatus:value})
performSearch()
}}
items={[{key:"ALL",value:"ALL"},{key:"ANOTHER",value:"ANOTHER"}]}
/>
any idea?

If I had to guess I'd say that its because you are trying to call performSearch to log the console before the state is set. If you console log the state before you return your component you will probably be able to see the correct value in the state. I'm not sure what your use case is but if you want to use the value you can just return it in your function and not worry about the reducer and state at all. Like so:
const performSearch = (value) => {
console.log(value)
}
useEffect(() => {
performSearch('ALL')
},[])
<Select
onChange={(e) => {
const {value} = e.target
performSearch(value)
}}
items={[{key:"ALL",value:"ALL"},{key:"ANOTHER",value:"ANOTHER"}]}
/>
if you need to use the reducer then you can probably create a promise or I would just return the value to preformSearch and then set the state through your reducer from there like so:
const reducer = (state, newState) => ({ ...state, ...newState });
const [state, setState] = useReducer(reducer, {
filterStatus : 'ALL'
});
const performSearch = (value) => {
setState({filterStatus: value});
//Do your stuff with the value
console.log(value)
}
useEffect(() => {
//you can probably just set the value in preformSearch here manually or you can set it to the states value but setting to the states value would result in another render because you would set the state in performSearch again
performSearch(state.filterStatus)
},[])
<Select
onChange={(e) => {
const {value} = e.target
performSearch(value)
}}
items={[{key:"ALL",value:"ALL"},{key:"ANOTHER",value:"ANOTHER"}]}
/>
But like I said I'm not really sure what your end goal is with this component but for this use case I'm not sure you need to use the useReducer function at all.

The problem is that you are calling performSearch() inside the onChange function, so when you set the new state you will only see the value from the previous state. Try to put the performSearch function outside of the onSelect function and you will get the correct output.

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

useState variable is one step behind its assignment

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

Back to back useState not updating first state, only the second one

I am trying to have a general function which run setState twice, however, the first setState will not update. Are there any way to get around it? or how to fix this issue?
Parent
const [data, setData] = useState({});
const updateData = (key, value) => {
console.log(key, value);
setData({ ...data, [key]: value });
};
...
<div>
Num 1: {data.num1}, Num2: {data.num2}
</div>
<Child updateData={updateData} />
Child
const { updateData } = props;
const onClick = () => {
updateData("num1", 1);
updateData("num2", 2);
};
return <button onClick={onClick}> Click here </button>
console.log return both values being called, but only 1 value being updated
codesandbox example here
(After some testing, even calliing both in the same parent function, if calling setData twice, it still wont work (see Simplify.js)
While you could use a callback so that the argument contains the currently updated state, including prior state setters that've run but before a re-rendering has occurred:
const updateData = (key, value) => {
setData(data => ({ ...data, [key]: value }));
};
If you have a limited number of possible properties in the data variable, consider using separate states instead:
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
const onClick = () => {
setNum1(num1 + 1);
setNum2(num2 + 2);
};
setState is asynchronous so if you do the two calls one after the other in the same function the state won't have updated for the second call and you won't get what you want.
The best practise in general for when setting state based off previous state is to use a callback.
const updateData = (key, value) => {
setData(prevData => { ...prevData, [key]: value });
};
const onClick = () => {
updateData("num1", 1);
updateData("num2", 2);
};

React does not re-render when state changes

I have a list of warehouses that I pull from an API call. I then render a list of components that render checkboxes for each warehouse. I keep the state of the checkbox in an object (using the useState hook). when I check/uncheck the checkbox, I update the object accordingly.
My task is to display a message above the checkbox when it is unchecked. I tried simply using the object, however, the component was not re-rendering when the object changed.
I found a solution to my problem by simply adding another useState hook (boolean value) that serves as a toggle. Since adding it, the component re-renders and my object's value is read and acted on appropriately.
My question is: why did I have to add the toggle to get React to re-render the component? Am I not updating my object in a manner that allows React to see the change in state? Can someone explain to me what is going on here?
I've created a sandbox to demonstrate the issue: https://codesandbox.io/s/intelligent-bhabha-lk61n
function App() {
const warehouses = [
{
warehouseId: "CHI"
},
{
warehouseId: "DAL"
},
{
warehouseId: "MIA"
}
];
const [warehouseStatus, setWarehouseStatus] = useState({});
const [toggle, setToggle] = useState(true);
useEffect(() => {
if (warehouses.length > 0) {
const warehouseStates = warehouses.reduce((acc, item) => {
acc[item.warehouseId] = true;
return acc;
}, {});
setWarehouseStatus(warehouseStates);
}
}, [warehouses.length]);
const handleChange = obj => {
const newState = warehouseStatus;
const { name, value } = obj;
newState[name] = value;
setWarehouseStatus(newState);
setToggle(!toggle);
};
return warehouses.map((wh, idx) => {
return (
<div key={idx}>
{!warehouseStatus[wh.warehouseId] && <span>This is whack</span>}
<MyCheckbox
initialState
id={wh.warehouseId}
onCheckChanged={handleChange}
label={wh.warehouseId}
/>
</div>
);
});
}
Thanks in advance.
You are mutating state (don't mutate state)
this:
const handleChange = obj => {
const newState = warehouseStatus;
const { name, value } = obj;
newState[name] = value;
setWarehouseStatus(newState);
};
should be:
const handleChange = ({name,value}) => {
setWarehouseStatus({...warehouseStatus,[name]:value});
};
See the problem?
const newState = warehouseStatus; <- this isn't "newState", it's a reference to the existing state
const { name, value } = obj;
newState[name] = value; <- and now you've gone and mutated the existing state
You then call setState with the same state reference (directly mutated). React says, "hey, that's the same reference to the state I previously had, I don't need to do anything".

Setting value in react-select Async without adding extra state

I have built a function component, DepartmentSelect, that uses react-select Async to allow users to select a department from a dropdown. I want to pass the id of the selected department in the props.
I have a working example, but it seems like I have duplicated logic. I passed a promise to the loadOptions react-select prop and I store the options in the state. I have the departmentId of the selected department in the DepartmentSelect props and I am storing the selected value in the state.
//... imports and getOptionLabel removed for brevity
const getOptionValue = (option) => {
return option.id;
};
interface Props {
departmentId: string,
onChange: (number) => void
}
let options = [];
const DepartmentSelect = (props: Props) => {
const [value, setValue] = useState(
options.find(o => o.id == props.departmentId)
);
const [isLoading, setLoading] = useState(true);
const handleChange = (option, action) => {
const id = option && option.id;
setValue(options.find(o => o.id == id));
props.onChange(id);
};
const loadOptions = () => {
return ky.get('/sbm/departments.json')
.then(r => r.json())
.then(json => {
options = json;
setValue(options.find(o => o.id == props.departmentId));
setLoading(false);
return json;
});
};
return (
<AsyncSelect
defaultOptions
getOptionValue={getOptionValue}
loadOptions={loadOptions}
onChange={handleChange}
value={value}
/>
);
};
export default DepartmentSelect;
The code is working. I have removed a few irrelevant lines to make it shorter. Is there a way I can have the same functionality without storing the options and value in the state?
This question is very similar to
How can I work with just values in react-select onChange event and value prop?
In the above question, the options are passed in as a prop, so that they can get the selected prop without having to check if the options have been loaded from the server first. Would it be better to load the options separately and use a non-async select?
Edit
It would be nice if I could pass props.departmentId as the value prop to Async, but props.departmentId is a string. The value prop of Async requires one of the options, which are in the format
{
departmentId: string,
bill_cd: string,
name: string
}

Categories

Resources