A simple inputfield to update the state.
If you input 5 and click 'Pledge', the 'before' log show 0, but the 'after' log also show 0. If you click the button again then it shows 5, and it continues like that. Like it's always on step behind.
Why is that?
App.js
state = {
fundGoal: 1000,
amountFunded: 0
}
getAmount = amount => {
console.log('before', this.state.amountFunded)
const parsedAmount = parseInt(amount)
this.setState(prevState => ({
amountFunded: prevState.amountFunded + parsedAmount
}))
console.log('after', this.state.amountFunded)
}
Form in another component
getAmount = e => {
e.preventDefault()
const value = e.target.elements.amount.value
this.props.getAmount(value)
e.target.elements.amount.value = ''
}
The setState() function runs asynchronously so if you want to see the updated state after setState() has finished running you can pass a callback function to setState as follows
getAmount = amount => {
console.log('before', this.state.amountFunded)
const parsedAmount = parseInt(amount)
this.setState(prevState => ({
amountFunded: prevState.amountFunded + parsedAmount
}), () => console.log('after', this.state.amountFunded));
}
Because setState is an asynchronous call. There is a callback after the transaction is done.
this.setState(prevState => ({
amountFunded: prevState.amountFunded + parsedAmount
}), () => console.log('after', this.state.amountFunded))
Related
I am making a kind of input in react.
I have the following
=> A component returning a onSelectionChange that return an array of selected element (works fine)
useEffect(() => {
onSelectionChange?.(selection.items);
}, [selection]);
=> A input that wrap the above component and contain a button save/cancel. When use click save, I want to dispatch a onChange with the latest value selected. If user click cancel, it will reset to initial value. ( I intentionally removed some of the component code to keep the problematic parts)
export const VariantsInput = forwardRef<any, any>(
({ value = [], ...rest }, ref) => {
//....
const [selectedProducts, setSelectedProducts] = useState<VariantSetDetail[]>(value);
const onValidate = useCallback(() => {
console.log(selectedProducts); // always the previous state of selectedProducts
onChange(selectedProducts);
closeDialog(dialogId);
}, [selectedProducts]);
useEffect(() => {
console.log(selectedProducts); // always the latest state of selectedProducts
}, [selectedProducts]);
//...
<VariantSelector
selectedSku={value}
onSelectionChange={(selection) => {
setSelectedProducts(() => [
...selection.map((selected) => {
const alreadySelected = value.find(
(product) =>
product.productVariantId === selected.productVariantId
);
return alreadySelected ? alreadySelected : selected;
}),
]);
}}
/>
<button onClick={onValidate}> Save</button>
//....
The issue is selectedProducts is always the previous state in the onValidate. If I check all my events onSelectionChange, or put log inside the effect and the root of the component to display the state, it contains exactly what I want. But in the on validate, it is always []....
I am really confused as to why.
I have this component which creates row data for a table. It sets state. It also creates a button for one of the row states, which contains a reference to a function. I want the state to be available to that button. But it is one request behind. The state available in the showJourneyDetails function is always the previous state to the current state. I know react state is asynchronous so how do I make the current state available inside the showJourneyDetails function?
const [suspected, setSuspectedData] = useState([]);
const [suspectedReads, setSuspectedReads] = useState(false);
const findVehiclesOfInterestBetweenDateRange = () => {
fetch(`http://127.0.0.1:8000/vehicles`)
.then((response) => response.json())
.then((vehicles) => {
const suspectedDataReads = vehicles.map((row) => {
return {
journey_id: row.journey_id,
reads: row.reads,
};
});
setSuspectedReads(suspectedDataReads);
const suspected = vehicles.map((row) => {
return {
vrm: row.vrm,
start_time: row.start_time,
end_time: row.end_time,
journey_size: row.journey_size,
journey_time: row.journey_time,
score: row.score,
details: (<Button
variant="contained"
onClick={showJourneyDetails}
id={row.journey_id}
>
Details
</Button>),
};
});
setSuspectedData(suspected);
})
.catch((error) => {
console.log(error);
});
};
const showJourneyDetails = (event) => {
console.log(event.target.id);
console.log("showJourneyDetails suspectedDataReads:", suspectedDataReads);
}
I'm working on a hotel feature where the user can filter through and display the corresponding rooms available, however when I set the onClick to update the filters and display the filtered rooms, the rooms display correctly after the second click and there after.
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
filterRooms();
};
const filterRooms = () => {
....
....
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useState() (and class component's this.setState()) are asynchronous, so your second state updater won't have an up to date value for filtered_rooms when it runs.
Rather than:
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
const someHandler = e => {
setSomeState(...);
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
};
You need to setSomeOtherState within a useEffect hook, and ensure to mark some_state as a dependency.
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
useEffect(() => {
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
}, [some_state]);
const someHandler = e => {
setSomeState(...);
};
It is hard to give an suggestion for your code since it is fairly edited, but it'd probably look like this:
const filterRooms = () => {
// ...
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useEffect(() => {
filterRooms();
}, [filtered_rooms]);
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
};
See this codepen for a simple (albeit a bit contrived) example.
I have a checklist object state
const [checkList, setCheckList] = useState({checkA, checkB .. 100 checks})
How to update all states to e.target.checked at once? I should be able to update like this:
const handleAllCheck = (e) => {
Object.keys(checkList).map((resource) => {
setCheckList({ ...checkList, [resource]: e.target.checked });
});
};
But this updates only one of 100 checks, What am I missing?
Issues
You are enqueueing a bunch of state updates in a loop but not using a functional state update, so the same state from the current render cycle the updates are enqueued in is used for each update, overwriting the previous update. The last enqueued update wins and is the state set for the next render cycle.
const handleAllCheck = (e) => {
Object.keys(checkList).map((resource) => {
setCheckList({
...checkList, // <-- state from the previous render cycle
[resource]: e.target.checked,
});
});
};
Solution
In order to update all the checked values you can simply forEach over the keys and use a functional state update to spread in the state from the previous enqueued update.
const handleAllCheck = (e) => {
const { checked } = e.target;
Object.keys(checkList).forEach((resource) => {
setCheckList(checkList => ({
...checkList, // <-- state from the previous update
[resource]: checked,
}));
});
};
Alternatively you can use a single update by reducing the old state into a new state object.
const handleAllCheck = (e) => {
const { checked } = e.target;
setCheckList(checkList => Object.keys(checkList).reduce(
(checkList, resource) => ({
...checkList,
[resource]: checked,
}),
{},
));
};
can you try
const handleAllCheck = (e) => {
const newCheckList = Object.keys(checkList).map((resource) => {
return e.target.checked;
});
setCheckList(newCheckList);
I was trying to implement a simple paint component with React hooks. My expected behavior was 'mouseMove' to be executed when I moved my mouse while remaining clicked. However, state.isMouseDown always returned false within mouseMove().
Any fixes or references to potentially helpful documents would be grateful.
const initialState = {
isMouseDown: false,
isMouseMoving: false
};
const DrawingCanvas = () => {
const [state, setState] = useState(initialState);
useEffect(() => {
console.log('mounted');
document.addEventListener('mousedown', () => mouseDown());
document.addEventListener('mousemove', () => mouseMove());
}, []);
const mouseDown = () => {
console.log('mousedown');
setState(state => ({
...state,
isMouseDown: true
}));
};
const mouseMove = () => {
// why is this false even when click and move?
console.log('mouseMove:isMouseDown', state.isMouseDown);
if (!state.isMouseDown) return;
console.log('mousemove'); // this line is not being executed
setState(state => ({
...state,
isMouseMoving: true
}));
};
console.log(state);
return (
<div>
<p>mouseDown: {`${state.isMouseDown}`}</p>
<p>mouseMoving: {`${state.isMouseMoving}`}</p>
</div>
);
};
As explained in this related answer, the problem is that event listener accesses state object from the scope where it was defined, i.e. initial state, because event is listened on component mount.
A solution is to either use mutable state, or access state exclusively from state updater function. In the code above, state.isMouseDown refers to original state. In case it's needed to avoid state updates, state updater can return original state:
const mouseMove = () => {
setState(state => {
if (!state.isMouseDown)
return state; // skip state update
else
return {
...state,
isMouseMoving: true
};
});
};