State is one change behind in select menu - reactjs

I have a dropdown menu that is used to select a State. It is used to display different data per state. I am console.log(), Whenever I go to select a state and the initial one is '' and when I change again, it seems to be one step behind. For example: My dropdown menu starts empty, I then select New York. It prints '', I then select Maine, It prints New York. I think I understand why it is happening but I cannot figure out how to fix it.
ProjectStatus.js
const [selectedState, setSelectedState] = useState('');
const handleChange = event => {
setSelectedState(event.target.value);
console.log(selectedState);
};
return (
<div className='project-status-nav'>
<h3>Project Status Page</h3>
<hr className='separator' />
<FormControl variant='filled' className={classes.formControl}>
<InputLabel htmlFor='selectState'>Select State</InputLabel>
<Select
id='selectState'
className='selectStyle'
value={selectedState}
onChange={handleChange}>
<MenuItem value={''}> </MenuItem>
<MenuItem value={'New York'}>New York</MenuItem>
<MenuItem value={'New Jersey'}>New Jersey</MenuItem>
<MenuItem value={'Colorado'}>Colorado</MenuItem>
<MenuItem value={'Florida'}>Florida</MenuItem>
<MenuItem value={'Maine'}>Maine</MenuItem>
</Select>
</FormControl>
</div>
);

That happens because state isn't immediatly updated. When the function is called, selectedState is declared and doesn't change untill the next call (update).
You can if you want observe selectedState changes with useEffect, or create an custom hook to do it like this:
// works like the React class setState, but the callback is called passing the new State
function useStateCallback(initialState) {
const [[state, cb], setState] = useState([initialState, null]);
useEffect(
() => {
cb && cb(state);
},
[state]
);
const setStateCallback = (value, callback) => setState([value, callback]);
return [state, setStateCallback];
}
function App() {
const [val, setVal] = useStateCallback(0);
const increment = () =>
setVal(val + 1, newVal => alert("Incremented: " + newVal));
}

setSelectedState is not synchronous, so when you log the value of selectedState just after calling setSelectedState, you get the current value, not the new one. That's why you're always one step behind

I faced the same problem and solved it like this.
const [breakwater, setBreakwater] = useState(props.breakwater);
useEffect( () => {
props.handleBreakwater(breakwater); //I updated the parent component's state in useEffect
},[breakwater]);
const handleprimaryBwType = (e) =>{
setBreakwater({...breakwater, primaryBwType : e.target.value});
//props.handleBreakwater(breakwater); when i updated here a selection was updating from behind
}

Related

React: why the input value is not displayed in the list after clicking on the "add"-button?

I'm trying out React and trying to make a simple component. Input and "Add" button.
I want get a list of values after filling in the input and clicking on the button. I can see that the state is getting filled, but I don't understand why the list is not being rerender.
Here is my code https://jsfiddle.net/3hkm2qnL/14/
`
const InputWithAddBtn = props => {
const [ value, setValue ] = React.useState('');
return (
<div>
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
<button onClick={() => props.add(value)}>+</button>
</div>
);
};
`
The problem is in the add() function, which by pushing onto the original array does not signal to the component to rerender.
const add = (value) => {
initValue.push(value)
console.log(initValue)
setValue(initValue)
}
One possible solution:
const add = (value) => {
const newValues = [...initValue, value]
console.log(newValues)
setValue(newValues)
}
That will trigger correctly the component to rerender.
For more info see https://stackoverflow.com/a/67354136/21048989
Cheers

React - Resetting children state when parent changes its state in functional components

I'm working with a list of notes in React Native, and I was using a bad-performant method to select/deselect the notes when I'm "edit mode". Everytime I selected a note, the application had to re-render the entire list everytime. If I do a test with 100 notes, I get input lags when I select/deselect a note, obviously.
So I decided to move the "select state" to the single Child component. By doing this, I'm having the re-render only on that component, so it's a huge improvement of performance. Until here, everything's normal.
The problem is when I'm disabling edit mode. If I select, for example, 3 notes, and I disable the "edit mode", those notes will remain selected (indeed also the style will persist). I'd like to reset the state of all the selected note, or finding a valid alternative.
I recreated the scene using React (not React Native) on CodeSandbox with a Parent and a Child: https://codesandbox.io/s/loving-field-bh0k9k
The behavior is exactly the same. I hope you can help me out. Thanks.
tl;dr:
Use-case:
Go in Edit Mode by selecting a note for .5s
Select 2/3 elements by clicking on them
Disable Edit Mode by selecting a note for .5s
Expectation: all elements get deselected (state of children resetted)
Reality: elements don't get deselected (state of children remains the same)
this is easy enough to do with a useEffect hook.
It allows you to "watch" variable changes over time.
When editMode changes the contents of the Effect hook runs, so when editMode goes from true to false, it will set the item's selected state.
Add this to your <Child /> component:
useEffect(() => {
if (!editMode) {
setSelected(false);
}
}, [editMode]);
If you use React.memo you can cache the Child components and prevent their re-renders.
const Parent = () => {
const [editMode, setEditMode] = useState(false);
const [childrenList, setChildrenList] = useState(INITIAL_LIST);
const [selected, setSelected] = useState([]);
const toggleEditMode = useCallback(() => {
if (editMode) {
setSelected([]);
}
setEditMode(!editMode);
}, [editMode]);
const deleteSelectedChildren = () => {
setChildrenList(childrenList.filter((x) => !selected.includes(x.id)));
setEditMode(false);
};
const onSelect = useCallback((id) => {
setSelected((prev) => {
if (prev.includes(id)) {
return prev.filter((x) => x !== id);
}
return [...prev, id];
});
}, []);
// Check when <Parent /> is re-rendered
console.log("Parent");
return (
<>
<h1>Long press an element to enable "Edit Mode"</h1>
<ul className="childrenWrapper">
{childrenList.map((content, index) => (
<Child
key={content.id}
index={index}
content={content}
editMode={editMode}
toggleEditMode={toggleEditMode}
onSelect={onSelect}
selected={selected.includes(content.id)}
/>
))}
</ul>
{editMode && (
<button onClick={deleteSelectedChildren}>DELETE SELECTED</button>
)}
</>
);
};
You have to wrap the functions you pass as props inside useCallback, otherwise they will be different on every Parent render, invalidating the memoization.
import { useRef, memo } from "react";
const Child = memo(
({ content, editMode, toggleEditMode, onSelect, selected }) => {
// Prevent re-rendering when setting timer thread
const timerRef = useRef();
// Toggle selection of the <Child /> and update selectedChildrenIndexes
const toggleCheckbox = () => {
if (!editMode) return;
onSelect(content.id);
};
// Toggle Edit mode after .5s of holding press on a Child component
const longPressStartHandler = () => {
timerRef.current = setTimeout(toggleEditMode, 500);
};
// Release setTimeout thread in case it's pressed less than .5s
const longPressReleaseHandler = () => {
clearTimeout(timerRef.current);
};
// Check when <Child /> is re-rendered
console.log("Child - " + content.id);
return (
<li
className={`childContent ${editMode && "editMode"} ${
selected && "selected"
}`}
onMouseDown={longPressStartHandler}
onMouseUp={longPressReleaseHandler}
onClick={toggleCheckbox}
>
<pre>
<code>{JSON.stringify(content)}</code>
</pre>
{editMode && (
<input type="checkbox" onChange={toggleCheckbox} checked={selected} />
)}
</li>
);
}
);
You can see a working example here.

get current useState values inside functions (w/ short, explicit codepen)

I understand (somewhat) how to use useEffect to update state, but I struggle with situations like when you need current state inside of another function, before the "nextTick" as it were.
Here is a simple Codepen with the exact issue. Make sure the Pen console is open.
https://codepen.io/kirkbross/pen/vYRNpqG?editors=1111
const App = () => {
const [state, setState] = React.useState(null);
// how can I make sure the below function knows what the current state really is?
const handleAppend = (state) => {
console.log("click");
console.log(state?.text + " foobar");
};
return (
<div class="app">
<div className="row">
<span>Text: </span>
<input
type="text"
onChange={() => setState({ text: e.target.value })}
/>
</div>
<div className="row">
<button onClick={handleAppend}>
Append "foobar" to text and log it to console
</button>
</div>
</div>
);
};
You're shadowing your state variable in your handleAppend function. You don't need to pass in an argument since state is available in scope of the component
const handleAppend = () => {
console.log("click");
console.log(state?.text + " foobar");
};
I did some changes. You dont need to use ur state as a parameter, since your textState lives inside your app component and there for you can reach it within your function.
Also, i changed the state and setState to textState, setTextState to make it less confusing. Also after clicking on the button and console logging, i cleared the textState so the next value wont be effected. Check it out below.
function App() {
const [textState, setTextState] = React.useState(null);
const handleAppend = () => {
console.log("click");
console.log(textState + " foobar");
setTextState('')
//also, you could make the input box clear after each press on button by adding value={textState} in the input.
};
return (
<div className="App">
<input
type="text"
onChange={(e) => setTextState(e.target.value)}
/>
<button onClick={handleAppend}>
Append "foobar" to text and log it to console
</button>
</div>
);
}
My real world case was more complicated than the Pen. The actual function needing state was a useCallback function and I had forgotten to add state to the dep array of the useCallback function.
const handleDragEnd = useCallback(
async (result) => {
const { source, destination, draggableId } = result;
console.log(state); // shows up now.
},
[state], // I had forgotten to add state to the useCallback dep array
);

React Hooks SetState Method isn't updating the state at all

I am using a Material UI Select Component to render a simple drop down menu, with its value as a state declares using the useState method.
const [collaboratingTeams, setCollaboratingTeams] = useState([])
The below code is of the Select Component, with its value and the corresponsing handler function in its onChange prop.
<Select
validators={["required"]}
errorMessages={["this field is required"]}
select
multiple
variant="outlined"
value={collaboratingTeams}
name="collaboratingTeams"
onChange={(e) => handleSelectCollaboratingTeams(e)}
helperText="Select Collaborating Teams "
>
{arrTeams.map((option, index) => (
<MenuItem
key={option.teamId}
value={option.teamId}
variant="outlined"
>
<Checkbox
checked={collaboratingTeams.indexOf(option.teamId) !== -1}
/>
<ListItemText primary={option.teamValue} />
</MenuItem>
))}
</Select>
The below code is the function that triggers when a drop down data is changed.
This function sets the state, which should then technically update the Select's selected options.
const handleSelectCollaboratingTeams =(e)=>{
setCollaboratingTeams(e.target.value)
}
The issue is, the setCollaboratingTeams method isn't updating the state only. I understand that the setstate method in hooks works so coz of its asynchronous nature but at some point it should display up right. Don't understand where I'm going wrong.
I expect the collaboratingTeams array to be updated with a new value when a new value is selected by the user.
you should define the new state for storing the selected item.
Example for class component:
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState({ selectedOption });
};
Example for functional component(using React-hook):
const [selectedOption, setSelectedOption] = useState(null);
handleChange = selectedOption => {
setSelectedOption(selectedOption);
};
dont use arrow function with onchange it often used when we need to pass id or some other data

fire an onChange event while setting the initial value

I am trying to build a select field which will "select" an Entry automatically when there is only one entry in the list. My Problem is, that there will no onChange event triggered, when setting the value in the list. Is there any possibility to send these event programatically?
This is my code so far
export const SelectField = function(props) {
const classes = useStyles();
const {t} = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(undefined);
if (selectedValue === undefined && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0]);
//need to fire an event in this case
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""});
}
return (
<Select
value={selectedValue}
onChange={props.onChange()}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};
There is an important difference in passing a function as a prop and calling a function.
onChange={props.onChange()}
Will call that function every render.
onChange={props.onChange}
Will pass that function to be called by the component.
For one is the mentioned onChange() to onChange, but that is not all.
It seems the issue is connected to code outside of the provided component. One indicator of an issue is the following:
....
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""}); // This line could be faulty
}
....
I don't know what kind of value menuEntries is, but it should be a state somewhere higher in your component tree. You should also pass in the setter. Probably called something like setMenuEntries. Then call that setter in instead of mutate the prop.
setMenuEntries([...menuEntries, {"name":t("select"), "value":""}])
Only when setting a state a rerender is triggered.
In general, updating props of any function is considered a bad-practise when following the principle of immutability. Eslint can help you with signifying patterns like that.
From the looks of your code it seems to be alright, there is however one thing that might cause your problem.
return (
<Select
value={selectedValue}
onChange={props.onChange} <---
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
I changed how you set the onchange function. You did it while also calling the actual function. This will make the function fire when the component renders and not on the change of your controlled value for this select.
I have solved my problem by calling he handler by my own, if i set the initial value:
export const SelectField = function(props) {
const classes = useStyles();
const { t } = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(props.value);
// on startup load test cases
React.useEffect(() => {
if (selectedValue === props.value && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0].value);
props.onChange(props.menuEntries[0].value);
}
}, [selectedValue]);
const updateValue = function(e) {
props.onChange(e.target.value);
setSelectedValue(e.target.value);
};
return (
<Select
value={selectedValue}
onChange={updateValue}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{
(props.menuEntries.length !== 1 && props.addSelectEntry) && (
<MenuItem key={t("select")} value={t("select")}>
{t("select")}
</MenuItem>
)
}
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};

Resources