handle checkbox data and store in a state/variable - reactjs

How can I store the selected value in another state and stop unchecking the value when I select another button?
const Notifications = () => {
data:[
{id:1, name:apple, notification:{id:1,msg:"hi"}, {id=2,msg:"hlo"}},
{id:2, name:banana, notification:{id:13,msg:"hi"}, {id=3,msg:"hlo"}}
{id:3 ,name:mango, notification:{id:14,msg:"hi"}, {id=34,msg:"hlo"}},
{id:4, name:grape, notification:{id:15,msg:"hi"}, {id=341,msg:"hlo"}},
{id:5, name:carrot, notification:{id:16,msg:"hi"}, {id=4,msg:"hlo"}},
]
const onCheckedValue = (e) => {
const { checked, value } = e.target;
if (checked) {
setCheckedData([...checkedData, value]);
} else {
setCheckedData(checkedData.filter((item) => item !== value));
}
};
return(
<>
{data.map(d => {
<Button onClick=(setActive(true))>
{d.name}
</Button>
})}
//render the notifications when I clicked on the button I want to store the checked value in a state with respect to the selected button and checked notification.
{active ?
data.notifications.map((notification) => (
<div className="checkbox">
<Checkbox
onChange={onCheckedValue}
value={notification}
/>
/div>
}
</>
)
}

You can use the React Use State Hook. You'll want to define a variable and a state setter for that variable like so:
const [selected, setSelected] = useState([]);
Then when you click or un-click a checkbox, you can call setSelected with the updated selected value. I instantiated selected as an empty array in my example, so you'd arr.push(new value).

Your second block of code is where there are issues.
You are attempting to access properties on data, but that is an array and you cannot access the properties of the objects inside of it like that.
You will need to loop over the array or access an index of the array directly in order to access notifications.
As a side note, you are attempting to use the ternary operator to conditionally render (?), but not supplying the second argument after the :. I gave it a value of null so that it doesn't render anything if not active.
That could look something like this:
// I don't know where active comes from, but I'll assume that's correct.
{active ? (
data.map((item) => (
<div className="checkbox">
<Checkbox
onChange={onCheckedValue}
checked={!!checkedData.find(name => name === item.name)}
value={item.name}
/>
</div>
))) : null
}
In this example, I map over data and create a Checkbox for each item in the array. I don't know if that's what you want as it's unclear. However, with your onCheckedValue function, it will make updates to the checkedData array.
You should pass the prop checked to control the checkbox's state. I set the checked value to the return of checkedData.find(), and used the double-bang (!!) operator to coerce it into a Boolean value (true or false).
The find operation will be looking to see if the data[i].name matches any value in the checkedData array, since you are storing the object's name property in the checkedData array. If it finds nothing, it will be !!undefined which will evaluate to false, and if it does find it, it will be !!"banana" which evaluates to true.

Related

How to update a boolean value of an element that its inside an array of objects in react using hooks

I have the following array:
const [notifications, setNotifications] = useState([
{
"idNotification": 1,
"message": "Message number 1",
"hideNotification": false,
},
{
"idNotification": 2,
"message": "message number 2",
"hideNotification": false,
}
])
what i want its to loop through this array to show each notification, and if I click on the X button (the X button appear with the prop "toggle") then i want that the notification value "hideNotification" becomes true, and therefore stop showing up on the list
{
notifications?.map((tdata, index) => (
tdata.hideNotification === false ?
<div key={index}>
<Alert
color='primary'
isOpen={!tdata.hideNotification}
toggle={() => {
// I know this doesn´t work
tdata.hideNotification = true;
}}>
{tdata.message}
</Alert>
</div> : null
))
}
I've seen other posts with similar question but with string values. I don't really know how to apply that to a boolean value
Hi. Please try this:
<Alert
color='primary'
isOpen={!tdata.hideNotification}
toggle={() => {
setNotifications((prevEl) => {
let ind = prevEl.findIndex(
(notification) => notification.idNotification === tdata.idNotification
);
notifications[ind].hideNotification = true;
return [...prevEl];
});
}}>
{tdata.message}
</Alert>
When you want a loop on an array and return the element and sometime not, map is not advised.
You should use filter.
You can filter your list on "hideNotification" by doing const
notificationsToDisplay = notifications?.filter((tdata, index) => {
return tdata.hideNotification === false
})
Now you can map on notificationsToDisplay array. Since it contains only the elements having hideNotification property set to true, you'll never see the ones having the property set to false.
Also to set you hideNotification you have to use a state, you can't modify it directly using an assignation. Otherwise your component won't rerender, and you will not see any change.
You shoud use setNotifications()
Since you know the index or even the id of the element you want to modify you just need to find your element in the notifications array using findIndex
and then update the nofications array using setNotifications()
Ok, #Sardor is somewhat-correct in his answer, but he is missing explanation and also some of it is incorrect/can be simplified. I tried to edit, but the edit queue was filled.
First, #Sardor is correct in using the setNotifications setter in the toggle method. You do want to set the state, not the object's property value, like the OP suggests with a comment saying it didn't work. #Sardor uses the findIndex to get the appropriate object in the notifications array, but you can simply use the tdata.idNotification to give you the mapped ID. He also tries to edit the notifications state directly inside the setNotifications method which is incorrect.
The point of having the previous state in the params of the setter method; e.g.
setNotifications((previousNotificationState) => ...)
is to use the previous state (at the time of running the setter) to mutate it knowing the previous state has accurate values.
TLDR; React makes it easy to keep track and edit the state, so don't try to make it hard. You should have a setter edit the state which would cause a re-render, here is an example:
{
notifications?.map((tdata, index) => (
hideNotification === false ?
<div key={index}>
<Alert
color='primary'
isOpen={!hideNotification}
toggle={() =>
setNotifications((prevEl) => Array.from(prevEl)
.map((copyTData) => copyTData.idNotification === tdata.idNotification
? { ...copyTData, hideNotification: true }
: copyTData)
)}>
{message}
</Alert>
</div>
: null
)
)
}
Notice how I:
used Array.from() to copy the previous state and then mutate it
The above use of Array.from() to copy the state was something a well respected colleague once told me was good practice.
Last (personal) note. index may be a unique identifier for the key property, but I'd argue you almost always have something on each iteration, besides the index, that could be unique. In this case the idNotification is unique and can be used as the key instead so the index property doesn't clutter the mappings parameters. Indexes mean nothing in an array when the array changes frequently.

React dynamic add select input field

I am trying Create a select field dynamically. When clicking the add button, it will dynamically add select fields. I have and issue where the values don't change.
My code can be viewed on codesandbox:
https://codesandbox.io/s/react-select-field-dynamically-uo4dy?file=/src/App.js
Take a look at my changes:
Forked sandbox
// I added a 3rd argument for the name:
const handleRoomChange = (option, index, name) => {
const value = option.value; // you had this as 'const { value } = option.value' which dereferences value twice
console.log(value);
const list = [...roomInputs];
list[index][name] = value; //you had this as list[index][value]
setRoomInputs(list);
};
// onChange passes the name as 3rd argument to handleRoomChange
<Select
name="boardBasic"
placeHolder="Board"
value={options.value}
onChange={option => handleRoomChange(option, i, "boardBasic")}
options={options}
styles={{
menu: (provided) => ({ ...provided, zIndex: 9999 })
}}
/>
You have three problems that I can see.
First, you only have a handleRoomChange function that is trying to handle both room changes and board changes. You should probably have a handleBoardChange function, as well.
Second, if you output option to the console inside handleRoomChange, you will notice that option.value is a number. So, when you proceed to set list[index][value] to value, what you're saying is that you want (for example) roomInputs[1][1] to be 1. roomInputs[1] has no 1 property, which is why you will end up with the wrong properties inside your object on submission. You need to be setting roomInputs[1].roomType to value, instead (and the boardBasic property in your handleBoardChange method, when you write it).
Third, you are trying to use object destructuring to assign object.value to your value variable...but you're trying to destructure object.value instead of destructuring object. For this reason, value is undefined every time you run the function. Replace the assignment with const { value } = option; and you will start getting defined values back.

How to get a reference to a DOM element that depends on an input parameter?

Say I have the standard TODO app, and want a ref to the last item in the list. I might have something like:
const TODO = ({items}) => {
const lastItemRef = Reeact.useRef()
return {
<>
{items.map(item => <Item ref={item == items.last() ? lastItemRef : undefined} />)}
<>
}
}
But this doesn't seem to work - after lastItemRef is initialized, it is never subsequently updated as items are added to items. Is there a clean way of doing this without using a selector?
I think in your case it depends upon how the items list is updated. This is because useRef won't re-render the component if you change its current attribute (persistent). But it does re-render when you choose, for example, useState.
Just as a working case, see if this is what you were looking for.
Ps: check the console

React.js re-render behavior changes when list contents change

This question requires some background to understand:
My app uses various lists of items with Boolean state that the user toggles by clicking.
I implemented this logic with two reusable elements:
A custom hook, useSelections(), which maintains an array of objects of the form { id, name, isSelected }, where isSelected is Boolean and the only mutable element. It returns the array as current state, and a dispatch function that takes an input of the form { id, newValue } and updates the isSelected member of the object with the given id
A function component Selector, which takes as props a single item and the dispatch from useSelections and returns a <li> element whose CSS class depends on isSelected.
Normally, they are used together in the following way, which works fine (i.e., internal state and the color of the list item are synchronized, and toggle when clicked):
function localComponent(props) {
const [items, dispatch] = useSelections(props.data);
return (
<ul>
{items.map(item => <Selector key={item.id} item={item} onChange={dispatch} />)}
</ul>
);
}
It works equally well when useSelections() is elevated to a parent component, and items and dispatch are passed as props.
The trouble started when the array became larger, and I decided to page it:
<ul>
{items.slice(start, end).map(item => <Selector key={item.id} item={item} onChange={dispatch} />)}
</ul>
(start and end are part of component state.)
When my component is first rendered, it works normally. However, once start and end are changed (to move to the next page), clicking an item changes internal state, but no longer triggers a re-render. In UX terms this means that after the first 'next' click, when I click on an item nothing appears to happen, but if I hit 'next' again, and then 'back', the item I just clicked on has now changed.
Any suggestions would be appreciated.
I solved the problem by removing the logic from the reducer function to ignore calls where there is no change:
const hasChanged = modifiedItem.isSelected !== newValue;
modifiedItem.isSelected = newValue;
return hasChanged ? { data } : state;
is now simply:
modifiedItem.isSelected = newValue;
return { data };
There's still something going on with React here that I do not understand, but my immediate problem is solved.

React Radio Button event on "checked" attribute

I'm trying to handle an event based on the 'checked' attribute of a radio button in React.js. I want the buttons to allow for selecting more than one value, so I removed the 'name' attribute which seemed to disallow multiple selections.
The base radio button component looks like
export function Radio_Button_Multiple_Selection (props) {
return (
<label>
<input type="radio"
checked={props.form_data[props.field_name].indexOf(props.btn_value) > -1}
onClick={props.radio_effect} />
{props.btn_value}
</label>
)
}
I have a form_data objec that has an array of values that correspond to the btn_value of the radio buttons, where if the value is found in the array it should come up as checked. (And this part is working fine right now, they do in fact show up checked as expected):
const form_data = {
...
occupations: ['student', 'merchant', 'paladin', 'troll'],
...
}
Now, I also have a react class with a method for manipulating the values in the occupations array,responding to whether the radio button being licked was already checked or not:
handleRadioButton (event) {
const target = event.target;
const value = target.value;
const isChecked = target.checked;
console.log(isChecked) // this undefined. why?!?
if (isChecked) {
// remove it from array
// etc.
} else {
// add it to array
// etc.
}
}
My main issue is that following:
When I console.log the "checked" logic that returns a boolean inside the RadioButton component's checked attribute, it comes up as expected (true of false if it is or isnt in the array). But it always comes up as checked = undefined in the class method for handling the buttons.
You cannot uncheck a radio button in HTML either. You have to control the checked attribute with your props or state:
Even more, you should rely only on your state, instead of a mix, e.target.checked & state.
class Radio extends Component {
state = {}
render() {
return <input
type="radio"
checked={this.state.checked}
onClick={e => this.setState({
checked: !this.state.checked
})}
/>
}
}
<input type="radio" />
I found the workaround:
use the 'value' attribute to store the same information that I am storing under the 'checked' attribute, with an array that has the button's value AND the checked logic boolean; e.g., value=[boolean, btn_value]
then access the 'value' attribute in the event handler. the value arrives as a full string, so I just split it at the comma and work from there.
it's hacky, but it worked.

Resources