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);
Related
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'm converting a class component to functional component for practice. It has a ref object to contain some variables for the component, such as IntersectionObserver object to implement infinite scrolling.
The issue starts from here. The callback function of the IntersectionObserver calls a function(says update) defined in the component to load more data. Because the IntersectionObserver is defined inside the useRef, the update function is the function bound when the component gets initialized. So the value of the state that is used in the update function is also the value of the initial state.
How can I compose this functional component in a proper way?
Backbone demo
export default function A(props) {
const [state, setState] = useState({
pageNo: 1,
isLoading: false,
items: []
});
const update = useCallback(() => {
setState(state => ({...state, isLoading: true}));
someApi(state.pageNo);
setState(state => ({
...state,
pageNo: pageNo + 1
}));
setState(state => ({...state, isLoading: false}));
}, [isLoading, pageNo]);
const observerCallback = useCallback((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
update();
}
}
}, [update]);
const observer = useRef(new IntersectionObserver(observerCallback)); // The callback is the function binding the update function that binds some of the initial state
const lastEl = useRef(null);
const preLastEl = useRef(null);
useEffect(() => {
update();
}, [props]);
if (lastEl.current && lastEl.current != preLastEl.current) {
preLastEl.current = lastEl.current;
observer.observe(lastEl.current);
}
return (
<SomeProgressBar style={{ display: state.isLoading ? "" : "none" }}/>
{
state.items.map((item) => <B ... ref={lastEl}/>)
}
);
}
I don't exactly why you're using the ref and why you can't do it differently. so in case you have to do it this way, your refs are dependent to state object and they need to be changed when the state are changed so you should use a useEffect to change the refs based on new state. try to implement one of these two steps:
1
const refs = useRef({
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
});
useEffect(() => {
update(state.pageNo);
}, [props]);
function update(pageNo = 1) {
setState(prev => ({...prev, isLoading: true}));
someApi(pageNo); // state.pageNo will be always 1
setState(prev => ({...prev, isLoading: false}));
}
2 in case above code didn't work try this
useEffect(() => {
if(state.pageNo){
refs.current = {
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
}
}
}, [state.pageNo])
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))
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
};
});
};