I am trying to use Semantic UI react for layout. These are the following sample code I am having trouble with onChange. I can check to click the toggle but resets everytime I refresh.
import {
Checkbox
} from 'semantic-ui-react'
onChangeInput(event) {
let name = event.target.name
let value = event.target.value
let talent = this.state.newTalentProfile
talent[name] = value
this.setState({
newTalentProfile: talent
})
}
<Select
name = "willing_to_relocate"
ref = "willing_to_relocate"
defaultValue = {this.props.talent.willing_to_relocate}
onChange = { this.onChangeInput.bind(this)} >
<Option value = ""label = "" / >
<Option value = "YES"label = "YES" / >
<Option value = "NO"label = "NO" / >
</Select>
the below code doesn't work, but the above one works when i make changes it saves it to database
<Checkbox toggle
name = "willing"
ref = "willing"
label = "Willin To Relocate"
onChange = {this.onChangeInput.bind(this)
}
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
onChangeCheckbox = (evt, data) => {
let checked = data.checked
console.log(checked)
}
<Checkbox toggle label='Running discounted price?'
onClick={(evt, data)=>this.onChangeCheckbox(evt, data)}
/>
This should do the trick.
Your code suggests you expect event.target to be a checkbox element, but it is not. If you examine the variable event.target within your browser, you'll notice that it points to a label element, not the checkbox itself. Therefore, I'm guessing that your event.target.value and event.target.label are evaluating to null, causing the rest of your code to not function the way you expect.
There's many ways to get around this. One way is to set a state variable for your component to represent the state of the checkbox:
class TheComponentThatContainsMyCheckbox extends Component {
state = {
textValue: null,
willingToRelocate: true
}
...
Then, you can create a handler that toggles this state when checked:
toggleCheckBox = () => {
const willingToRelocate = !(this.state.willingToRelocate);
this.setState({willingToRelocate});
// Note: if you have other logic here that depends on your newly updated state, you should add in a callback function to setState, since setState is asynchronous
}
Notice that I'm using arrow syntax here, which will automatically bind this for me. Then, hook it up to the onChange event of the Semantic UI Checkbox:
<Checkbox label='Willing to Relocate.' onChange={this.toggleCheckBox}/>
You can now use your this.willingToRelocate state to decide other application logic, passing it up or down using whatever state management pattern you like (state container/store like Redux, React Context, etc.).
I assume your this.onChangeInput handles database updates.
semantic ui react offers a prop checked for Checkbox.
You should have a state {checked: false} in your code, then this.setState whatever in componentDidMount after retrieving the database record.
Pass this.state.checked into checked prop of Checkbox.
Related
I have several checkboxes running in several checkbox groups. I can't figure out how to uncheck (thus changing the state) on a particular checkbox. FOr some reason I can't reach e.target.checked.
<Checkbox
size="small"
name={item}
value={item}
checked={checkboxvalues.includes(item)}
onChange={(e) => handleOnChange(e)}
/>
and my function
const handleOnChange = (e) => {
const check = e.target.checked;
setChecked(!check);
};
I made a working sample of the component in this sandbox.
You need to create handleOnChange function specific to each group. I have created one for Genre checkbox group in similar way you can create for other groups.
Here is handler function.
const handleOnChangeGenre = (e) => {
let newValArr = [];
if (e.target.checked) {
newValArr = [...state.pillarGenre.split(","), e.target.value];
} else {
newValArr = state.pillarGenre
.split(",")
.filter((theGenre) => theGenre.trim() !== e.target.value);
}
setState({ ...state, pillarGenre: newValArr.join(",") });
};
pass this function as handleOnChange prop to CustomCheckboxGroup as below.
<CustomCheckboxGroup
checkboxdata={genres}
checkboxvalues={state.pillarGenre}
value={state.pillarGenre}
sectionlabel="Genre"
onToggleChange={handleGenreSwitch}
togglechecked={genreswitch}
handleOnChange={handleOnChangeGenre}
/>
comment your handleOnChange function for testing.
check complete working solution here in sandbox -
complete code
Here's how I'd do it: https://codesandbox.io/s/elastic-pateu-flwqvp?file=/components/Selectors.js
I've abstracted the selection logic into a useSelection() custom hook, which means current selection is to be found in store[key].selected, where key can be any of selectors's keys.
items, selected, setSelected and sectionLabel from each useSelection() call are stored into store[key] and spread onto a <CustomCheckboxGroup /> component.
The relevant bit is the handleCheck function inside that component, which sets the new selection based on the previous selection's value: if the current item is contained in the previous selected value, it gets removed. Otherwise, it gets added.
A more verbose explanation (the why)
Looking closer at your code, it appears you're confused about how the checkbox components function in React.
The checked property of the input is controlled by a state boolean. Generic example:
const Checkbox = ({ label }) => {
const [checked, setChecked] = useState(false)
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span>{label}</span>
</label>
)
}
On every render, the checked value of the <input /> is set according to current value of checked state. When the input's checked changes (on user interaction) the state doesn't update automatically. But the onChange event is triggered and we use it to update the state to the negative value of the state's previous value.
When dealing with a <CheckboxList /> component, we can't serve a single boolean to control all checkboxes, we need one boolean for each of the checkboxes being rendered. So we create a selected array and set the checked value of each <input /> to the value of selected.includes(item) (which returns a boolean).
For this to work, we need to update the value of selected array in every onChange event. We check if the item is contained in the previous version of selected. If it's there, we filter it out. If not, we add it:
const CheckboxList = ({ items }) => {
const [selected, setSelected] = useState([])
const onChecked = (item) =>
setSelected((prev) =>
prev.includes(item)
? prev.filter((val) => val !== item)
: [...prev, item]
)
return items.map((item) => (
<label key={item}>
<input
type="checkbox"
checked={selected.includes(item)}
onChange={() => onChecked(item)}
/>
<span>{item}</span>
</label>
))
}
Hope that clears things up a bit.
The best way to do it, it's to save selected checkboxes into a state array, so to check or uncheck it you just filter this state array based on checkbox value property that need to be unique.
Try to use array.some() on checkbox property checked. To remove it it's just filter the checkboxes setted up in the state array that are different from that single checkbox value.
I am using a Material UI Select Component to render a simple drop down menu, with its value as a state declares using the useState method.
const [collaboratingTeams, setCollaboratingTeams] = useState([])
The below code is of the Select Component, with its value and the corresponsing handler function in its onChange prop.
<Select
validators={["required"]}
errorMessages={["this field is required"]}
select
multiple
variant="outlined"
value={collaboratingTeams}
name="collaboratingTeams"
onChange={(e) => handleSelectCollaboratingTeams(e)}
helperText="Select Collaborating Teams "
>
{arrTeams.map((option, index) => (
<MenuItem
key={option.teamId}
value={option.teamId}
variant="outlined"
>
<Checkbox
checked={collaboratingTeams.indexOf(option.teamId) !== -1}
/>
<ListItemText primary={option.teamValue} />
</MenuItem>
))}
</Select>
The below code is the function that triggers when a drop down data is changed.
This function sets the state, which should then technically update the Select's selected options.
const handleSelectCollaboratingTeams =(e)=>{
setCollaboratingTeams(e.target.value)
}
The issue is, the setCollaboratingTeams method isn't updating the state only. I understand that the setstate method in hooks works so coz of its asynchronous nature but at some point it should display up right. Don't understand where I'm going wrong.
I expect the collaboratingTeams array to be updated with a new value when a new value is selected by the user.
you should define the new state for storing the selected item.
Example for class component:
state = {
selectedOption: null,
};
handleChange = selectedOption => {
this.setState({ selectedOption });
};
Example for functional component(using React-hook):
const [selectedOption, setSelectedOption] = useState(null);
handleChange = selectedOption => {
setSelectedOption(selectedOption);
};
dont use arrow function with onchange it often used when we need to pass id or some other data
I'm using materiel UI form component in a modal.
This modal can be opened for add or edit an item, so values can be empty or not.
I put default props values in a state but this is always empty and never get previous values...
Here is my code :
const Comp = (props) => {
const { edit, values } = props // edit props for editing user
// values is :
{
prenom: 'name',
nom: 'name'
}
// ...
const [nom, setNom] = React.useState(edit ? values.nom : '')
const [prenom, setPrenom] = React.useState(edit ? values.prenom : '')
// ...
return (
<form>
<TextField
id="prenom"
value={prenom}
label="Prénom"
variant="outlined"
onChange={(event) => setPrenom(event.target.value)}
/>
<TextField
id="nom"
value={nom}
label="Nom"
variant="outlined"
onChange={(event) => setNom(event.target.value)}
/>
</form>
)
}
Thanks for your help
I'm guessing that you have your Comp used on the parent but not visible till some state changes, something like isDialogOpen. Then once the user wants to edit some object you do something like
setIsDialogOpen(true);
setDialogEditMode(true);
setValuesToEdit({nom: 'Foo', prenom: 'Bar'});
You have to understand that once you use the component (<Comp prop='value' />) React renders it, even that nothing gets to the actual Dom, the Comp function will be called! so at first it's being called with some empty values, then you want to let the user do the editing, you change the parent state. BUT the Comp state is still the state that it was created with (Empty values).
Remember this: useState acts in two ways:
On the first render it returns the value from the given parameter.
Any future renders it ignores the parameter and returns the saved state.
So in order to change the saved state you have to declare a reaction/effect when the props change, you do that with useEffect inside your Comp
for example:
useEffect(() => {
setNom(values.nom);
setPrenom(values.prenom);
}, [values])
I was trying to update the state after user selects the dropdown, however, the selected option is never changed. See the gif -- https://recordit.co/KH2Pqn34bp.
I am confused that ideally, after using setFilterOptions to update state, it's supposed to re-render this component with a new value, but it doesn't happen. Could anyone help take a look? What am I missing here? Thanks a lot!
Example code on sandbox -- https://codesandbox.io/s/react-select-default-value-forked-1ybdk?file=/index.js
const SearchFilter = () => {
const [filterOptions, setFilterOptions] = useContext(SearchFilterContext);
let curSort = filterOptions['sortType'] || DEFAULT_SORT_OPTION;
const handleSortChange = (option) => {
setFilterOptions(previous => Object.assign(previous, { 'sortType': option }))
};
return (
<span className='filter-container'>
<Select options={SORT_TYPE_OPTIONS} value={curSort} onChange={handleSortChange}/>
</span>
);
};
Couple of problems in your code:
To set the default value of the Select component, you have written some unnecessary code. Instead, you could just use the defaultValue prop to set the default value of the Select component.
<Select
options={OPTIONS}
defaultValue={OPTIONS[0]}
onChange={handleSortChange}
/>
You are mutating the state directly. Object.assign(...) returns the target object. In your case, the target object is the previous state.
Instead of returning the new state object, you mutate the state directly and return the previous state object which prevents a re-render.
Using the spread-syntax, you can update the state correctly as shown below:
const handleSortChange = (option) => {
setFilterOptions({ ...filterOptions, sortType: option });
};
Following code fixes the above mentioned problem in your component:
const SearchFilter = () => {
const [filterOptions, setFilterOptions] = useState({});
const handleSortChange = (option) => {
setFilterOptions({ ...filterOptions, sortType: option });
};
return (
<span className="filter-container">
<Select
options={OPTIONS}
defaultValue={OPTIONS[0]}
onChange={handleSortChange}
/>
</span>
);
};
The reason you're not seeing a change is that this functional component will only re-render when it sees that your state changed (based on your sandbox script). At the moment when you use Object.assign(previous, { 'sortType': option}) you're changing the sortType property in the object but the object itself doesn't change and so the functional component doesn't see a change.
We can resolve this by using either Object.assign({}, previous, { 'sortType': option}) which will create a NEW object with the previous state attributes and the changed sortType (the first param to Object.assign is the object where the following object properties will get copied into. if we use an empty object that's the equivalent of a new object) or we can use a spread operator and replace it with ({...sortType, 'sortType': option}) which will also create a new object that the functional component will recognize as a changed state value.
const handleSortChange = (option) => {
setFilterOptions(previous => Object.assign({}, previous, { 'sortType': option }))
};
or
const handleSortChange = (option) => {
setFilterOptions(previous => ({...previous, 'sortType': option})
};
Keep in mind these are shallow object copies.
I'm trying to get the id,name, and value from Select field element of Material-UI react component.
This is my container:
//some code is removed to keep things simple
class MyContainer extends Component {
constructor(props) {
super(props);
}
render() {
return(
<MyComp onChange={this._onChange.bind(this)} />
);
}
_onChange(evt) {
console.log(evt.target);
console.log(evt.target.id); //blank
console.log(evt.target.name); //undefined
console.log(evt.target.value); //html value of selected option!
}
}
export default connect(select)(MyContainer);
in my presentational component:
import React, {Component} from 'react';
import Select from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
const someData = ["aaaa", "bbbb", "ccccc"];
class MyComp extends Component {
render() {
const {onChange} = this.props;
return (
<form >
<Select
value={this.props.test}
name={"test"}
id={"test"}
onChange={this.props.onChange}
hintText={"Select a fitch rating service"}>
{
someData.map((e) => {
return <MenuItem key={Math.random()} value={e} primaryText={e}/>
})
}
</Select>
</form>
);
}
}
Problem is that _onChange(evt) is giving this values:
evt.id is blank
evt.name is undefined
evt.target.value is <div>whatever the selected value was</div>
It seems as though the passed argument to _onChange(evt) is not the SELECT but rather the option since when i print out evt.target it gives <div>whatever the selected value was</div>. anyone knows why? If i use a plain select field (not material-ui) then this works as expected (i.e. i can get the id,name, correct value of selected option). How do i get the target id, name..etc from onChange event of Material-UI Select component?
P.S i'm using the same _onChange method for TextField component of material-ui and it works there. I've also tried:
_onChange = (event, index, value) => {
console.log(event.target.id); //blank
console.log(event.target.name); //undefined
console.log(index); //correct index
console.log(value); //correct value
};
I'd keep it simpler using event.currentTarget instead of event.target.
In your event handler, you can access the attribute of interest from the element that triggered the event by:
_onChange(evt) {
console.log(evt.currentTarget.getAttribute("data-name");
console.log(evt.currentTarget.getAttribute("data-value");
}
thus calling .getAttribute on the attribute name prepended by "data-"
Update 2
In response to your comments:
As per the material-ui docs, getting back the touchtap event on option element rather than the select element is expected. If you want the id and name of the element, I would suggest binding the variables to the callback:
The onchange method in the parent component:
_onChange(id, name, evt, key, payload) {
console.log(id); //id of select
console.log(name); //name of name
console.log(payload); //value of selected option
}
And when you attach it to the select component, you need to use bind
<Select
value={this.props.test}
name={"test"}
id={"test"}
onChange={this.props.onChange.bind(null,"id","name")}
hintText={"Select a fitch rating service"}>
Update
Here are the react event docs. Under the event-pooling you will find reference to the use of e.persists() in the block quote. The explanation given in this issue is that React pools the event object, so it get's reused when another event is fired.
React has a rather special event object. It wraps the native event in a synthetic event, which is what event points to in your _onChange method.
The problem is that this synthetic event object is recycled and reused for the next event, so it is garbage collected and set to null. I don't think this is well documented, but I'll search for a link and update this answer once I find it.
The solution is for the first line in your event handler to make a copy of the event, persist it, or get a reference to the native event:
Make a copy of the event
_onChange = (event, index, value) => {
e = _.cloneDeep(event); // Here I'm using the cloneDeep method provided by underscore / lodash . User whatever library you prefer.
console.log(e.target.id);
console.log(e.target.name);
};
Persist the event
_onChange = (event, index, value) => {
event.persist() // This stops react from garbage collecting the event object. It may impact performance, but I doubt by much.
console.log(event.target.id);
console.log(event.target.name);
};
Get a reference to the native event object
_onChange = (event, index, value) => {
e = event.native; // This looks the same as a vanilla JS event
console.log(e.target.id);
console.log(e.target.name);
};
This is an answer for latest version of Material UI and React (2020):
You need to have the name property on your <Select> element. That name value should correspond to the field's name in your object.
This should work (name is set to the field you're trying to update):
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
name="taskType"
value={props.job.taskType || ""}
label="{props.label}"
onChange={props.onTaskTypeChange}
>
But this will not work (name is omitted). This will return evt.target.name = undefined:
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={props.job.taskType || ""}
label="{props.label}"
onChange={props.onTaskTypeChange}
>