I have an array of objects and I am trying to group the objects by type, like this:
const el = {
Type1: [
{
param1: 1,
param2: value1,
position: 1,
type: 'Type1'
}, {
param1: 2,
param2: value2,
position: 2,
type: 'Type1'
}
],
Type2: [
{
param1: 3,
param2: value3,
position: 1,
type: 'Type2'
}, {
param1: 4,
param2: value4,
position: 2,
type: 'Type2'
}, {
param1: 5,
param2: value5,
position: 3,
type: 'Type2'
}
]
}
The objects in Type property represent choices for radio buttons.
This way I set the first choice checked by default:
const groupBy = (array, key) => {
return array.filter(o => o.checked != undefined).reduce((result, currentValue) => {
(result[currentValue[key]] = result[currentValue[key]] || []).push(
currentValue);
return result;
}, {});
};
const [state, setState] = useState({
buttonsTypes: groupBy([...arrayObjects, arrayObjects.map((item, index) => item.position === 1 ? item['checked'] = true : item['checked'] = false)], 'type'),
selections: arrayObjects.filter(item => item.checked)
});
My question is how to map this dynamically and change the checked value on click?
I tried this so far:
const selectChoice = (obj) => {
setState(prevState => {
return {
buttonsTypes: ... // Not sure how to update the checked value only for the selection of the specific obj.Type
selections: ...
};
});
};
{Object.entries(state.buttonsTypes).map((item, index) => {
return <React.Fragment>
<div className="some-class"/>
<span id="text">{item[0]}</span>
<div id="radio-button-wrapper">
{item[1].map((choice, index) => {
return <label className="label-css">
<input type="radio" name={choice.param2} value={choice.param1} checked={choice.checked} onClick={() => selectChoice(choice)}/>
</label>
})
}
</div>
</React.Fragment>
})
}
Can anyone suggest some examples or another approach on how to solve this?
Update: As suggested in the answer, I solved it this way by keeping only an array of the selected choices for each radio group:
const selectChoice = (obj) => {
setState(prevState => {
const sel = prevState.selections;
const idx = sel.findIndex(item => item.type === obj.type);
if (idx === -1) {
sel.push(obj);
} else {
sel[idx] = obj;
}
return {
...prevState,
selections: sel
};
});
};
Inside the mapping:
{Object.entries(state.groupedArray).map((item, index) => {
return <React.Fragment>
<div className="some-class"/>
<span id="text">{item[0]}</span>
<div id="radio-button-wrapper">
{item[1].map((choice, index) => {
return <label className="label-css">
<input type="radio" name={choice.param2} value={choice.param1} defaultChecked={choice.position === 1} onClick={() => selectChoice(choice)}/>
</label>
})
}
</div>
</React.Fragment>
})
}
Would appreciate if anyone has any suggestions or comments for improvements.
Why not use 'state' to keep track of the index of the checked radio button?
Then inside of {item[1].map((choice, index) => {...}}
you can check whether the currently saved 'state' matches the current index in the input you're trying to return
and update the index of the currently checked radio input by attaching your selectChoice handler
Related
I have this dynamic checkbox, that I want to update the state with the selected options only ,I tried to add some checks to filter the state on change , but it seems I am not seeing what went wrong!
const checkBoxesOptions = [
{ id: 1, title: 'serviceOne' },
{ id: 2, title: 'serviceTwo' },
{ id: 3, title: 'serviceThree' },
];
const [selectedCheckBoxes, setSelectedCheckBoxes] = React.useState([]);
{checkBoxesOptions.map((checkBox, i) => (
<CheckBox
key={i}
label={checkBox.title}
value={1}
checked={false}
onChange={value => {
let p = {
title: checkBox.title,
isTrue: value,
};
if (p.isTrue) {
const tempstate = selectedCheckBoxes.filter(
checkbox => checkbox !== checkBox.title
);
console.log('temp state', tempstate);
setSelectedCheckBoxes([...selectedCheckBoxes, p.title]);
}
console.log(p);
}}
/>
))}
The value parameter is the event object.
(event) => {
const value = event.target.checked
<... Rest of the code ...>
}
I have such situation:
There is an array:
const arr = [
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
]
Then I use this array in my React component to get values, but here I also need to change status to true or false in current element (not for all objects)
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => !status}>{div.title}</div> // here
</div>
)
})
So how can I make this?
How about simply (if you have the access to the arr variable) you have an option of getting an index of the array as the 2nd parameter in the map function.
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => {
arr[ix].status = !arr[ix].status
}}>{div.title}</div> // here
</div>
)
})
or pull it out in a function (better way) as
function handleClick (ix) {
const currentStatus = arr[ix].status
arr[ix].status = !currentStatus
}
arr.map((el, ix) => {
return (
<div key={el.id}>
<div onClick={() => handleClick(ix)}>{div.title}</div> // here
</div>
)
})
EDIT: Didn't see it was a reactJS, my bad, in that case the best case is to manage it with state.
You can try:
const handleClick = (el) => () => {
el.status = !el.status
}
arr.map(el => {
return (
<div key={el.id}>
<div onClick={handleClick(el)}>{div.title}</div> // here
</div>
)
})
Maintain array in react state;
const [arr,setArr]=useState([
{
id: 0,
title: 'a',
status: false
},
{
id: 1,
title: 'a',
status: false
},
{
id: 2,
title: 'a',
status: false
},
])
Write a function to handle the change in state
const changeStatus =id=>{
const newArr = arr.map((el,index)=>{
if(el.id===id){
return {...el,status:!el.status}
}
else return;
})
setArr(newArr)
}
call this function on onClick.
arr.map(el => {
return (
<div key={el.id}>
<div onClick={() => changeStatus(el.id)}>{div.title}</div> // here
</div>
)
})
I have a complex data set, so I will show a very simplified version for an example.
Input data:
const data = [
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
}
];
Each item of the array is removable. It turns out something like this.
There are several conditions. I should not modify the data array so i create a deep copy. As well inside the copy i can only delete elements but don't modify their properties. Thus each element has to have local state with a new value.
Working example:
function App() {
const [mainData, setMainData] = useState(deepCopy(data));
return (
<React.Fragment>
{
mainData.map((item, i) => {
return (
<Input {...item} key={i} num={i} setMainData={setMainData}/>
)
})
}
</React.Fragment>
)
}
const Input = (props) => {
const [value, setValue] = useState(props.defaultValue);
const deleteElem = () => {
props.setMainData((mainData) => {
return [...mainData.filter((_, ind) => ind !== props.num)];
});
};
return (
<div>
<div>
<div>{`${props.caption}:`}</div>
<input value={value} onChange={(e)=>setValue(e.target.value)}/>
</div>
<button onClick={deleteElem}>delete</button>
</div>
)
};
const deepCopy = (aObject) => {
if (!aObject) {
return aObject;
}
let value;
let bObject = Array.isArray(aObject) ? [] : {};
for (const key in aObject) {
value = aObject[key];
bObject[key] = (typeof value === "object") ? deepCopy(value) : value;
}
return bObject;
};
If you try to delete not the last element then (because of the keys) the values of the inputs elements will be mixed up.
What can I do about it?
With deepCopy you can add a unique id to each item when you initialize your state. Once you do that you can leverage that id for passing as key to the Input element
import {uuid} from 'uuidv4';
function deepCopyAndAddId = () => {
let newData = deepCopy(data);
newData = newData.map((item, index) => ({...item, id: uuid()}));
}
function App() {
const [mainData, setMainData] = useState(deepCopyAndAddId);
return (
<React.Fragment>
{
mainData.map((item, i) => {
return (
<Input {...item} key={item.id} num={i} setMainData={setMainData}/>
)
})
}
</React.Fragment>
)
}
To make minimum changes in your code - just never delete the item in deleteElem, but add a flag deleted to it instead.
When render an item, show <Fragment> for the deleted item.
I am mapping multiple radio buttons (group) options and when the user click on radio buttons, would like to add selected values and uniqueIds to an array.
with my current code I can get the value that I am currently clicking on but can't add to array.
{result !== null && result.length > 0 ? (
<div>
{result.map(item => {
const label = item.question
const listOptions = item.options.map(item => {
return (
<Radio
name={item.uniqueId}
inline
value={item.uniqueId}
key={item.uniqueId}
className="radio-options"
checked={item.checked}
onChange={e => {
this.handleChange(label, uniqueId);
}}
>
{item.options}
</Radio>
);
});
return (
<div className="radio-options-overview">
<p>{label}</p>
{listOptions}
</div>
);
})}
handleChange = (label, uniqueId) => {
console.log(label, uniqueId);
let addToArray = [];
this.setState({ selectedItems: addToArray})
};
array would look something like this,
[
{ "label": "ageRange", "uniquId": 2 },
{ "label": "smoker", "uniquId": 1 },
{ "label": "exOption", "uniquId": 3 }
]
You are nearly there. #Clarity provided good solution.
if you wanting to replace exisiting value and replace it with new one
Try This
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
// Find items that already exists in array
const findExistingItem = selectedItems.find((item) => {
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
// Remove found Item
selectedItems.splice(findExistingItem);
// set the state with new values/object
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
} else {
// if new Item is being added
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
}
};
You can do like this:
handleChange = (label, uniqueId) => {
this.setState(state => ({ selectedItems: [...state.selectedItems, uniqueId]}));
};
By using array spread and functional form of setState you make sure that you don't directly mutate the state and add the items to the latest state.
In case you'd want to add an object with a label: uniqueId pair, you could do like so:
handleChange = (label, uniqueId) => {
this.setState(state => ({
selectedItems: [...state.selectedItems, {
[label]: uniqueId
}]
}));
};
EDIT: If you want to overwrite the items with the same labels, the easiest would be to store them as an object and not an array, so the item with the same label would overwrite an existing one:
handleChange = (label, uniqueId) => {
this.setState(state => {
return { selectedItems: { ...state.selectedItems, [label]: uniqueId } };
});
};
Honestly, I don't understand, what are you trying to do, but if you need to add object (or something else) inside an array, you could use .push() method. For example:
let addToArray = [];
let test = {"label": "ageRange", "uniquId": 2};
addToArray.push(test);
console.log(addToArray); //[{label: "ageRange", uniquId: 2}]
I have a react form which has dynamic rows, but I'm stuck at line 76 (Demo: https://codesandbox.io/s/pw58j0vzoq), not sure what can I do to update the value in the row
class App extends React.Component {
state = {
acceptedValues: [
{
id: 1,
_arguments: ["Samsung", "xiaomi"]
},
{
id: 2,
_arguments: ["OR", "AND"]
}
]
};
handleChange = (name, index, argumentIndex) => e => {
const { acceptedValues } = this.state;
if (name === "_arguments") {
updatedState = acceptedValues.map((o, i) => {
if (i === index) {
return {
...o,
_arguments: o._arguments.map((o2, index2) => {
if (index2 === argumentIndex) {
//what to do here?
}
return o;
})
};
}
return o;
});
this.setState({
acceptedValues: updatedState
});
}
};
render() {
const { acceptedValues } = this.state;
return (
<div>
{acceptedValues.map(({ operator, _arguments }, index) => (
<div style={{ marginBottom: 20 }}>
<div>
{_arguments.map((val, argumentIndex) => (
<div>
<input
onChange={this.handleChange(
"_arguments",
index,
argumentIndex
)}
id="_arguments"
type="text"
value={val}
/>
<button onClick={this.removeArgument(index, argumentIndex)}>
-
</button>
</div>
))}
</div>
</div>
))}
</div>
);
}
}
I'm able to navigate until the correct array but stuck at how to update the value within the array, I've 2 indexs in my handleChange function.
There you are mapping an array into another, so you should just pick which string to return, like this:
_arguments: o._arguments.map(
(o2, index2) => {
if (index2 === argumentIndex) return e.target.value
return o2
}
)
Or in an even cleaner way:
_arguments: o._arguments.map(
(o2, index2) => index2 === argumentIndex ? e.target.value : o2
)