I'm exploring React and am somewhat confused over lifecycle methods and parent-child communication. Specifically, I'm trying to create a component which wraps a select element and adds an input box when the "Other" option is selected. I have implemented this using getDerivedStateFromProps() but according to the documentation this lifecycle method should rarely be used. Hence my question: is there another pattern I should be aware of and use in this case?
This is my code, the value and options are passed down as props, as is the handleChange() method of the parent component. So when changes are made in the select or input elements, the parent component state is updated first and a new value is passed down through props.value.
export default class SelectOther extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props) {
let optionIndex = -1;
for (let i = 0; i < props.options.length; i++) {
if (props.options[i].value === props.value) {
optionIndex = i;
break;
}
}
if (optionIndex > -1) {
return {
selected: props.options[optionIndex].value,
text: "",
showInput: false
};
} else {
return {
selected: "",
text: props.value,
showInput: true
};
}
}
handleChange(e) {
this.props.handleChange({
"target": {
"name": this.props.name,
"value": e.target.value
}
});
}
render() {
return (
<div>
<label>{ this.props.label }</label>
<select name={ this.props.name } value={ this.state.selected } onChange={ this.handleChange }>
{
this.props.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
}
<option value="">Other</option>
</select>
{
this.state.showInput &&
<div>
<label>{ this.props.label } (specify other)</label>
<input type="text" className="form-control" value={ this.state.text } onChange={ this.handleChange }></input>
</div>
}
</div>
)
}
}
You can simplify by not having SelectOther have any state, here is an example of how you can pass a function that dispatches an action to change values. Because SelectOther is a pure component it won't needlessly re render:
//make this a pure component so it won't re render
const SelectOther = React.memo(function SelectOther({
label,
name,
value,
options,
handleChange,
}) {
console.log('in render',name, value);
const showInput = !options
.map(o => o.value)
.includes(value);
return (
<div>
<label>{label}</label>
<select
name={name}
value={showInput ? '' : value}
onChange={handleChange}
>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
<option value="">Other</option>
</select>
{showInput && (
<div>
<label>{label} (specify other)</label>
<input
type="text"
name={name}
className="form-control"
value={value}
onChange={handleChange}
></input>
</div>
)}
</div>
);
});
const App = () => {
//create options once during App life cycle
const options = React.useMemo(
() => [
{ value: 'one', label: 'one label' },
{ value: 'two', label: 'two label' },
],
[]
);
//create a state to hold input values and provide
// a reducer to create new state based on actions
const [state, dispatch] = React.useReducer(
(state, { type, payload }) => {
//if type of action is change then change the
// payload.name field to payload.value
if (type === 'CHANGE') {
const { name, value } = payload;
return { ...state, [name]: value };
}
return state;
},
//initial state for the inputs
{
my_name: '',
other_input: options[0].value,
}
);
//use React.useCallback to create a callback
// function that doesn't change. This would be
// harder if you used useState instead of useReducer
const handleChange = React.useCallback(
({ target: { name, value } }) => {
dispatch({
type: 'CHANGE',
payload: {
name,
value,
},
});
},
[]
);
return (
<div>
<SelectOther
label="label"
name="my_name"
value={state.my_name}
options={options}
handleChange={handleChange}
/>
<SelectOther
label="other"
name="other_input"
value={state.other_input}
options={options}
handleChange={handleChange}
/>
</div>
);
};
//render app
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could have uses useState in App but then I have to use useEventCallback or do a useState for every input value. The following documentation comes up with the useEventCallback pattern and then immediately after states that we don’t recommend this pattern so that's why I came up with the useReducer solution instead.
Related
I have is a div that contains three input elements and a remove button. what is required is when the user clicks on the add button it will add this div dynamically and when the user clicks on the remove button it will remove this div. I was able to add one input element (without div container) dynamically with the following method.
create an array in the state variable.
assign a name to the dynamic input field with the help of array indexing like name0, name1
How can I do with these many input fields? The problem grows further when I create this whole div as a separate component. I am using a class-based component.
handleChange=(event) =>
{
this.setState({[event.target.name]:event.target.values});
}
render()
{
return(
<div className="row">
<button type="button" onClick={this.addElement}>Add</button>
<div className="col-md-12 form-group">
<input type="text" className="form-control" name="name" value={this.state.name} onChange={this.handleChange} />
<input type="text" className="form-control" name="email" value={this.state.email} onChange={this.handleChange} />
<input type="text" className="form-control" name="phone" value={this.state.phone} onChange={this.state.phone} />
<button type="button" onClick={this.removeElement}>Remove</button>
</div>
</div>
)
}
I would approach this from a configuration angle as it's a little more scalable. If you want to eventually change across to something like Formik or React Form, it makes the move a little easier.
Have an array of objects that you want to turn into input fields. Your main component should maintain state whether the <Form /> component is showing, and if it's visible pass in the config and any relevant handlers.
Your form component should maintain state for the inputs, and when you submit it, passes up the completed state to the parent.
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I hope this would be help for your question.
I made a child component which have three input tags.
// parent component
import React, { Component } from "react";
import TextField from "./TextField";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{
key: Date.now(),
name: "",
email: "",
phone: ""
}
]
};
}
onChange = (inputUser) => {
this.setState((prevState) => {
const newUsers = prevState.users.map((element) => {
if (element.key === inputUser.key) return inputUser;
return element;
});
return { users: newUsers };
});
};
addElement = () => {
const { name, email, phone } = this.state;
this.setState((prevState) => ({
users: prevState.users.concat({
key: Date.now(),
name,
email,
phone
})
}));
};
removeElement = (id) => {
this.setState((prevState) => ({
users: prevState.users.filter((user) => user.key !== id)
}));
};
render() {
const { users } = this.state;
return (
<div className="row">
<button type="button" onClick={this.addElement}>
Add
</button>
<div className="col-md-12 form-group">
{users.map((user) => (
<React.Fragment key={user.key}>
<TextField
value={user}
onChange={(inputUser) => this.onChange(inputUser)}
/>
<button
type="button"
onClick={() => this.removeElement(user.key)}
disabled={users.length <= 1}
>
Remove
</button>
</React.Fragment>
))}
</div>
</div>
);
}
}
export default App;
// child component
import { Component } from "react";
class TextField extends Component {
handleChange = (ev) => {
const { name, value } = ev.target;
this.props.onChange({
...this.props.value,
[name]: value
});
};
render() {
const { value: user } = this.props;
return (
<>
<input
className="form-control"
name="name"
value={user.name}
onChange={this.handleChange}
placeholder="name"
type="text"
/>
<input
className="form-control"
name="email"
value={user.email}
onChange={this.handleChange}
placeholder="email"
type="text"
/>
<input
className="form-control"
name="phone"
value={user.phone}
onChange={this.handleChange}
placeholder="phone"
type="text"
/>
</>
);
}
}
export default TextField;
You can also check the code in codesandbox link below.
https://codesandbox.io/s/suspicious-heisenberg-xzchm
It was a little difficult to write down every detail of how to generate what you want. So I find it much easier to ready a stackblitz link for you to see how is it going to do this and the link is ready below:
generating a dynamic div adding and removing by handling inputs state value
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox
The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}
const { useReducer } = React
const InputWithLabelAbove = ({
labelText,
id,
onChange,
pattern,
value,
}) => {
return (
<label htmlFor={id}>
{labelText}
<div>
<input
type="text"
id={id}
pattern={pattern}
value={value}
onChange={onChange}
/>
</div>
</label>
)
}
const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
const Component = () => {
// calc object
const calculatorObj = {
tax: '',
water: '',
energy: '',
internet: '',
transport: '',
food: '',
education: '',
childcare: '',
otherInsurance: '',
otherCosts: ''
}
// state
const inputValues = {
total: '',
showCalculator: false,
calculatedTotal: 0,
calc: {
...calculatorObj
}
}
// reducer for form states
const [values, setValues] = useReducer(
(state, newState) => ({...state, ...newState}),
inputValues
)
// on change function to handle field states.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
console.log('onchange')
const re = /^[0-9\b]+$/
const converted = !re.test(value) || value.length === 0 ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
}
}, [values.calc])
const calcLabelArr = ['Council tax', 'Water', 'Energy (gas and/or electricity)', 'Internet', 'Transport', 'Food', 'Children\'s education', 'Childcare', 'Other insurance', 'Other essential costs']
return (
<div
style={{ width: '60%', marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<MemoInputWithLabelAbove
id={i[0]}
type="text"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</div>
)
}
ReactDOM.render(
<Component />,
document.getElementById('reactBind')
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="reactBind"></div>
Below is the rendering of the inputs using an array (with 10 elements) so 10 input elements are rendered.
// calculator object typically populated but for this example its empty for ease.
const calcLabelArr = []
// function to return calculator fields
const buildView = () => {
return (
<Col
xs="12"
md={{ span: 6, offset: 3 }}
style={{ marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<InputWithLabelAbove
id={i[0]}
type="text"
labelPosition="top"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</Col>
)
}
Below is the onChange function used to set that state of each input.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
const re = /^[0-9\b]+$/
const converted = !re.test(value) || isEmpty(value) ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
} else {
setValues({
total: converted,
})
}
}, [values.calc])
Below is the component that is memoized.
import React from 'react'
import { join } from 'lodash'
import { Label, StyledInput, Red } from './style'
export type IProps = {
labelPosition: string,
labelText: string,
id: string,
hasErrored?: boolean,
onChange: () => void,
dataUxId?: string,
pattern?: string,
sessioncamHide?: boolean,
sessioncamClassList?: string | string[],
value?: string,
validation?: boolean,
}
const InputWithLabelAbove: React.FC<IProps> = ({
labelPosition,
labelText,
id,
hasErrored,
onChange,
dataUxId,
pattern,
sessioncamHide,
sessioncamClassList,
value,
validation,
}) =>
(
<Label hasErrored={hasErrored} labelPosition={labelPosition} htmlFor={id}>
{labelText && (<span>{labelText}{validation && (<Red>*</Red>)}</span>)}
<div>
<StyledInput
type="text"
id={id}
hasErrored={hasErrored}
dataUxId={`InputText_${dataUxId}`}
pattern={pattern}
labelPosition={labelPosition}
value={value}
onInput={onChange}
onChange={onChange}
/>
</div>
</Label>
)
export const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
As you can see, it isn't the key I don't think that is causing the re-render, my input component is memoized and the onChange is using a callback, however on using the react profiler, every onChange re-renders all my input components. Could someone elaborate to why this is?
One thing that jumps out is this property on the input component:
onChange={(e) => handleChange(e, 'calculator')}
Even though handleChange is memoized, you're creating a new arrow function every time to call the memoized function. So even if the input is memoized, it's seeing a new onChange every time.
You'd need to pass a stable function to avoid re-rendering, for instance:
const onChange = React.useCallback(
e => handleChange(e, "calculator"),
[handleChange]
);
and then
onChange={onChange}
(It wasn't clear to me where you're defining handleChange; if it's within the component that's rendering these inputs, you can probably combine that definition with the above in a single useCallback or possible useMemo if it's multiple callbacks. Although small pieces are good too.)
new to React/Redux, I am trying to make it so a user can have as many choices with different choice and customAttribute.
For now I have a button they can createUI with to dynamically create a new textfield to input another choice and customAttribute but am completely stumped on how to store such a thing in redux.
I've seen other questions and answers on how to store username and/or password, but have not seen any examples on how to store an object with my full state in it.
My Component
class CreateRIG extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
rigName: '',
desc: '',
choices: [{
choice: '',
customAttribute: ''
}]
}
}
createUI() {
return this.state.choices.map((el, i) => (
<div key={i}>
<FormControl>
<Select
id="choice"
name="choice"
onChange={this.handleChange.bind(this, i)}
value={el.choice || ''}
>
<MenuItem value=''>
<em>None</em>
</MenuItem>
<MenuItem value='ID'>ID</MenuItem>
<MenuItem value='PSA'>PSA</MenuItem>
<MenuItem value='ExternalID'>ExternalID</MenuItem>
</Select>
</FormControl>
<div>
<TextField name="customAttribute" label="Attribute"
onChange={this.handleChange.bind(this, i)} value={el.customAttribute || ''} />
))
}
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<TextField name="rigName"
value={this.state.rigName}
onChange={event => this.setState({ rigName: event.target.value })}/>
</div>
<div className={styles.spacing}>
<TextField name="description"
onChange={event => this.setState({ desc: event.target.value })}/>
</div>
{this.createUI()}
<Button type="submit"
onClick={this.props.onSubmitForm}>NEXT</Button>
</form>
);
}
}
const mapDispatchToProps = dispatch => {
return {
onSubmitForm: (rigName, desc, choice, customAttribute) => dispatch({ type: 'FORM_DATA'})
}
}
const connectedCreateRIGPage = connect(mapStateToProps, mapDispatchToProps)(CreateRIG);
export { connectedCreateRIGPage as CreateRIG };
export default CreateRIG;
Action.tsx
export const createRIGActions = {
searchInput
};
function searchInput(rigName, desc, choice, customAttribute) {
return {
type: createRIGConstants.STORE_FORM,
rigName,
desc,
choice,
customAttribute
}
}
Reducer.tsx
const initialState = {
results: []
};
export function createRIGReducer(state = initialState, action) {
switch (action.type) {
case createRIGConstants.STORE_FORM:
return {
...state,
results: state.results
}
// Need to somehow get the data from the form
default:
return state
}
}
How do you store a complex object from a form, into redux state on submit? Right now my onSubmit is console logging the correct object I want so thats nice
I believe it is just matter what you pass into dispatch. You can add payload there as well.
Try the following:
const mapDispatchToProps = dispatch => {
return {
onSubmitForm: (rigName, desc, choice, customAttribute) => dispatch({ type: 'FORM_DATA', payload: {rigName, desc, choice, customAttribute}})
}
}
Then in your reducer you can access that payload like the following:
export default (state=initialState, action) => {
switch(action.type) {
const { payload } = action;
case 'FORM_DATA':
return {
...state,
// here you can use data from payload to update the state
};
Read further here from Redux documentation: Managing Normalized Data
I hope this helps!
I have created a component that can be used for creating a new company record. A modal is opened with a form and the values are linked to the state values. In my situation, it will be possible to create more than one record of a company if the user chooses to add another company. A new company object will be pushed to the company state and the new empty form will be rendered.
This is what I've tried based on this answer:
import { Component } from 'react';
import { Modal, Header, Form, Button, Icon, Tab, Segment } from 'semantic-ui-react';
export default class CompanyCreate extends Component {
constructor(props) {
super(props);
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
this.initialState = this.state;
this.handleChange = this.handleChange.bind(this);
this.handleCompanyChange = this.handleCompanyChange.bind(this);
}
handleChange = (e, { name, value }) => this.setState({ [name]: value });
handleCompanyChange = (e, { name, value }) => {
const index = this.state.company.findIndex((x) => {
return x[name] === value;
});
if (index === -1) {
console.log('error');
} else {
this.setState({
company: [
...this.state.company.slice(0, index),
Object.assign({}, this.state.company[index], value),
...this.state.company.slice(index + 1)
]
});
}
};
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}
}
My problem is that I can't set the company state properly. How can you update the state in relation to the changes in the form fields?
Looking for answers, I found the package: immutability-helper. Based on this answer, the problem was solved simply and elegantly.
The solution:
import update from 'immutability-helper';
//...
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
//...
handleCompanyChange = (e, { name, value, id }) => {
let newState = update(this.state, {
company: {
[id]: {
[name]: { $set: value }
}
}
});
this.setState(newState);
};
//...
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
id={index}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
id={index}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}