How to save multi-selected options into an array within React state - reactjs

I'm trying to save both team members in select into an array within a state object. My expectation would be to be able to ctrl-click the options and this would update state (using the on change handler). For right now I have this, which only ends up saving the first option in state:
setProjectDetails((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
I'm working with a mongoose/mongodb backend, so the id's are associated with the team members. I'm looking for the state to end up like this:
const initialProjectDetails = {
title: "test",
description: "test",
teamMembers: ["628c0133e5edf7b21cd8f31b", "628c0133e5edf7b21cd8f31b"], };
Form:

I think you forgot to update the desired key in the object (teamMembers):
setProjectDetails((prevState) => ({ ...prevState, teamMembers: e.target.value, }));

Related

React - Set state object using args

I have an object saved in a state I am trying to create a function to update parts of the object.
function removeError(val) {
setPersonsState((prevState) => ({
...prevState,
val: "",
}));
}
I have tried to pass val to the object key to update it.
onClick={removeError('Bob')}
Where Bob replaces val to update in the object. Sorry if terminology is bad.
Well, you should change it to be in the following way, sine you don't want to change a key named val, but the key with name equal to the value of val
function removeError(val) {
setPersonsState((prevState) => ({
...prevState,
[val]: "",
}));
}
And, as suggested in the comments by #RoshanKanwar, also the click handler is not correct, it should be
onClick = {() => removeError('Bob')}

How do I change the state of an array of objects in React?

I'm trying to change the state of an array containing an object whenever something is typed inside of an input. The state of the array I'm trying to change looks like this:
const defaultCV = {
personalInfo: {
firstName: "",
lastName: "",
title: "",
about: "",
},
education: [
{
id: uniqid(),
university: "",
city: "",
degree: "",
subject: "",
from: "",
to: "",
},
],
Specifically, I want to change the state of the 'education' section. My current, non-working code looks like this:
const handleEducationChange = (e) => {
setCV((prevState) => ({
...prevState,
education: [
{
...prevState.education,
[e.target.id]: e.target.value,
},
],
}));
};
When I type in the input and the function is triggered, I get the error "Warning: Each child in a list should have a unique "key" prop." I've been trying to make this work for the past few hours, any help as to what I'm doing wrong would be appreciated.
Are you using the Array.map() method to render a list of components? That is a common cause of that error. For example if you are mapping the education array.
You can fix by using the object id as the key since that is already generated for each object:
defaultCV.education.map(institution => {
return <Component key={institution.id} institution={institution} />
}
You are destructuring an array in to an object that will not work
education: [{ // this is the object you are trying to restructure into
...prevState.education, // this here is an array in your state
[e.target.id]: e.target.value,
}, ],
}));
Suppose you render this input field:
<input id='0' type='text' name='university' value={props.value} />
Your event.target object will include these props:
{id = '0', name = 'university', value = 'some input string'}
When updating the state, you have to first find the array item (object), that has this id prop, then you can change its 'name' property and return the new state object.
This worked for me:
setCV(prevState => {
const eduObjIdx = prevState.education.findIndex(obj => obj.id === +e.target.id)
prevState.education[eduObjIdx][e.target.name] = e.target.value
return {
...prevState,
education: [
...prevState.education.splice(0, eduObjIdx),
prevState.education[eduObjIdx],
...prevState.education.splice(eduObjIdx + 1),
],
}
})
Make sure you send the current state of the input value when rendering the component:
<Component value={state.education[currentId].id} />
where currentId is the id of the university you are rendering.
If you render it mapping an array, don't forget the key (answered at 0), otherwise you'll get the above error message.
This way you don't mutate the whole education array. May not be the best solution though.

React Drag-and-Drop with useState is overwriting previous state

I'm working on a page builder of sorts for my product, and I'm using react-dnd to accomplish what I need. So far I've gotten the basic drag-and-drop functionality taken care of, and I'm able to add a single item to my page, but I'm hitting a snag.
When I drag a new item to my drop zone, the expected behavior is that it will add a new item to the existing array that I've got stored in a nested object in state but, instead when I drag-and-drop another item to it it is overwriting the previous item, only allowing my to have a single item in my page. I have been beating my head against the wall all day. Below is my code; please comment if you need more and I will do my best to clarify.
So, here is my useState. I have a couple other items in here, but they're unimportant. All that matters is the content array:
const [lessonContent, setLessonContent] = useState({
title: '',
courseType: '',
content: [],
});
My function to add an element to lessonContent.content which is passed to the useDrop hook:
const handleAddLessonElement = (element: Record<string, unknown>) => {
setLessonContent({
...lessonContent,
content: [...lessonContent.content, element],
});
};
const [{ isOver }, drop] = useDrop(() => ({
accept: 'card',
drop: (item) => handleAddLessonElement(item),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
The useDrag hook:
const [{ isDragging }, drag] = useDrag(() => ({
type: 'card',
item: {
type: element.value,
value: '',
config: { label: element.label },
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}));
Also, I'm using the <DNDProvider> with HTML5Backend, and I've got it wrapping all components that are utilizing tools from react-dnd. As I said the basics are working, but I seem to be messing something up in adding to state. I just cannot, for the life of me, find what it is.
Thanks in advance!
Ended up figuring out, turns out it was a solution I had tried but hadn't implemented correctly.
My problem is that I was not tracking the array as it updated, so I was constantly overwriting what I had just done. So, all it took was updating my handleAddLessonElement function to track the updated state before adding an element to the array.
const handleAddLessonElement = (element: Record<string, unknown>) => {
setLessonContent((previousState: Record<string, any>) => ({
...previousState,
content: [...previousState.content, element],
}));
};
As you can see, I added the previousState which will track the newly update state as I add elements, then use the spread operator to add these new elements to the copied array.

what is the correct syntax to update react hook state of Objects

I have a list of 100 checklists stored in
const [checkList, setCheckList] = React.useState({"checkA": false, "checkB": false....100 items})
in the onClick, I do
setCheckList({
...checkList,
"checkA": event.target.checked,
});
this works fine, But when I am looping using .map((item) => {...})
setCheckList({
...checkList,
item: event.target.checked,
});
creates 101th checkbox called "item"
What am I missing?
That's because you're using 'item' as a property name and you should put the item in brackets like this:
setCheckList({
...checkList,
[item]: event.target.checked,
});
a common approach when updating a state of input objects would pass event.target.name as key to your update state. you should also at your input declare a name attribute with proper matching key:
setCheckList({
...checkList,
[event.target.name]: event.target.checked,
});

Clearing inputs in React

I have a component in which I create a new post. I have a state used to configure and create a form. The main object in the state is called formControls and inside each element looks something like this:
title: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Title"
},
value: "",
validation: {
required: true
},
valid: false,
isTouched: false
}
Then I have a submit handler in which I create a new post and I try to clear the inputs. I m using 2 way binding so I try to clear by looping through state, make copies and update the values for each elements in the formControls : title, author and content like this:
for (let key in this.state.formControls) {
const updatedState = { ...this.state.formControls };
const updatedInput = { ...this.state.formControls[key] };
updatedInput.value = "";
updatedState[key] = updatedInput;
console.log(updatedState);
this.setState({
formControls: updatedState
});
}
The things is that it only clears the last element in the form (textarea). I console logged updatedState and in each iteration it clears the current input but in the next iteration the previous cleared input has again the value before clearing so only the last element is cleared in the end. If i move const updatedState = { ...this.state.formControls };
outside the for loop is behaves as it should. Does this happen because of async operation of setState() and it doesn t give me the right previous state when I try to update in each iteration?
I was hoping that maybe someone could help me understand why is like this. I would post more code but is quite long.
The data available to you in the closure is stale after the first call to setState. All iterations in the for .. in block will be run with the "old" data, so your last iteration is the one which is actually setting the fields to the values as they were when the for .. in loop began.
Try calling setState only once and put all your required changes into that.
const updatedFormControls = { ...this.state.formControls };
for (let key in this.state.formControls) {
const updatedInput = { ...updatedFormControls[key] };
updatedInput.value = "";
updatedFormControls[key] = updatedInput;
}
console.log(updatedFormControls);
this.setState({
formControls: updatedFormControls
});
Another way to do the same thing might look like this:
this.setState(state => ({
...state,
formControls: Object.keys(state.formControls).reduce(
(acc, key) => ({
...acc,
[key]: { ...state.formControls[key], value: '' }
}),
{}
)
});

Resources