How to Access a Child's State in React

How to access a child's state in React

If you already have an onChange handler for the individual FieldEditors I don't see why you couldn't just move the state up to the FormEditor component and just pass down a callback from there to the FieldEditors that will update the parent state. That seems like a more React-y way to do it, to me.

Something along the line of this perhaps:

const FieldEditor = ({ value, onChange, id }) => {
const handleChange = event => {
const text = event.target.value;
onChange(id, text);
};

return (
<div className="field-editor">
<input onChange={handleChange} value={value} />
</div>
);
};

const FormEditor = props => {
const [values, setValues] = useState({});
const handleFieldChange = (fieldId, value) => {
setValues({ ...values, [fieldId]: value });
};

const fields = props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={handleFieldChange}
value={values[field]}
/>
));

return (
<div>
{fields}
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
);
};

// To add the ability to dynamically add/remove fields, keep the list in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];

return <FormEditor fields={fields} />;
};

Original - pre-hooks version:

class FieldEditor extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange(event) {
const text = event.target.value;
this.props.onChange(this.props.id, text);
}

render() {
return (
<div className="field-editor">
<input onChange={this.handleChange} value={this.props.value} />
</div>
);
}
}

class FormEditor extends React.Component {
constructor(props) {
super(props);
this.state = {};

this.handleFieldChange = this.handleFieldChange.bind(this);
}

handleFieldChange(fieldId, value) {
this.setState({ [fieldId]: value });
}

render() {
const fields = this.props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));

return (
<div>
{fields}
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
}

// Convert to a class component and add the ability to dynamically add/remove fields by having it in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];

return <FormEditor fields={fields} />;
};

ReactDOM.render(<App />, document.body);

How to access child state in react?

I have done similar thing in a project by passing state of parent as a prop in child to access child component data in parent component for form elements.

In your case if you send component's state in its child as a prop and each child use state of parent like this.props.state.variablename and not this.state.variablename. You will have control on child components' states / data.

Sending state to childrens from form component using this.prop.children as a prop is not straight forward. Below link helps in doing this.

https://stackoverflow.com/a/32371612/1708333

Example:

Parent component:

<FormFields
state={this.state}
_onChange={this._onChange}
/>

Child component

<Input
name="fieldname"
value={this.props.state.fieldname}
type="text"
label="Lable text"
validationMessage={this.props.state.validationMessages.fieldname}
onChange={this.props._onChange}
/>

Let me know if you need more information.

React - How to get state data from a child component?

To accomplish this you would need to "Lift the state" up to a common parent component (known as ancestor component), in your case, this would be the <Form> component. Then you would pass down the values to each corresponding child component as props.

It would look something like this:

import React, { Component } from "react";

import Name from "./Name";
// More imports go here..

class Form extends Component {
state = {
firstName: "",
lastName: ""
};

handleSubmit = event => {
event.preventDefault();
};

// Handle first name input on change event
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
});
};

// Handle last name input on change event
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
});
};

render() {
return (
<form onSubmit={this.handleSubmit}>
<Name
firstName={this.state.firstname}
lastName={this.state.lastName}
handleFirstNameChange={this.handleFirstNameChange}
handleLastNameChange={this.handleLastNameChange}
/>
{/* More components go here.. */}
<p>Current state:</p>
{JSON.stringify(this.state)}
</form>
);
}
}

export default Form;

Working example

More info: Lifting state up from the official React docs.

Reactjs: How to access state of child component.from a method in parent component that depends on state of child component

It is always more logical to pass state and data down rather than up. If the Parent needs to interact with the edit state then that state should live in the parent. Of course we want independent edit states for each child, so the parent can't just have one boolean. It needs a boolean for each child. I recommend an object keyed by the name property of the field.

In ChildComponent, we move isEdit and setEdit to props. handleCancelEdit is just () => setEdit(false) (does it also need to clear the state set by onChange?).


function ChildComponent({fieldName, value, inputType, placeHolder, name, onChange, onSubmit, isEdit, setEdit}) {
return (
<p>{fieldName}: {value === ''? (
<span>
<input type={inputType} placeholder={placeHolder}
name={name} onChange={onChange}
/>
<button type="submit" onClick={onSubmit}>Add</button>
</span>
) : ( !isEdit ? (<span> {value} <button onClick={() =>setEdit(true)}>Edit</button></span>) :
(<span>
<input type={inputType} value={value}
name={name} onChange={onChange}
/>
<button type="submit" onClick={onSubmit}>Save</button>
<button type="submit" onClick={() => setEdit(false)}>Cancel</button>
</span>)
)}
</p>
);
};

In Parent, we need to store those isEdit states and also create a setEdit function for each field.

function Parent() {
const [isEditFields, setIsEditFields] = useState({});

const handleSetEdit = (name, isEdit) => {
setIsEditFields((prev) => ({
...prev,
[name]: isEdit
}));
};

/* ... */

return (
<div>
<ChildComponent
fieldName={"Email"}
value={email}
inputType={"text"}
placeHolder={"Enter email"}
name={"email"}
onChange={(e) => setEmail(e.target.value)}
onSubmit={handleUserEmail}
isEdit={isEditFields.email}
setEdit={(isEdit) => handleSetEdit("email", isEdit)}
/>
<ChildComponent
fieldName={"About"}
value={about}
inputType={"text"}
placeHolder={"Enter some details about yourself"}
name={"about"}
onChange={(e) => setAbout(e.target.value)}
onSubmit={handleUserAbout}
isEdit={isEditFields.about}
setEdit={(isEdit) => handleSetEdit("about", isEdit)}
/>
</div>
);
}

You can clean up a lot of repeated code by storing the values as a single state rather than individual useState hooks. Now 5 of the props can be generated automatically just from the name.

function Parent() {
const [isEditFields, setIsEditFields] = useState({});

const [values, setValues] = useState({
about: '',
email: ''
});

const getProps = (name) => ({
name,
value: values[name],
onChange: (e) => setValues(prev => ({
...prev,
[name]: e.target.value
})),
isEdit: isEditFields[name],
setEdit: (isEdit) => setIsEditFields(prev => ({
...prev,
[name]: isEdit
}))
});

const handleUserEmail = console.log // placeholder
const handleUserAbout = console.log // placeholder

return (
<div>
<ChildComponent
fieldName={"Email"}
inputType={"text"}
placeHolder={"Enter email"}
onSubmit={handleUserEmail}
{...getProps("email")}
/>
<ChildComponent
fieldName={"About"}
inputType={"text"}
placeHolder={"Enter some details about yourself"}
onSubmit={handleUserAbout}
{...getProps("about")}
/>
</div>
);
}


Related Topics



Leave a reply



Submit