Get value from appended child component in React - javascript

I have a main react component which appends child components say <Child /> on button click
My Child component is of the format
<form>
<input .... />
<button type="submit">Submit</button>
<form>
Now I need to get the value of these input elements from every Child component which is appended but am not able to figure out a way to do so.
I won't know how many Child components would be added as the user can click the button any number of times to append yet another <Child /> so I can't have a fixed number of variables exported from the Child component to a variable in parent component.
Any suggestions would be highly appreciated.
Edit:
Code to append the child:
The submit function:
const [val, setVal] = useState([]);
const submit = () => {
setVal([...val, <Child />]);
}
Append button:
<Button onClick={submit}>Add Child</Button>
To render:
{val}
Since val is an array, it prints all the components inside it

I took some liberties because I don't know what would be the expected output of all the forms.
What I basically did is to create a map-like structure where I will map each child form with its value/s (depending on your needs could be modified) and I passed a submit function to the child components in order to store the values on the parent.
In that way on a child submit I will be able to get the values passed from the child as well as its index.
Parent component
const Parent = () => {
const [val, setVal] = useState([]);
// Map like structure to store the values (is just an example, you can use whatever you want)
const [mapOfValues, setMapOfValues] = useState({});
// Pass submit function as prop
const submit = () => {
setVal([...val, <Child submit={onChildSubmit} />]);
};
// Submit function that stores the value for the child
const onChildSubmit = (childIndex, value) => {
setMapOfValues({
...mapOfValues,
[childIndex]: value
});
};
return (
<div className="App">
{val.map((value, index) =>
// Key to prevent React warning and childIndex to map the value to the child form
React.cloneElement(value, { key: index, childIndex: index })
)}
<button onClick={submit}>Add</button>
</div>
);
}
Child component
const Child = (props) => {
const [value, setValue] = useState("");
const submitForm = (e) => {
e.preventDefault();
props.submit(props.childIndex, value);
};
return (
<form onSubmit={submitForm}>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button>Submit</button>
</form>
);
};
Once you are done, and you want to submit from the parent component, you could use a reduce, map or whatever you need to format the values as you want.
The link to the sandbox is this
If you have any other question let me know.

Related

How do I pass a value from parent to child, then edit it and pass it back in React?

Using hooks.
The parent component passes a value to the child component, which the child component displays. But I'd like that component editable and once the user clicks save, its new value should be passed back to parent component and is then used to update.
parent component:
const [value, setValue] = useState("");
// value is set by some function
<Child
value={value}
/>
child component:
<h2 contentEditable=true >props.value</h2>
// somehow save the value, even to a different variable, and pass it back to parent component to setValue
I have tried setting in my child component file something like
const childValue=props.value
or
const [childValue, setChildValue] = useState(props.value)
and I tried console.log on those values but they're all empty, and if I passed them to h2 in the child component, nothing displays.
--- EDIT
I have tried passing the function to set value to parent but I'm struggling getting the changed value.
For child component I'd like to save either onChange or on clicking a button, but I'm missing a way to capture the new value.
parent component has a saveValue(newValue) function which I pass to child
in child component
const {value, saveValue} = props;
return
<h2
onChange={() => saveValue(e.target.value)}
contentEditable=true> {value} </h2>
I have saveValue in parent component and tried changing the function to print out the argument in the console but nothing gets logged.
I also tried saving the changes with a button, I have no method of capturing the actual changes made to h2 though, as my code looks something like this:
const {value, setValue} = props;
<h2 contentEditable=true>{value}</h2>
<button onClick={()=>setValue(value)}>Save</button>
This just sets the value to the old value and not the edited one
Pass setValue to the Child component as a prop, and call it inside. For example:
const ChildComponent = ({value, setValue}) => {
return (
<>
<button onClick={() => setValue('my new value')}>
change value
</button>
<span>{value}</span>
</>
)
}
const ParentComponent = () => {
const [value, setValue] = useState("");
return <ChildComponent value={value} setValue={setValue}/>
}
You can approach this problem in 2 ways. Both the approach are basically the same thing, as you are ultimately passing a function to handle the state change.
Approach 1: Create a handler function (say handleValueChange) to manage the state change using setValue in the parent component. Then you can pass this handler function to the child component.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
function handleChange() {
// Some logic to change the "value" state
setValue("A new value");
}
return <Child value={value} handlerFunction={handleChange} />
}
Child component:
const Child = (props) => {
const { value, handlerFunction } = props;
// Utilize the props as per your needs
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handlerFunction}>Change state</button>
</>
);
};
Approach 2: Pass on the setValue function to the child component as props.
Parent component:
const Parent = () => {
const [value, setValue] = useState("");
return <Child value={value} setValue={setValue} />
}
Child component:
const Child = (props) => {
const { value, setValue } = props;
// Utilize the props to change the state, as per your needs
function handleChange() {
setValue("A new value");
}
return (
<>
<h2 contentEditable>Value: {value}</h2>
<button onClick={handleChange}>Change state</button>
</>
);
};

Avoid rerendering when doing button.click() inside useeffect()

I'm trying to set the data from child component to parent component's state in turn that data will be assigned to input text field. And then I want to automatically click the button to submit the data from input text field to handleBtnSubmit() function.
But when I did this child component is rendering indefinitely. Can someone please help resolving this kind of situation?
Parent
const [responses, setResponses] = useState([]);
const [currentMessage, setCurrentMessage] = useState('');
const submitAction = () => {
setResponses((responses) => [...responses, message]);
handleMessageSubmit(message.text);
setCurrentMessage('');
};
const handleBtnSubmit = () => {
submitAction();
};
<Messages
messages={responses}
parentData={{ currentMessage, setCurrentMessage, btnRef, handleBtnSubmit }}
/>
<div className='typing-area'>
<div className='input-field'>
<input
type='text'
placeholder='Type something here'
required
value={currentMessage}
onChange={handleMessageChange}
onKeyDown={handleSubmit}
/>
</div>
<button onClick={handleBtnSubmit} ref={btnRef}>
<img src={sendButton} alt='Send Button' />
</button>
</div>
Child
const setMessage = (option) => {
parentData.setCurrentMessage(option);
// parentData.btnRef.current.click();
// console.log(parentData.currentMessage);
};
useEffect(() => {
console.log(setCurrentMessage.currentMessage);
// parentData.btnRef.current.click(); //tried this
// parentData.handleBtnSubmit(); //also also tried directly calling handleBtnSubmit();
//both are causing indefinite rerender
}, [parentData.currentMessage]); //tried this too
<li className='option' key={i} onClick={() => setMessage(option)}>
{option}
</li>
First, pass currentMessage as a separate property, instead of passing it within an object. Something like:
<Messages
messages={responses}
currentMessage={currentMessage}
parentData={{ setCurrentMessage, btnRef, handleBtnSubmit }}
/>;
Then try passing the currentMessage prop as a dependency into the useEffect as shown below:
useEffect(() => {
console.log(currentMessage);
setCurrentMessage.btnRef.current.click();
}, [currentMessage]); // child's message state
This way, the useEffect code is called only when the currentMessage changes, and not indefinitely.
Create another state that stores the full value onclick/submit. And use that value as a dependency to the useEffect()
const [messageText, setMessageText] = useState('');
useEffect(() => {
submitBtnRef.current.click();
}, [messageText]);

Is possible set state from parent and set state from children at same element?

I have a component and I need to update the value from parent (ok), but if the user change the value of the input (children / functional component), I need set this value without pass to parent to re-render children.
See this example:
https://codesandbox.io/s/boring-thunder-qvibf?file=/src/App.js
Follow this steps:
Set some value on input and see console log
Click on button, the input receive new value
Change value on input again
Click on button, why doesn't work again and set value to input?
I need independent children (functional component or class component), and sometimes I need use parent to set children value, but also keeping the children to change his own state, this is possible?
The reason that your example isn't working is that any button clicks after the first don't change the value of the parent state and so don't trigger a render.
Below is a simple snippet that gets around this by adding a unique counter value on each click to always trigger a render, but you could also pass the handler to the child and clear the parent state, or sync it with the child state, waiting for the next button click.
I will say that setting state from props is an anti-pattern and leads to unexpected disconnects (as evidenced by the question); it would be better to set/handle the state in a single place.
const App = () => {
const [parentValue, setParentValue] = React.useState("");
const clickCount = React.useRef(0);
const parentValueHandler = () => {
setParentValue(`New value from parent ${++clickCount.current}`);
};
return (
<div className="App">
<Child parentValue={parentValue} />
<button onClick={parentValueHandler}>Change value to Parent</button>
</div>
)
}
const Child = ({ parentValue }) => {
const [value, setValue] = React.useState(parentValue);
React.useEffect(() => {
setValue(parentValue);
}, [parentValue]);
return (
<input
type='text'
name='child'
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
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>

ReactJs - Input Component behaves strangely when its returned from a function

Scenario
I declared a react component that renders a simple html input tag.
const MyComponent = (props) => (
<input
defaultValue="test"
onChange={(e) => {
props.setTitle(e.target.value);
}}
/>
);
Then I declared a function that takes a setState as a parameter and returns that component with the setState inside the input's onChange.
const getComponent = (setTitle) => (props) => (
<input
defaultValue="test"
onChange={(e) => {
setTitle(e.target.value);
}}
/>
);
Then I called my function to get the component and to render it:
const Root = () => {
const [title, setTitle] = React.useState('');
const Component = getComponent(setTitle);
return (
<div>
<div>{title}</div>
<Component />{' '}
</div>
);
};
Expected:
The input element behaves normally, and changes its value
Reality:
The input loses focus after each character typed, and won't retain its value.
Here is a simple example of the error:
CodeSandbox
The reason this is happening is that when your code comes to this line:
const Component = getComponent(setTitle);
This generates new function (i.e. new instance) that is not rendered again, but mounted again. That is the reason you get unfocused from field.
There is no way you will make this work in this way, it's just not meant to be working like this. When you do it once when you are exporting it, than its ok, but every time === new instance.
If this is just an experiment that you are trying, than ok. But there is no reason not to pass setState as prop to that component.
I found a solution that let me use the function and keep the component from mounting each time.
If you put the function call inside the jsx, the component won't remount each render.
const Root = () => {
const [title, setTitle] = React.useState('');
const Component = ;
const someprops = {};
return (
<div>
<div>{title}</div>
{getComponent(setTitle)(someprops)}
</div>
);
};

How to access a child's state in React

I have the following structure:
FormEditor - holds multiple instances of FieldEditor
FieldEditor - edits a field of the form and saving various values about it in its state
When a button is clicked within FormEditor, I want to be able to collect information about the fields from all FieldEditor components, information that's in their state, and have it all within FormEditor.
I considered storing the information about the fields outside of FieldEditor's state and put it in FormEditor's state instead. However, that would require FormEditor to listen to each of its FieldEditor components as they change and store their information in its state.
Can't I just access the children's state instead? Is it ideal?
Just before I go into detail about how you can access the state of a child component, please make sure to read Markus-ipse's answer regarding a better solution to handle this particular scenario.
If you do indeed wish to access the state of a component's children, you can assign a property called ref to each child. There are now two ways to implement references: Using React.createRef() and callback refs.
Using React.createRef()
This is currently the recommended way to use references as of React 16.3 (See the documentation for more information). If you're using an earlier version then see below regarding callback references.
You'll need to create a new reference in the constructor of your parent component and then assign it to a child via the ref attribute.
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.FieldEditor1 = React.createRef();
}
render() {
return <FieldEditor ref={this.FieldEditor1} />;
}
}
In order to access this kind of ref, you'll need to use:
const currentFieldEditor1 = this.FieldEditor1.current;
This will return an instance of the mounted component so you can then use currentFieldEditor1.state to access the state.
Just a quick note to say that if you use these references on a DOM node instead of a component (e.g. <div ref={this.divRef} />) then this.divRef.current will return the underlying DOM element instead of a component instance.
Callback Refs
This property takes a callback function that is passed a reference to the attached component. This callback is executed immediately after the component is mounted or unmounted.
For example:
<FieldEditor
ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
{...props}
/>
In these examples the reference is stored on the parent component. To call this component in your code, you can use:
this.fieldEditor1
and then use this.fieldEditor1.state to get the state.
One thing to note, make sure your child component has rendered before you try to access it ^_^
As above, if you use these references on a DOM node instead of a component (e.g. <div ref={(divRef) => {this.myDiv = divRef;}} />) then this.divRef will return the underlying DOM element instead of a component instance.
Further Information
If you want to read more about React's ref property, check out this page from Facebook.
Make sure you read the "Don't Overuse Refs" section that says that you shouldn't use the child's state to "make things happen".
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);
As the previous answers said, try to move the state to a top component and modify the state through callbacks passed to its children.
In case that you really need to access to a child state that is declared as a functional component (hooks) you can declare a ref in the parent component, and then pass it as a ref attribute to the child, but you need to use React.forwardRef and then the hook useImperativeHandle to declare a function you can call in the parent component.
Take a look at the following example:
const Parent = () => {
const myRef = useRef();
return <Child ref={myRef} />;
}
const Child = React.forwardRef((props, ref) => {
const [myState, setMyState] = useState('This is my state!');
useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})
Then you should be able to get myState in the Parent component by calling:
myRef.current.getMyState();
It's 2020 and lots of you will come here looking for a similar solution but with Hooks (they are great!) and with the latest approaches in terms of code cleanliness and syntax.
So as previous answers had stated, the best approach to this kind of problem is to hold the state outside of child component fieldEditor. You could do that in multiple ways.
The most "complex" is with a global context (state) that both parent and children could access and modify. It's a great solution when components are very deep in the tree hierarchy and so it's costly to send props in each level.
In this case I think it's not worth it, and a more simple approach will bring us the results we want, just using the powerful React.useState().
An approach with a React.useState() hook - way simpler than with Class components
As said, we will deal with changes and store the data of our child component fieldEditor in our parent fieldForm. To do that we will send a reference to the function that will deal and apply the changes to the fieldForm state, you could do that with:
function FieldForm({ fields }) {
const [fieldsValues, setFieldsValues] = React.useState({});
const handleChange = (event, fieldId) => {
let newFields = { ...fieldsValues };
newFields[fieldId] = event.target.value;
setFieldsValues(newFields);
};
return (
<div>
{fields.map(field => (
<FieldEditor
key={field}
id={field}
handleChange={handleChange}
value={fieldsValues[field]}
/>
))}
<div>{JSON.stringify(fieldsValues)}</div>
</div>
);
}
Note that React.useState({}) will return an array with position 0 being the value specified on call (Empty object in this case), and position 1 being the reference to the function
that modifies the value.
Now with the child component, FieldEditor, you don't even need to create a function with a return statement. A lean constant with an arrow function will do!
const FieldEditor = ({ id, value, handleChange }) => (
<div className="field-editor">
<input onChange={event => handleChange(event, id)} value={value} />
</div>
);
Aaaaand we are done, nothing more. With just these two slim functional components we have our end goal "access" our child FieldEditor value and show it off in our parent.
You could check the accepted answer from 5 years ago and see how Hooks made React code leaner (by a lot!).
Hope my answer helps you learn and understand more about Hooks, and if you want to check a working example here it is.
Now you can access the InputField's state which is the child of FormEditor.
Basically, whenever there is a change in the state of the input field (child), we are getting the value from the event object and then passing this value to the Parent where in the state in the Parent is set.
On a button click, we are just printing the state of the input fields.
The key point here is that we are using the props to get the input field's id/value and also to call the functions which are set as attributes on the input field while we generate the reusable child input fields.
class InputField extends React.Component{
handleChange = (event)=> {
const val = event.target.value;
this.props.onChange(this.props.id , val);
}
render() {
return(
<div>
<input type="text" onChange={this.handleChange} value={this.props.value}/>
<br/><br/>
</div>
);
}
}
class FormEditorParent extends React.Component {
state = {};
handleFieldChange = (inputFieldId , inputFieldValue) => {
this.setState({[inputFieldId]:inputFieldValue});
}
// On a button click, simply get the state of the input field
handleClick = ()=>{
console.log(JSON.stringify(this.state));
}
render() {
const fields = this.props.fields.map(field => (
<InputField
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
<div>
<button onClick={this.handleClick}>Click Me</button>
</div>
<div>
{fields}
</div>
</div>
);
}
}
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditorParent fields={fields} />;
};
ReactDOM.render(<App/>, mountNode);
You may access the child state by passing a callback to the child component.
const Parent = () => {
return (
<Child onSubmit={(arg) => {
console.log('accessing child state from parent callback: ', arg)
}}
/>
)
}
const Child = ({onSubmit}) => {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={setText}>
<button onClick={() => onSubmit(text)} />
</>
)
}
Now if you click the button in the child component, you will execute the function passed from the parent and have access to the child component's state variables.

Categories

Resources