Store checkbox values as array in React - reactjs

I have created the following demo to help me describe my question: https://codesandbox.io/s/dazzling-https-6ztj2
I have a form where I submit information and store it in a database. On another page, I retrieve this data, and set the checked property for the checkbox accordingly. This part works, in the demo this is represented by the dataFromAPI variable.
Now, the problem is that when I'd like to update the checkboxes, I get all sorts of errors and I don't know how to solve this. The ultimate goal is that I modify the form (either uncheck a checked box or vice versa) and send it off to the database - essentially this is an UPDATE operation, but again that's not visible in the demo.
Any suggestions?
Also note that I have simplified the demo, in the real app I'm working on I have multiple form elements and multiple values in the state.

I recommend you to work with an array of all the id's or whatever you want it to be your list's keys and "map" on the array like here https://reactjs.org/docs/lists-and-keys.html.
It also helps you to control each checkbox element as an item.

Neither your add or delete will work as it is.
Array.push returns the length of the new array, not the new array.
Array.splice returns a new array of the deleted items. And it mutates the original which you shouldn't do. We'll use filter instead.
Change your state setter to this:
// Since we are using the updater form of setState now, we need to persist the event.
e.persist();
setQuestion(prev => ({
...prev,
[e.target.name]: prev.topics.includes(e.target.value)
// Return false to remove the part of the array we don't want anymore
? prev.topics.filter((value) => value != e.target.value)
// Create a new array instead of mutating state
: [...prev.topics, e.target.value]
}));

As regard your example in the codesandbox you can get the expected result using the following snippet
//the idea here is if it exists then remove it otherwise add it to the array.
const handleChange = e => {
let x = data.topics.includes(e.target.value) ? data.topics.filter(item => item !== e.target.value): [...data.topics, e.target.value]
setQuestion({topics:x})
};
So you can get the idea and implement it in your actual application.
I noticed the problem with your code was that you changed the nature of question stored in state which makes it difficult to get the attribute topics when next react re-renders Also you were directly mutating the state. its best to alway use functional array manipulating methods are free from side effects like map, filter and reduce where possible.

Related

React doesn't rerender component despite state modification

I have a list of cards which I want to filter/sort, using 4 different select / input fields.
For 3 of the 4 filters, things work great. Cards are successfully filtered and sorted, and the filters can be combined with no problem.
On the first one though, I have to click it twice, or click it once then activate another filter, to see the changes in my cards.
I do not understand why this particular filter does not trigger the rerender of the cards because like all the others filters, its value is updated in the state, which looks like this :
const [filters, setFilters] = useState({
orderByFilter: null, // doesn't work the first time
titleFilter: null,
dateFilter: '',
searchFilter: '',
});
What I did to address the asynchronous state update is that I watch from changes in filters in a useEffect hook, to update the filtered data. Again, works fine for the other 3 filters.
useEffect(() => {
setFilteredData(filterCards(dataCards, filters));
// forceUpdate();
}, [filters]);
I am able to make it work for the 4 filters when I trigger a force update after setFilteredData :
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void;
But it feels like a hack to me and shows that I didn't understand what the real problem is.
Is there a way to make this work without this force update ?
PS : Edited the code to avoid indexation by search engines, as this is semi-sensitive data.
I think the issue here is that for the order filter you're using sort, which sorts the array in place, so in the end you get the same array, but with sorted values, which doesn't get picked up by React as a change, i.e. does not trigger a render. To fix this, when initialising the filter array, try using it's copy:
let filteredArray = [...valueListCards];
Also the approach of saving the filtered data to state is not optimal, as it results in these kinds of bugs, plus you need to keep the state in sync with the actual data. A better idea would be to derive the state, which is basically applying the filters at render.

React, conditional setState with the click of a button

So, I don't know if how I want this to work is possible at all, but in my head I think it could I just can't seem to get it to work..
Basically I have an empty array of objects:
this.state = {
orders: [{}]
}
Then when I click on a button I want an order to be added (custom event because cart has no parent-child relations to the button clicked, for reasons):
pubSub.on("addToCart", (data) =>
this.setState((prevState => ({
orders: [...prevState.orders, data.order]
})
);
This works. I end up with:
orders: [
{[sku]: sku, quantity: quantity}
]
Now the problem I am stuck with is that if an order is to be added with an SKU that is already in the array, I want just the quantity to be updated.
If the SKU isn't already found in the array, I want a new order added.
So I want to check the array for the existence of a key (key SKU is the same as the value of key SKU).
So, to check an array for the existence of a key I would use:
this.state.orders.map(obj =>{
if (!(data.sku in obj)){
**setState here with order added**
}
})
I know how the conditional should look like if it finds that the key exists, and then only update the quantity.
the problem I face is that it iterates over all the orders in the array, and every time the key doesn't exist, it adds a new order.
How could I make this, so it checks for the existence of a key, and only adds a new order 1 time if that's the case, and not every time?
I want actually to first check all the keys, and then decide what to do.
With this approach, I have to decide every round of the map what should be done.
Also keep in mind that we are in the scope of a custom event handler, where I want to use data.
inside this handler, I cannot assign variables outside the scope of the map function. So basically it's or I use just setState, or I use the map function and inside there the setState. I can't use them both next to each other as data is only available in the first method call.
This is also the first time I try to use this custom event handler thing, and the scoping has tied my hands a bit.
If this makes sense, and someone can point me in the right direction, that would be greatly appreciated.
Thanks in advance!
You can pass function in setState. This function will have current state and, if you use classes, current props. In this function you create state change - part of state object that you want to update. In your case it might look like this (it's barebones and dirty, to show how you can do it):
// data is here, somwhere
this.setState(function(state, props) {
let index = state.orders.findIndex(obj => data.sku in obj);
if (index > -1) {
// update existing
return {orders: state.orders.slice(0, index).concat(
{...state.orders[index], quantity: state.orders[index].quantitiy + 1},
state.orders.slice(index+1))}
} else {
// create new
return {orders: state.orders.concat(data)}
}
})

componentDidUpdate is firing constantly or never firing

So this is my code for component did update and this.props.getQuestions() is constantly firing. questions property is an array of questions which takes the values of the global state.
componentDidUpdate(prevProps){
if(prevProps.questions !== this.props.questions){
this.props.getQuestions();
}
}
Connecting state to props
const mapStateToProps = state => ({
questions: state.question.questions
});
When I try to compare the length of array between prevState and the current state, the method is never fired.
componentDidUpdate(prevProps){
if(prevProps.questions.length !== this.props.questions.length){
this.props.getQuestions();
}
}
How to make it work, so this method is fired only when the array is updated?
Simple array compare doesn't work. It will always match your condition ie. always not equal. And thus, componentDidUpdate fires constantly. Rather, you should check it like:
if(JSON.stringify(prevProps.questions) !== JSON.stringify(this.props.questions)){
this.props.getQuestions();
}
Regarding the checking with length property, it should work fine. But you stated that it isn't working. It seems you get same array when you use getQuestions. Check that function properly that it gets updated questions.
Your condition for fire was not right which you can see with your comparison of length of the array. You are checking for the equality of array's reference which is anyways incorrect(not sure how redux handles the prop update though).
You drill down to specificity of the questions in comparison and can compare questions' length, questions' questionId etc.. Loop through them and identify as desired.
You can even use library like lodash https://lodash.com/docs to compare array's and specific property.
If questions is the only property in mapStateToProps() you could just ensure you create a new array wherever you update it
newState.question.questions = [...updatedArray];
and remove the if statement from your componentDidUpdate entirely.
Make sure you do not update the questions array when calling getQuestions(), doing that can cause infinite loops where your didUpdate function causes an update (triggers didUpdate, ad infinitum)

Why "selected" not rendered here?

I am trying to understand how React works with the new Hooks implementation. In this example, I want the browsers to render selected items as I click on the rendered options. But as you can see, it doesn't work.
Here is the example: https://codesandbox.io/s/pjorxzyrx7
Do I have to use the useEffect in this case? Also, as I understand, useEffect couldn't render anything and only return functions. So, what am I missing here?
Thank you!
You're currently mutating the contents of the selected array instead of replacing it. React can't detect a state change when you do this.
Try something like the following:
const handleSelected = item => {
console.log(item);
console.log(selected);
setSelected([...selected, item]);
};
When updating arrays or objects as a part of a state, always make a new copy to assign so that React can properly know when to re-render.
Also, include relevant parts of your code directly in the question in the future, instead of hiding it behind a link (although including a runnable example is great!)

ReactTable setState on columns is not changing layout

first I should mention that I'm very new to react so this might be something silly..
I've struggled to get ReactTable's column to be sortable with the help of mattlockyer's gist
What I don't understand is I call setState on columns and then the render call is triggered correctly but it doesn't update the layout.
The render function "columns" contains an updated object with the correct column order, however render does not change the column order nor does sorting columns content.
This is the code in question,
this.setState({
columns: newColLayout,
}, () => {
this.mountEvents();
});
It will trigger a render, but the columns stay the same.
Ive put together a codepen with my current work and with comments about the code in question.
Any help would be greatly appreciated.
OK so i think i figured out whats going on.
The issue is splice modifies the state without triggering a change event, then when the event is fired from setStat, react doesn't see the change made to the column layout as it matches the current value.
The way i get around this is by cloning the column array using slice.
// Clone the current column state.
const columnClone = this.state.columns.slice(0);
// Remove item from array and stick it in a new position.
columnClone.splice(i, 0, columnClone.splice(this.draggedCol, 1)[0]);
Here is the updated code...
Still might be improved by someone who has more experience with react.

Resources