parent check but children should not checked - javascript

I am using react checkbox tree package. I have a treeview with checkbox as below.
const nodes = [
{
value: "mars",
label: "Mars",
children: [
{
value: "phobos",
label: "Phobos"
},
{ value: "deimos", label: "Deimos" }
]
},
{
value: "saturn",
label: "Satrun"
},
{
value: "jupitor",
label: "Jupitor"
}
];
function Widget() {
const [checked, setChecked] = useState([]);
const [expanded, setExpanded] = useState([]);
const updateCheckedState = (node) => {
const childValues = [];
const isParent = node.isParent;
const updatedValues = isParent ? childValues : [node.value];
if (node.checked) {
setChecked([...updatedValues]);
} else {
const filteredChecks = checked.filter((check) => {
return !updatedValues.includes(check);
});
setChecked(filteredChecks);
}
};
return (
<CheckboxTree
iconsClass="fa5"
nodes={nodes}
checked={checked}
expanded={expanded}
onCheck={(nodes, node) => {
updateCheckedState(node);
}}
onExpand={(expanded) => setExpanded(expanded)}
/>
);
}
Full example is here
My issue is when I clicked checkbox with children it doesn't checked(example Mars). But I clicked no children element then it checked. Please help me to fix this.
If parent checked, children should not checked

checked is the array of node values, so you will need to assign the children's values.
const updatedValues = isParent
? node.children.map((v) => v.value)
: [node.value];

Let me assist you. First apply ternary operator for childvalues
const childValues = node.children ? node.children.map(child => child.value) : [];
another change you should do is to add spread operator checked In argument of setChecked like this
if(node.checked) {
setChecked([...checked, ...updatedValued]);
}
This way when you will click checkbox with children it doesn't checked. clicking no children element but it checked. issue will also be resolved :)
and one one more thing you have also put wrong labels in node array
value: "saturn",
label: "Satrun"
should be
value: "saturn",
label: "saturn"
And same goes for Jupiter.

Related

How to update the array of objects using onChange handler in React JS?

I have the below array of objects:
const [rows, setRows] = useState([
{id: 1, key: "key1", value: "value1"},
{id: 2, key: "key2", value: "value2"}
]);
And I have the below inputs as well:
<TextField name="key" onChange={(e)=> handleTable(e, record.id)} value{rows.filter...}/>
<TextField name="value" onChange={(e)=> handleTable(e, record.id)} value{rows.filter...}/>
Now I know that for handling the above inputs I should loop to find the appropriate object based on its ID then try to update it, I need one another for loop for the value of the above inputs as well, but that takes a long time in terms of hooks and reloading each time the user enters something, how can I handle the above situation, both updating and showing the appropriate item in the array?
Yes, you need to loop the textfields and pass the index to the change handler.
const [rows, setRows] = React.useState([
{ id: 1, key: "key1", value: "value1" },
{ id: 2, key: "key2", value: "value2" }
]);
const handleChange = (e,idx) => {
clone = [...rows];
let obj = clone[idx];
obj.value = e.target.value;
clone[idx] = obj;
setRows([...clone])
}
and Then you need to loop your rows with text field.
{ rows?.map((row, index) =>
<TextField value={rows[index]?.value} onChange={(e) =>
handleChange(e,index)} />
)}
This may help you to tweak your solution.
const [rows, setRows] = useState([
{ id: 1, key: "key1", value: "value1" },
{ id: 2, key: "key2", value: "value2" }
]);
const handleTable = (e, id) => {
const newRows = [...rows]; //spread the rows array into a new array
const index = rows.find((item, i) => {
if (item.id === id) return i;
}); //found the index using the id
if (e.target.name === "key") {
newRows[index].key = e.target.value; // update using the index
} else {
newRows[index].value = e.target.value;
}
setRows(() => [...newRows]);
};
<TextField name="key" onChange={(e)=> handleTable(e, record.id)} value{rows.filter...}/>
<TextField name="value" onChange={(e)=> handleTable(e, record.id)} value{rows.filter...}/>
if there is a better way plz edit
const [value , setValue]=useState([])
in html :
*need separate state for each input elements
<input value={value[key] ? value[key] : ""} onChange={(e) => handleSetValue(e.target.value, key)}/>
set value func() :
function handleSetValue(e, key) {
setValue(s => {
const newArr = s.slice();
newArr[key] = e;
return newArr;
});
}

Multi React-Select not setting value

I am trying to create a searchable dropdown that allows users to select only 2 genres which are then added to genreName. I currently have it working but the values will not set or all the values set ?
I have it working with another set of code but I couldn't add the search feature for the material ui select.
Anyone have any idea how to fix this ?
const genres = [
{ value: 'acoustic', label: 'acoustic' },
{ value: 'afrobeat', label: 'afrobeat' },
{ value: 'alt-rock', label: 'alt-rock' },
{ value: 'alternative', label: 'alternative' },
{ value: 'brazil', label: 'brazil' },
{ value: 'breakbeat', label: 'breakbeat' },
]
const AddGenre = ({ }) => {
const [ariaFocusMessage, setAriaFocusMessage] = useState('');
const [isMenuOpen, setIsMenuOpen] = useState(false);
const onFocus = ({ focused, isDisabled }) => {
const msg = `You are currently focused on option ${focused.label}${isDisabled ? ', disabled' : ''
}`;
setAriaFocusMessage(msg);
return msg;
};
const onMenuOpen = () => setIsMenuOpen(true);
const onMenuClose = () => setIsMenuOpen(false);
//trying to set the values here
const [genreName, setGenreName] = useState([]);
const handleInputChange = (value, e) => {
if (e.action === 'input-change') {
setGenreName(value);
console.log(genreName)
}
}
return (
<div className="bodyComp">
<form>
<label style={style.label} id="aria-label" htmlFor="aria-example-input">
Select a Genre
</label>
<Select
isMulti
aria-labelledby="aria-label"
ariaLiveMessages={{
onFocus,
}}
onInputChange={handleInputChange}
options={genres}
//if I set the value as genres all values are set ?
value={genres}
inputId="aria-example-input"
name="aria-live-color"
onMenuOpen={onMenuOpen}
onMenuClose={onMenuClose}
/>
</form>
</div>
)
};
export default AddGenre

How to filter an array and add values to a state

I have the current state as:
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
I map through the state and display the data in a div and call a onClicked function to toggle the isChecked value on click:
const clickData = index => {
const newDatas = [...data];
newDatas[index].isChecked = !newDatas[index].isChecked;
setData(newDatas);
const newSelected = [...selected];
const temp = datas.filter(isChecked==true) // incomplete code, struggling here.
const temp = datas.isChecked ?
};
I have another empty state called clicked:
const[clicked, setClicked] = setState([]). I want to add all the objected whose isChecked is true from the datas array to this array. How can I do this?
I just add checkBox & onChange event instead of using div & onClick event for your understanding
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
const [clicked, setClicked] = useState([]);
const clickData = index => {
let tempData = data.map(res => {
if (res.id !== index) {
return res;
}
res.isChecked = !res.isChecked;
return res;
});
setClicked(tempData.filter(res => res.isChecked));
};
useEffect(() => {
setClicked(data.filter(res => res.isChecked));
}, []);
return (
<div>
{data.map((res, i) => (
<div key={i}>
<input
type="checkbox"
checked={res.isChecked}
key={i}
onChange={() => {
clickData(res.id);
}}
/>
<label>{res.name}</label>
</div>
))}
{clicked.map(({ name }, i) => (
<p key={i}>{name}</p>
))}
</div>
);
}
https://stackblitz.com/edit/react-y4fdzm?file=src/App.js
Supposing you're iterating through your data in a similar fashion:
{data.map((obj, index) => <div key={index} onClick={handleClick}>{obj.name}</div>}
You can add a data attribute where you assign the checked value for that element, so something like this:
{data.map((obj, index) => <div key={index} data-checked={obj.isChecked} data-index={index} onClick={handleClick}>{obj.name}</div>}
From this, you can now update your isClicked state when the handleClick function gets called, as such:
const handleClick = (event) => {
event.preventDefault()
const checked = event.target.getAttribute("data-checked")
const index = event.target.getAttribute("data-index")
// everytime one of the elements get clicked, it gets added to isClicked array state if true
If (checked) {
let tempArr = [ ...isClicked ]
tempArr[index] = checked
setClicked(tempArr)
}
}
That will let you add the items to your array one by one whenever they get clicked, but if you want all your truthy values to be added in a single click, then you simply need to write your handleClick as followed:
const handleClick = (event) => {
event.preventDefault()
// filter data objects selecting only the ones with isChecked property on true
setClicked(data.filter(obj => obj.isChecked))
}
My apologies in case the indentation is a bit off as I've been typing from the phone. Hope this helps!

How to update state with usestate in an array of objects?

I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};

Toggle item in an array react

I want to toggle a property of an object in an array. The array looks as follows. This is being used in a react component and When a user clicks on a button I want to toggle the winner.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
My react code looks something like this
function Parent = () => {
const [fixtures, setUpdateFixtures] = useState(initialFixtures)
const toggleWinner = (index) => {
const updatedFixtures = fixtures.map((fixture, i) => {
if (i === index) {
return {
...fixture,
winner: fixture.winner === home ? away : home,
};
} else {
return fixture;
}
})
setUpdateFixtures(updatedFixtures);
}
return <Fixtures fixtures={fixtures} toggleWinner={toggleWinner} />;
}
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => toggleWinner(index)}> Change Winner</button>
</div>
))
}
the code works but it feels like it is a bit too much. I am sure there is a better more succinct way of doing this. Can anyone advise? I do need to pass the fixtures in from the parent of the Fixture component for architectural reasons.
const updatedFixtures = [...fixtures];
const fixture = updatedFixtures[i];
updatedFixtures[i] = {
...fixture,
winner: fixture.winner === fixture.teams.home ? fixture.teams.away : fixture.teams.home,
};
You can slice the fixtures array into three parts:
from 0 to index: fixtures.slice(0, index). This part is moved to the new array intact.
The single item at index. This part/item is thrown away because of being changed and a new item is substituted.
The rest of the array: fixtures.slice(index + 1).
Next, put them into a new array:
const newFixtures = [
...fixtures.slice(0, index), // part 1
{/* new item at 'index' */}, // part 2
...fixtures.slice(index + 1) // part 3
];
To construct the new item:
Using spread operator:
const newFixture = {
...oldFixture,
winner: /* new value */
};
Using Object.assign:
const newFixture = Object.assign({}, oldFixture, {
winner: /* new value */
});
if you write your code in such a way - this will do the job.
const toggleWinner = index => {
const { winner, teams: { home, away } } = fixtures[index];
fixtures[index].winner = winner === home ? away : home;
setUpdateFixtures([...fixtures]);
};
Setting a new array of fixtures to state is completely enough to trigger render on Fixtures component.
I have made a working example for you.
You can use libraries like immer to update nested states easily.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
const newState = immer.default(initialFixtures, draft => {
draft[1].winner = "something";
});
console.log(newState);
<script src="https://cdn.jsdelivr.net/npm/immer#1.0.1/dist/immer.umd.js"></script>
If you are comfortable to use a class based approach, you can try something like this:
Create a class that holds property value for team.
Create a boolean property in this class, say isHomeWinner. This property will decide the winner.
Then create a getter property winner which will lookup this.isHomeWinner and will give necessary value.
This will enable you to have a clean toggle function: this.isHomeWinner = !this.isHomeWinner.
You can also write your toggleWinner as:
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
This looks clean and declarative. Note, if immutability is necessary then only this is required. If you are comfortable with mutating values, just pass fixture.toggle to your react component. You may need to bind context, but that should work as well.
So it would look something like:
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => fixture.toggle() }> Change Winner</button>
// or
// <button onClick = { fixture.toggle.bind(fixture) }> Change Winner</button>
</div>
))
}
Following is a sample of class and its use:
class Fixtures {
constructor(home, away, isHomeWinner) {
this.team = {
home,
away
};
this.isHomeWinner = isHomeWinner === undefined ? true : isHomeWinner;
}
get winner() {
return this.isHomeWinner ? this.team.home : this.team.away;
}
toggle() {
this.isHomeWinner = !this.isHomeWinner
}
}
let initialFixtures = [
new Fixtures('Liverpool', 'Manchester Utd'),
new Fixtures('Chelsea', 'Fulham', false),
new Fixtures('Arsenal', 'Tottenham'),
];
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
initialFixtures.forEach((fixture) => console.log(fixture.winner))
console.log('----------------')
initialFixtures = toggleWinner(1);
initialFixtures.forEach((fixture) => console.log(fixture.winner))
initialFixtures = toggleWinner(2);
console.log('----------------')
initialFixtures.forEach((fixture) => console.log(fixture.winner))
const toggleWinner = (index) => {
let updatedFixtures = [...fixtures].splice(index, 1, {...fixtures[index],
winner: fixtures[index].winner === fixtures[index].teams.home
? fixtures[index].teams.away : fixtures[index].teams.home})
setUpdateFixtures(updatedFixtures);
}

Categories