Access state of parent component in a child component? - javascript

I'm new to react js and I need to get the state of the component to be accessed by another class, I encountered this problem because I'm using atomic design because writing everything in one component is turning to be a problem, here is my code:
Headcomponent:
class Headcomponent extends React.Component{
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
this.setState({formErrors: fieldValidationErrors,
emailValid: emailValid,
passwordValid: passwordValid
}, this.validateForm);
}
validateForm() {
this.setState({formValid: this.state.emailValid &&
this.state.passwordValid});
}
render(){
return (
<Form fields={this.props.form} buttonText="Submit" />
);
}
}
Headcomponent.propTypes = {
form: PropTypes.array,
};
Headcomponent.defaultProps = {
form: [
{
label: 'label1',
placeholder: 'Input 1',
},
{
label: 'label2',
placeholder: 'Placeholder for Input 2',
},
],
};
export default Headcomponent;
form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (<LabeledInput label={field.label} placeholder={field.placeholder} key={i}/>))
}
<Button text={props.buttonText} />
</form>
);
Form.propTypes = {
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonText: PropTypes.string.isRequired,
};
export default Form;
LabeledInput.js (where I need to pass the state of my password)
const LabeledInput = props => (
<div className={`form-group `} >
<Label text={props.label} />
<Input value={props.value} placeholder={props.placeholder} type="text" onChange={props.onChange} />
<div class="valid-feedback">{props.errorMessage}</div>
<small class="form-text text-muted">{props.exampleText}</small>
</div>
);
LabeledInput.propTypes = {
label: PropTypes.string.isRequired,
placeholder: PropTypes.string,
onChange: PropTypes.func.required,
value: PropTypes.string.isRequired,
exampleText: PropTypes.string,
errorMessage: PropTypes.string,
};
export default LabeledInput;
How can I access state of headComponent in LabeledInput?

The most reasonable way is passing it down (The values of Headcomponent's state you need) as props:
Headcomponent.js
class Headcomponent extends React.Component {
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
render() {
return (
<Form
fields={this.props.form}
formValid={this.state.formValid} // from Headcomponent's state
buttonText="Submit"
/>
);
}
}
Form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (
<LabeledInput
label={field.label}
formValid={props.formValid} // from Headcomponent's state
placeholder={field.placeholder}
key={i}
/>
))
}
<Button text={props.buttonText} />
</form>
);
LabeledInput.js
const LabeledInput = props => (
<div className={`form-group `} >
{ props.formValid && 'This form is valid' } // from Headcomponent's state
<Label text={props.label} />
<Input value={props.value} placeholder={props.placeholder} type="text" onChange={props.onChange} />
<div class="valid-feedback">{props.errorMessage}</div>
<small class="form-text text-muted">{props.exampleText}</small>
</div>
);
So if any time the Headcomponent's state is updated it will be propagated to the LabeledInput component

The simplest way to access the state of headComponent in LabeledInput in to keep passing it down.
If you want to access the value this.state.password from headComponent inside LabeledInput then you pass this this.state.password as a prop to the form component in the render method
render(){
return (
<Form
fields={this.props.form}
buttonText="Submit"
password={this.state.password} />
);
}
This then gives you access to this.state.password as a prop inside the Form component. You then repeat the process and pass it to the LabeledInput component inside form
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (
<LabeledInput
label={field.label}
placeholder={field.placeholder}
key={i}
password={this.props.password}
/>))
}
<Button text={props.buttonText} />
</form>
);
This then gives you access to the value inside the LabeledInput component by calling this.props.password.

You can use props to achieve this.
Root Component:
<div>
<child myState="hello"></child>
</div>
Child Component:
<div>
<child2 myOtherProperty={this.props.myState}></child2>
</div>
Child1 Component:
<div>{this.props.myOtherProperty}</div>
You can also pass functions as property to other components and let them call back the the root component if needed like so:
<div>
<child onChange={this.myOnChangeMethodDefinedInRootComponent.bind(this)}></child>
</div>
this way you can "tell" the root components if something changed inside the children without using Redux
hope this helps

Here is a quick prototype of what you are looking at,
passing state from head component as props to all the way down to label compoent. Changes in label component will modify the state of head component and force to re-render all components.
// Head Component
class HeadCompoent {
constructor() {
this.state = {
password: '',
userName: '',
}
}
handleFieldChange = (key, val) => {
this.setState({
[key]: val,
});
};
render() {
<Form
fields={[{
item: {
value: this.state.password,
type: 'password',
key: 'password'
},
}, {
item: {
value: this.state.userName,
type: 'text',
key: 'userName'
}
}]}
handleFieldChange={this.handleFieldChange}
/>
}
}
// Form Component
const Form = (fields) => (
<div>
{
fields.map(p => <Label {...p} />)
}
</div>);
// Label Component
const Label = ({ type, value, key, handleFieldChange }) => {
const handleChange = (key) => (e) => {
handleFieldChange(key, e.target.value);
};
return (
<input type={type} value={value} key={key} onChange={handleChange(key)} />
);
};

export default headComponent then import it inside LabelledInout
This is the Headcomponent
export default class Headcomponent extends React.Component{
...
}
LabelledInput Component
import Headcomponet from './headComponent.js'
const LabelledInput = (props) => {
return(<Headcomponent />);
}

Related

Add multiple input field dynamically in react

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>

React child component not fetching props and updating state

I have a form that uses a child component with input fields. I have used props to get the data from the parent component, but it's not adding the prop value to the input field. There is no errors in the console.
Parent component:
const {
address,
errors
} = this.state;
return (
<form noValidate autoComplete="off" onSubmit={this.onSubmit.bind(this)}>
<LocationInputGroup
errors={errors}
address={address}
/>
<Button
type="submit"
style={{ marginRight: 10, marginTop: 20 }}
variant="callToAction"
> Submit</Button>
</form>
);
Child component:
constructor(props) {
super(props);
this.state = {
address: "",
errors: {}
};
}
componentDidMount() {
this.setState({
errors: this.props.errors,
address: this.props.address
});
}
render() {
const {
address,
errors
} = this.state;
return (
<div>
<InputGroup
value={address}
error={errors.address}
label="Address"
name={"address"}
onChange={e => this.setState({ address: e.target.value })}
placeholder={"Address"}
/>
</div>
);
}
InputGroup component:
class InputGroup extends Component {
constructor(props) {
super(props);
}
//TODO check if scrolling still changes with number inputs
//Bug was in Chrome 73 https://www.chromestatus.com/features/6662647093133312
//If it's no longer a bug these listeners can be removed and the component changed back to a stateless component
handleWheel = e => e.preventDefault();
componentDidMount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).addEventListener("wheel", this.handleWheel);
}
}
componentWillUnmount() {
if (this.props.type === "number") {
ReactDOM.findDOMNode(this).removeEventListener("wheel", this.handleWheel);
}
}
render() {
const {
disabled,
classes,
error,
value,
name,
label,
placeholder,
type,
isSearch,
onChange,
onBlur,
onFocus,
multiline,
autoFocus,
InputProps = {},
autoComplete,
allowNegative,
labelProps
} = this.props;
if (type === "phone") {
InputProps.inputComponent = PhoneNumberInputMask;
}
let onChangeEvent = onChange;
//Stop them from entering negative numbers unless they explicitly allow them
if (type === "number" && !allowNegative) {
onChangeEvent = e => {
const numberString = e.target.value;
if (!isNaN(numberString) && Number(numberString) >= 0) {
onChange(e);
}
};
}
return (
<FormControl
className={classes.formControl}
error
aria-describedby={`%${name}-error-text`}
>
<FormatInputLabel {...labelProps}>{label}</FormatInputLabel>
<TextField
error={!!error}
id={name}
type={type}
value={value}
onChange={onChangeEvent}
margin="normal"
onBlur={onBlur}
onFocus={onFocus}
InputProps={{
...InputProps,
classes: {
input: classnames({
[classes.input]: true,
[classes.searchInput]: isSearch
})
}
}}
placeholder={placeholder}
multiline={multiline}
autoFocus={autoFocus}
disabled={disabled}
autoComplete={autoComplete}
onWheel={e => e.preventDefault()}
/>
<FormHelperText
className={classes.errorHelperText}
id={`${name}-error-text`}
>
{error}
</FormHelperText>
</FormControl>
);
}
}
InputGroup.defaultProps = {
value: "",
type: "text",
labelProps: {}
};
InputGroup.propTypes = {
error: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string.isRequired,
label: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.string,
isSearch: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
multiline: PropTypes.bool,
autoFocus: PropTypes.bool,
InputProps: PropTypes.object,
disabled: PropTypes.bool,
autoComplete: PropTypes.string,
allowNegative: PropTypes.bool,
labelProps: PropTypes.object
};
export default withStyles(styles)(InputGroup);
I hope someone can advise what the issue is and how to overcome it.
Normally when we pass data from parent, we consider it to be Immutable,
(i.e) It should not be changed directly from the child components.
So, what one could do is use the prop value directly from the parent and use a method to mutate or change from the parent.
Below code is self explanatory.
Parent
class Parent {
// parent immutable state
public parentObject: object = {
location: "",
phoneNo: ""
};
constructor() {
this.state = {
parentObject: this.parentObject
}
}
public onParentObjectChangeCallBack(key: string, value: string | number) {
this.state.parentObject[key] = value;
// to rerender
this.setState();
}
public render () {
return <ChildComponent parentObject={this.state.parentObject}
onChange={this.onParentObjectChangeCallBack} />
}
}
Child
class ChildComponent {
#Prop
public parentObject: object;
#Prop
public onChange: Function;
public render() {
<div>
{
this.parentObject.keys.map((key) => {
return <div>
<span> {{key}}</span>
// calls parent method to change the immutable object
<input value={{this.parentObject[key]}} onChange=(val) =>
this.onChange(key, val) />
</div>
})
}
</div>
}
}

How to reload/refresh a react-table component from my form component

I have two reactJS components:
CustomerForm component with a form and form handling code.
CustomerList component which lists the customers using react-table.
Both components are functional and I can use the form to add data to the database, and then fetch and display data in react-table.
But I can't figure out how to refresh the react-table data on successful form submission.
I am loading the components directly into an HTML page and using babel to process JSX. I am just starting our with reactJS and more used to PHP/jQuery development, so maybe I am approaching this wrong. Would appreciate any feedback.
My code:
CustomerForm
class CustomerForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: [], name: '', phone: '', nameErr: '', phoneErr: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const target = event.target;
const name = target.name;
this.setState({[name]: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
var self = this;
axios.post(APP_URL + '/customer/add', {
name: this.state.name,
phone: this.state.phone
}).then(function (response) {
//console.log(response);
var data = response.data;
if (data.status === 0) {
if (typeof data.payload === 'object') {
for (var key in data.payload) {
if (data.payload[key]) {
self.setState({[key]: data.payload[key]});
}
}
}
}
}).catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="container mt-3">
<form onSubmit={this.handleSubmit}>
<div className="row">
<div className="col-6">
<div className="form-group">
<label>Name</label>
<input name="name" type="text" className={'form-control ' + (this.state.nameErr ? 'is-invalid' : '')} placeholder="Enter name" value={this.state.value} onChange={this.handleChange} />
<div className="invalid-feedback">{this.state.nameErr}</div>
</div>
<div className="form-group">
<label>Email address</label>
<input name="phone" type="text" className="form-control" placeholder="Enter phone" value={this.state.value} onChange={this.handleChange} />
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
);
}
}
const domContainer = document.querySelector('#CustomerFormContainer');
ReactDOM.render(e(CustomerForm), domContainer);
Customer List
class CustomerList extends React.Component {
constructor(props) {
super(props);
this.state = {data: [], loading: false, pages: null};
this.fetchData = this.fetchData.bind(this);
}
fetchData(state, instance) {
var self = this;
this.setState({loading: true});
axios.post(APP_URL + '/customer/index', {
page: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
}).then(function (response) {
// handle success
self.setState({
data: response.data.payload,
pages: 1,
loading: false
});
}).catch(function (error) {
// handle error
console.log(error);
}).finally(function () {
// always executed
});
}
render() {
const {data, pages, loading} = this.state;
return (
<div className="container mt-3">
<ReactTable
columns={[
{
Header: "Name",
accessor: "name"
},
{
Header: "Phone",
accessor: "phone"
}
]}
manual
data={this.state.data}
pages={this.state.pages}
loading={this.state.loading}
onFetchData={this.fetchData}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
);
}
}
const domContainer = document.querySelector('#CustomerListContainer');
ReactDOM.render(e(CustomerList), domContainer);
TL;DR
Render both components in a parent component. Lift the table state up and pass a function to the form that will call the function to fetch data.
First of all, one quick tip. Render only one component in one div and then nest all the other components inside.
In both files, you are rendering the component in diferent selectors
CustomerForm
const domContainer = document.querySelector('#CustomerFormContainer');
ReactDOM.render(e(CustomerForm), domContainer);
Customer List
const domContainer = document.querySelector('#CustomerListContainer');
ReactDOM.render(e(CustomerList), domContainer);
This isn't good because this way, it's not easy to share state and props between then.
What you should do is have a root component that render both components.
class App extends React.Component {
render() {
return (
<CustomerForm />
<CustomerList />
)
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(App), domContainer);
OBSERVATION
I'm not sure what is the e function and why you are using, but I assume this won't change anything, but please, specify what it is.
Doing this, you can share state and props between components.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: [], loading: false, pages: null };
this.fetchData = this.fetchData.bind(this);
this.reloadData = this.reloadData.bind(this);
}
reloadData() {
this.fetchData(state);
}
fetchData(state, instance) {
var self = this;
this.setState({ loading: true });
axios
.post(APP_URL + "/customer/index", {
page: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
})
.then(function(response) {
// handle success
self.setState({
data: response.data.payload,
pages: 1,
loading: false
});
})
.catch(function(error) {
// handle error
console.log(error);
})
.finally(function() {
// always executed
});
}
render() {
return (
<div>
<CustomerForm reloadData={reloadData} />
<CustomerList data={data} pages={pages} loading={loading} fetchData={fetchData} />
</div>
);
}
}
Here what I'm doing is called Lifting State Up. I'm getting the state of CustomerList to the parent component so you can reload the data when the form calls reloadData.
This way you also need to change CustomerList to get the data from this.props and in CustomerForm call reloadData when the submit is success.
Customer List
render() { // getting from props
const { data, pages, loading, fetchData } = this.props;
return (
<div className="container mt-3">
<ReactTable
columns={[
{
Header: "Name",
accessor: "name"
},
{
Header: "Phone",
accessor: "phone"
}
]}
manual
data={data}
pages={pages}
loading={loading}
onFetchData={fetchData}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
);
}
CustomerForm
Call this.props.reloadData() when the submit is success.
*I don't know when you want to reload the data, it's not clear when is a succes.

How to render data on submit in child component

I have a simple form on child component A. On submit I'm passing the data from the form to the parent component and storing the data in state.. I want to then move this data to a different child, child component B.(the sibling of A)
I'm having trouble getting the data to be rendered on submit in component B. I'm not sure how to trigger the rendering on submit or how to pass this information via props on submit.
Here is the Parent
class Msg extends React.Component {
constructor(props) {
super(props);
this.storeInput = this.storeInput.bind(this);
this.state = {
name: '',
msg: ''
};
}
storeInput (d) {
this.setState({
name: d.name,
msg: d.msg
})
}
render() {
return(
<div className='msgContainer'>
<Input
passBack={this.storeInput}/>
<Output />
</div>
)
}
}
Here is Component A
class Input extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
name: '',
msg: ''
};
}
handleChange(e) {
this.setState({[e.target.name]: e.target.value})
}
handleSubmit(e) {
e.preventDefault();
this.props.passBack(this.state);
}
render () {
const name = this.state.name;
const msg = this.state.msg;
return (
<div className='form-container'>
<form action="">
<label htmlFor="">name</label>
<input
name='name'
value={name}
onChange={this.handleChange}
type='text'></input>
<label htmlFor="">message</label>
<textarea
name='msg'
value={msg}
onChange={this.handleChange}
rows='5' cols='80'></textarea>
<input
onClick={this.handleSubmit}
type='submit'></input>
</form>
</div>
)
}
}
Here is Component B
class Output extends React.Component {
render () {
return(
<div className='output'>
</div>
)
}
}
Simply pass the state as props to Output like so:
Parent Component:
import React from 'react';
import Input from './Input';
import Output from './Output';
class Msg extends React.Component {
state = { name: '', msg: '' };
storeInput = d => {
this.setState({ name: d.name, msg: d.msg });
};
render() {
// destructure the state
const { name, msg } = this.state;
return (
<div className="msgContainer">
<Input passBack={this.storeInput} />
{/* pass the state as props to Output */}
<Output name={name} msg={msg} />
</div>
);
}
}
export default Msg;
Input.js
import React from 'react';
class Input extends React.Component {
state = { name: '', msg: '' };
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = e => {
e.preventDefault();
this.props.passBack(this.state);
this.setState({ name: '', msg: '' }); // clear up the input after submit
};
render() {
const { name, msg } = this.state;
return (
<div className="form-container">
<form onSubmit={this.handleSubmit}>
<label htmlFor="">name</label>
<input
name="name"
value={name}
onChange={this.handleChange}
type="text"
/>
<label htmlFor="">message</label>
<textarea
name="msg"
value={msg}
onChange={this.handleChange}
rows="5"
cols="80"
/>
<input type="submit" />
</form>
</div>
);
}
}
export default Input;
Output.js
import React from 'react';
// destructure the props coming in from Msg
// no need for a class-based component
const Output = ({ name, msg }) => (
<div className="output">
<div>Output</div>
<p>{name}</p>
<p>{msg}</p>
</div>
);
export default Output;
Live demo: https://jsfiddle.net/c8th67zn/

Pass down onChange to child component in react

I have a react component and I want to pass down onChange as a prop to a child component, I'm using atomic design, here's my code:
HeadComponent.js
class Headcomponent extends React.Component{
constructor (props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
}
handleUserInput = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},
() => { this.validateField(name, value) });
}
render(){
const fields= [
{
label: 'hhdghghd',
placeholder: 'fhfhhfhh 1',
ExampleMessage: this.state.formErrors.email ,
ErrorMessage: 'error message for input 1',
inputValue: this.state.email,
onChange: this.handleUserInput //this is where I'm passing my function
},
{
label: 'fffff',
placeholder: 'tttttt 2',
ExampleMessage: 'example message for second label',
ErrorMessage: 'error message for input 2',
onChange: this.handleUserInput
},
]
return (
<div>
<Form fields={fields} buttonText="Submit"/>
</div>
);
}
}
export default Headcomponent;
Form.js
const Form = props => (
<form className="Form">
{
props.fields.map((field, i) => (<LabeledInput label={field.label}
placeholder={field.placeholder}
ExampleMessage={field.ExampleMessage}
ErrorMessage={field.ErrorMessage}
onChange= {(e)=> field.onChange}
inputValue= {field.inputValue}
key={i}
passwordError={props.passwordError}
/>))
}
<Button text={props.buttonText} />
</form>
);
Form.propTypes = {
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonText: PropTypes.string.isRequired,
};
export default Form;
LabeledInput
const LabeledInput = props => (
<div className={`form-group `} >
<Label text={props.label} />
<Input inputValue={props.inputValue} placeholder={props.placeholder} type="text" onChange={(e)=> props.onChange} />
<ErrorMessage text={props.ErrorMessage} />
<ExampleMessage ExampleMessage={ props.ExampleMessage} />
</div>
);
LabeledInput.propTypes = {
label: PropTypes.string.isRequired,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired,
//value: PropTypes.string.isRequired,
exampleText: PropTypes.string,
};
export default LabeledInput;
Input.js
const Input = props => (
<input type={props.type} class="form-control form-control-success is-valid" placeholder={props.placeholder} value={props.inputValue} className="form-control form-control-success"
onChange={ (e)=> props.onChange } />
);
Input.propTypes = {
inputValue: PropTypes.string,
type: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default Input;
How to pass handleUserInput from HeadComponent.js down to Input.js, so far using props I can't get it to trigger while changing text.
You forgot to actually call field.onChange method. Instead of :
onChange= {(e)=> field.onChange}
You can change it to:
onChange= {field.onChange}
I also noticed that your handleUserInput set state at :
{ [name]: e.target.value }
However, you are not setting name to any of the inputs and that's not gonna work.
Please have a look at my working sample:
const LabeledInput = props => (
<div className="form-group">
<label>{props.label}</label>
<input
className="form-control"
type="text"
placeholder={props.placeholder}
onChange={props.onChange} // That's how you have to call onChange
name={props.name} // You are not setting this prop
/>
<div>{props.ErrorMessage}</div>
<div>{props.ExampleMessage}</div>
</div>
)
const Form = ({ buttonText, fields }) => (
<form className="Form">
{fields.map((field, i) => <LabeledInput key={i} {...field} />)}
<button className="btn btn-primary">{buttonText}</button>
</form>
)
class Headcomponent extends React.Component {
constructor() {
super()
this.state = {
email: '',
password: '',
formErrors: {email: '', password: ''},
emailValid: false,
passwordValid: false,
formValid: false,
items: [],
}
this.handleUserInput = this.handleUserInput.bind(this)
}
handleUserInput(e) {
const name = e.target.name
const value = e.target.value
// Now that you have `name` and `value`, state updates are going to work
this.setState({
[name]: value
})
}
render() {
const fields = [
{
label: 'Email',
placeholder: 'email placeholder',
ExampleMessage: this.state.formErrors.email ,
ErrorMessage: 'error message for input 1',
inputValue: this.state.email,
onChange: this.handleUserInput,
name: 'email',
},
{
label: 'Password',
placeholder: 'password placeholder',
ExampleMessage: 'example message for second label',
ErrorMessage: 'error message for input 2',
onChange: this.handleUserInput,
name: 'password',
},
]
return (
<div>
<Form fields={fields} buttonText="Submit"/>
<div style={{ marginTop: 20 }}>
<div>state.email: {this.state.email}</div>
<div>state.password: {this.state.password}</div>
</div>
</div>
)
}
}
ReactDOM.render(
<Headcomponent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<div id="root"></div>

Categories