semantic ui react Setting dropdown value to state - reactjs

how to have dropdowns selected value in state.here is my code iam getting value for name field but dropdown not working, can anyone find out what i am missing?
MyComponent.js
import React,{Component} from 'react';
class MyComponent extends Component{
state={
data:{
name:'',
subject:''
}
}
onChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
},()=>{
console.log(this.state.data);
}
)
render(){
const {data}=this.state;
const subjects= [
{text: '1',value: 'kannada'},
{text: '2', value: 'english'},
{text: '3',value: 'hindhi'}
]
return(
<div>
<Input
name="name"
onChange={this.onChange}
placeholder='Your name ...'
/>
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.onChange}
selection
options={subjects}
/>
</div>
)
}
}
export default MyComponent;
how to have selected dropdown value in state?, iam getting changed value for name field but for dropdown not getting.

handleChange = (e, { value }) => this.setState({ value })
Add value prop to Dropdown
render(
const { value } = this.state;
return(
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.handleChange}
selection
options={subjects}
value={value}
/>)
)

If anyone is using react hooks and semantic ui react, this is how I got it to work, without having to create a separate change handler function for it.
const options = [
{ key: "1", text: "Speaker", value: "SPEAKER" },
{ key: "2", text: "Event Planner", value: "EVENT_PLANNER" }
];
const [values, setValues] = useState({
firstName: "",
userType: ""
});
const onChange = (event, result) => {
const { name, value } = result || event.target;
setValues({ ...values, [name]: value });
};
<Form.Dropdown
placeholder="I am a.."
name="userType"
label="User Type"
selection
onChange={onChange}
options={options}
value={values.userType}
/>
What kept throwing me off was the 'result' that the onChange function takes as an argument. Since the options are stored as objects in an array, the correct way to access their values is with the 'result' and not 'event.target.'

One more way to use DropDown in React Semantic. it worked perfectly for me.
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
]
Method to set value
handleSelectChange=(e,{value})=>this.setState({stateValue:value})
In Form
<Form.Select fluid label='List Example' options={options}
placeholder='List Example'
value={value}
onChange={this.handleSelectChange} />

You can use the Form.Field also.
For more information.
constructor(props) {
super(props);
this.state={
subject : ""
}
}
handleChange = (e, result) => {
const { name, value } = result;
this.setState({
[name]: value
});
};
render() {
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
];
return(
<div>
<Form>
<Form.Field
placeholder="Subject"
name="subject"
label="Subject"
control={Dropdown}
fluid
selection
onChange={this.handleChange}
options={options}
value={this.state.subject}
/>
</Form>
</div>
);
}

Related

RTL - test expect(onChange).toHaveBeenCalledWith({ name, value }) not working

Im using Material-Ui & Typescript.
I`m testing the 'onChange' input component and it does not working with value.
Form component wraps the input component and it holds the input state.
const Form: React.FC = () => {
const [state, setState] = useState({ name: '', email: '' });
const { name, email } = state;
const onChange = ({ target: { name, value }}:IOnChange) => setState({ ...state, [name]: value });
const onSubmit = (e: any) => {
e.preventDefault();
};
return (
<Grid xs={12} container item component='form' onSubmit={onSubmit} noValidate>
<Input
label={'Name'}
name='name'
value={name}
onChange={onChange}
type='text'
/>
<Input
label={'Email'}
name='email'
value={email}
onChange={onChange}
type='email'
/>
</Grid>
);
};
export default Form;
this is the Input component:
const Input: React.FC<IProps> = ({ label, name, value, onChange, type }) => (
<TextField
label={label}
name={name}
value={value}
onChange={e => onChange(e)}
type={type}
variant="outlined"
inputProps={{
"data-testid": `input-${name}`,
}}
/>
);
interface IProps {
label: string
name: string
value: string
onChange: (e:React.InputHTMLAttributes<HTMLInputElement> & { target: { name: string, value: string }}) => void
type: string
};
export default Input;
And this is my test:
import { cleanup, fireEvent, render } from "#testing-library/react";
const props = { label: 'label', name: 'name', value: '', onChange: jest.fn(), type:'text' };
const { name, onChange } = props
describe("<Input />", () => {
describe("onChange", () => {
test("function call", () => {
const { getByTestId } = render(<Input {...props} />);
const e = expect.objectContaining({ target: expect.objectContaining({ name, value: 'value here' })});
fireEvent.change(getByTestId('input-name'), { target: { value: 'value here', name }});
this works
expect(onChange).toHaveBeenCalledTimes(1);
here is the error:
expect(onChange).toHaveBeenCalledWith(e);
Expected: ObjectContaining {"target": ObjectContaining {"name": "name", "value": "value here"}}
Received: {"_reactName": "onChange", "_targetInst": null, "bubbles": true, "cancelable": false, "currentTarget": null, "defaultPrevented": false, "eventPhase": 3, "isDefaultPrevented": [Function functionThatReturnsFalse], "isPropagationStopped": [Function functionThatReturnsFalse], "isTrusted": false, "nativeEvent": {"isTrusted": false},
"target": <input aria-invalid="false" class="MuiInputBase-input MuiOutlinedInput-input" data-testid="input-name" id="outlined-label" name="name" type="text" value="" />, "timeStamp": 1624028430742, "type": "change"}
});
});
});
The value on input is empty.
I tried whit userEvent and still not working.
How can i test onChange to be called with the new value?
Thank you..

Adding new options to form on click in ReactJs

I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.

Generate React form fields from JSON

i am trying to generate a form from JSON config. I am parsing the JSON and using map functions to generate material UI TextField components.
But issue is that the generated components do not get rendered, instead the whole JS code appears on screen. Not sure why.
here is my code in 2 files:
FormConfig.js:
"Form1": {
"fields": [
{
uiElement: "TextField",
id: '"standard-name"',
name: "'asdada'",
className: "{classes.textField}",
value: "{this.state.name}",
onChange: '{this.handleChange("name")}',
required: true,
margin: '"normal"'
},
{
uiElement: "TextField",
id: '"standard-uncontrolled"',
name: '"asda"',
label: '"Required"',
className: '"{classes.textField}"',
value: '"asd"',
onChange: "{}",
required: true,
margin: '"normal"'
}
]
},
"OtherForm":
{
"fields": [{}, {}]
}
}
const getForm = formName => {
return FormConfig[formName].fields.map(field => `<${field.uiElement} `+
Object.keys(field).filter(k => k !== 'uiElement')
.map(k => {
return k + "=" + field[k];
})
.join(" ") + `/>`
)
}
export default getForm;
TestForm.js
class TextFields extends React.Component {
state = {
name: 'Cat in the Hat',
age: '',
multiline: 'Controlled',
currency: 'EUR',
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
{
getForm("Form1")
}
<TextField
required
id="standard-required"
label="Required"
defaultValue="Hello World"
className={classes.textField}
margin="normal"
/>
I was expecting that the call to getForm() would have rendered my fields, but instead it spits out this on the web page. Am I doing something wrong?
<TextField id="standard-name" name='asdada' className={classes.textField} value={this.state.name} onChange={this.handleChange("name")} required=true margin="normal"/><TextField id="standard-uncontrolled" name="asda" label="Required" className="{classes.textField}" value="asd" onChange={} required=true margin="normal"/><TextField id="standard-read-only-input" name="asd" label="Read Only" className={classes.textField} value="asd" onChange={} required=false margin="normal" InputProps={{readOnly: true}}/><TextField id="standard-dense" name="w3rg" label="Dense" className={classNames(classes.textField, classes.dense)} value="sdas" onChange={} required=false margin="dense"/>
try to return the component at mapping:
const getForm = formName => {
return FormConfig[formName].fields.map(field => evalComponent(field))
}
const evalComponent = field => {
let { uiElement, ...props } = field
switch(uiElement) {
case 'TextField':
return <TextField {...props}/>
default:
return false
}
}

React-Select: Getting multiple onChange values from dropdown

import React from 'react';
import Select from 'react-select';
const options = [
{ value: 'all', label: 'all' },
{ value: 'destroyed', label: 'Destroyed' },
{ value: 'damaged', label: 'Damaged' },
{ value: 'physicalDamage', label: 'PhysicalDamage' }
]
class SearchFilters extends React.Component {
_onChange = (e, options) => {
const onChangeData = {
value: e.value,
name: e.label,
result: options.find(item => item.value === e.value)
};
console.log(onChangeData.value);
return onChangeData;
};
render(){
return(
<div>
<Select
options={options}
onChange={e => this._onChange(e, options)}
isMulti={true}
/>
</div>
)
}
}
export default SearchFilters
From the above code if isMulti is false in select, I'm able to print the value of the selected option in console, but if I change it to true, I get the value as undefined.
I need help in fixing this. Thanks in advance.
Ouput:

<Select> component from material-ui not updating changes

The dropdown looks and works fine, but will not update a new selection and I get the following error message:
Warning: Use the `defaultValue` or `value` props on <select> instead of setting `selected` on <option>.
Selected is not set on <option> as far as I'm aware, so it must be set by material-ui's component. Anyway, if I change value into defaultValue, I get the following error message:
aterial-UI: the `value` property is required when using the `Select` component with `native=false` (default).
I thought it was a problem in Material-ui itself, but their example works fine, although there are no parent/child components in the example like mine.
Example:
https://codesandbox.io/s/7yk922om7x
My code (shortened for brevity):
constructor(props) {
super(props)
this.state = {
languageValues: {
htmlFor: 'lstLanguages',
value: 'english',
input: <Input name="language" id="lstLanguages"/>,
options: [
{ value: 'dutch', text: 'Nederlands' },
{ value: 'french', text: 'Français' },
{ value: 'english', text: 'English' },
{ value: 'german', text: 'Deutsch' }],
helperText: 'Choose your language',
}
}
}
handleChange = event => {
event.preventDefault();
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<div>
<h2 id="speechTitle">Speech Settings</h2>
<hr/>
<FormGroup column='true'>
<DropDown
dataToFill={ this.state.languageValues }
onChange={ this.handleChange.bind(this) }
/>
Dropdown.js:
const DropDown = ({dataToFill}) => {
const menuItemValueList = dataToFill.options.map((item, i) => {
return <MenuItem value={ item.value } key={ i }>{ item.text }</MenuItem> //Always provide a key
})
return (
<FormGroup column='true'>
<FormControl>
<InputLabel htmlFor={ dataToFill.htmlFor }>Languages</InputLabel>
<Select
defaultValue={ dataToFill.value }
input={ dataToFill.input }
>
{ menuItemValueList }
</Select>
<FormHelperText>{ dataToFill.helperText }</FormHelperText>
</FormControl>
</FormGroup>
);
}
EDIT 1
I think I have found the problem: The handleChange function expects [event.target.name] but the value that needs changing is nested in the state, I'm not sure how I can access it...
Event handler code:
handleChange = event => {
this.setState({ [event.target.name]: event.target.value })
}
Nested state object:
languageValues: {
htmlFor: 'lstLanguages',
value: 'english',
input: <Input name="language" id="lstLanguages"/>,
options: [
{ value: 'dutch', text: 'Nederlands' },
{ value: 'french', text: 'Français' },
{ value: 'english', text: 'english' },
{ value: 'german', text: 'German' }
],
helperText: 'Choose your language',
},

Resources