Add multiple input field dynamically in react - javascript

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>

Related

Hide form after submit (but after bringing it back?)

I'm having a hard time making this form submit twice. What do I mean? When I click on submit, the form (on General.js) is hidden and it renders another component (DisplayGeneral.js) displaying the user's input. Then I added a button to bring back the form so the user can edit the information, the problem is that it doesn't toggle again when I click on the form button, you know? What am I missing here? Please, I appreciate any help.
General.js
import React, { Component } from 'react';
import '../style/style.css';
import DisplayGeneral from './DisplayGeneral';
class General extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
showing: true,
isEditing: true,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({
[name]: value,
});
}
handleSubmit(e) {
e.preventDefault();
const { showing } = this.state;
this.setState({
showing: !showing,
});
}
handleClick(e) {
e.preventDefault();
const { isEditing } = this.state;
this.setState({
isEditing: !isEditing,
});
}
form = () => {
return (
<div>
<form className='generalForm'>
<label htmlFor='nameInput' className='row'>
Name:{' '}
<input
type='text'
name='name'
value={this.state.name}
onChange={this.handleChange}
id='nameInput'
/>
</label>
<button onClick={this.handleSubmit}>Submit</button>
</form>
</div>
);
};
renderGeneral() {
const {
isEditing,
name,
} = this.state;
return (
<div>
{isEditing ? (
<div>
<DisplayGeneral
handleSubmit={this.handleSubmit}
name={name}
/>
<button onClick={this.handleClick}>Edit</button> // button added
</div>
) : (
this.form()
)}
</div>
);
}
render() {
const { showing } = this.state;
return (
<section className='divGeneral'>
<div className='sectionTitle'>
<h1>General Information</h1>
</div>
{showing ? this.form() : this.renderGeneral()}
</section>
);
}
}
export default General;
DisplayGeneral.js
import React, { Component } from 'react';
class DisplayGeneral extends Component {
render() {
const { name } = this.props;
return (
<section className='renderedSection'>
<article>
<span className='spanArtc'>Name: </span>
{name}
</article>
</section>
);
}
}
export default DisplayGeneral;
If you just want to toggle the form you can keep only one flag isEditing and change it to true, if you want to show the form and false for showing information.
import React, { Component } from 'react';
import '../style/style.css';
import DisplayGeneral from './DisplayGeneral';
class General extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
isEditing: true,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({
[name]: value,
});
}
handleSubmit(e) {
e.preventDefault();
this.setState({
isEditing: false,
});
}
handleClick(e) {
e.preventDefault();
this.setState({
isEditing: true,
});
}
form = () => {
return (
<div>
<form className='generalForm'>
<label htmlFor='nameInput' className='row'>
Name:{' '}
<input
type='text'
name='name'
value={this.state.name}
onChange={this.handleChange}
id='nameInput'
/>
</label>
<button onClick={this.handleSubmit}>Submit</button>
</form>
</div>
);
};
renderGeneral() {
const { name } = this.state;
return (
<div>
<DisplayGeneral handleSubmit={this.handleSubmit} name={name} />
<button onClick={this.handleClick}>Edit</button> // button added
</div>
);
}
render() {
const { isEditing } = this.state;
return (
<section className='divGeneral'>
<div className='sectionTitle'>
<h1>General Information</h1>
</div>
{isEditing ? this.form() : this.renderGeneral()}
</section>
);
}
}
export default General;

How to change Antd form initialValues depends at url or id?

I got same component with Antd form for add/edit article. With pathes in router
<Route path="/add" component={ !currentUser ? Login : ArticleEditor } />
<Route path="/article/:id/edit" component={ !currentUser ? Login : ArticleEditor } />
When I click "edit" button I add initialValues to form, than if I click "Create new article" url changes to "/add", but form didn't update values. Values remains from edited article. How to update form values? Tried to set initialValues depends at path, or "id" but its not worked. How to update antd form values in that case?
const initialValues = this.props.location.pathname === '/add' ? {} : {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description
};
Here you can see the component code - codesandbox link
The main issue with the code is form fields are not reset when url is changed, you can detect path change in shouldComponentUpdate and set isLoading to true and rest should work.
Updating initialValues will not work because, antd does shallow compare and once initialValues are set, you will not be able to change them.
There was an issue in the logic of componentDidUpdate which I corrected as well.
import React from "react";
import ErrorsList from "../ErrorsList/ErrorsList";
import userService from "../../services/userService";
import { connect } from "react-redux";
import { push } from "react-router-redux";
import { Form, Input, Button } from "antd";
import { store } from "../../store";
import actionCreators from "../../actionCreators";
const formItemLayout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 }
};
const formSingleItemLayout = {
wrapperCol: { span: 24, offset: 0 }
};
const mapStateToProps = (state) => ({
...state.editor
});
const mapDispatchToProps = (dispatch) => ({
onLoad: (payload) => dispatch(actionCreators.doEditorLoaded(payload)),
onUnload: () => dispatch(actionCreators.doEditorUnloaded()),
onUpdateField: (key, value) =>
dispatch(actionCreators.doUpdateFieldEditor(key, value)),
onSubmit: (payload, slug) => {
dispatch(actionCreators.doArticleSubmitted(payload));
store.dispatch(push(`/`)); //article/${slug}
},
onRedirect: () => dispatch(actionCreators.doRedirect())
});
class ArticleEditor extends React.Component {
constructor(props) {
super(props);
this.id = this.props.match.params.id;
const updateFieldEvent = (key) => (e) =>
this.props.onUpdateField(key, e.target.value);
this.changeTitle = updateFieldEvent("title");
this.changeDescription = updateFieldEvent("description");
this.changeBody = updateFieldEvent("body");
this.changeTagInput = updateFieldEvent("tagInput");
this.isLoading = true;
this.submitForm = () => {
const article = {
title: this.props.title,
description: this.props.description,
body: this.props.body,
tagList: this.props.tagInput.split(",")
};
const slug = { slug: this.props.articleSlug };
const promise = this.props.articleSlug
? userService.articles.update(Object.assign(article, slug))
: userService.articles.create(article);
this.props.onSubmit(promise, this.props.articleSlug);
};
}
componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.id !== prevProps.match.params.id) {
if (prevProps.match.params.id) {
this.props.onUnload();
}
this.id = this.props.match.params.id;
if (this.id) {
return this.props.onLoad(userService.articles.get(this.id));
}
this.props.onLoad(null);
}
this.isLoading = false;
}
componentDidMount() {
if (this.id) {
this.isLoading = true;
return this.props.onLoad(userService.articles.get(this.id));
}
this.isLoading = false;
this.props.onLoad(null);
}
componentWillUnmount() {
this.props.onUnload();
}
shouldComponentUpdate(newProps, newState) {
if (this.props.match.params.id !== newProps.match.params.id) {
this.isLoading = true;
}
return true;
}
render() {
const { errors } = this.props;
const initialValues = {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description,
tags: this.props?.tagList
};
return this.isLoading ? (
"loading..."
) : (
<div className="editor-page">
<div className="container page">
<div className="">
<div className="">
<ErrorsList errors={errors}></ErrorsList>
<Form
{...formItemLayout}
initialValues={initialValues}
onFinish={this.submitForm}
>
<Form.Item
label="Title"
name="title"
placeholder="Article Title"
rules={[
{
required: true,
message: "Please input article title"
}
]}
>
<Input onChange={this.changeTitle} />
</Form.Item>
<Form.Item
label="Description"
name="description"
placeholder="Short description"
rules={[
{
required: true,
message: "Please input article description"
}
]}
>
<Input onChange={this.changeDescription} />
</Form.Item>
<Form.Item
name="body"
label="Article Text"
placeholder="article text"
>
<Input.TextArea onChange={this.changeBody} />
</Form.Item>
<Form.Item name="tags" label="Tags" placeholder="Enter tags">
<Input onChange={this.changeTagInput} />
</Form.Item>
<Form.Item {...formSingleItemLayout}>
<Button
className="editor-form__btn"
type="primary"
htmlType="submit"
disabled={this.props.inProgress}
>
Submit Article
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleEditor);
take a look at this forked codesandbox.
You have to clean the fields before you re-use the 'ArticleEditor' component. Here you are using the same component for two different route, hence it's not changing.
You have to check if you are editing or adding a new entry to the Editor. Your editor component may look like this then,
const ArticleEditor = props => {
const [form] = Form.useForm();
useEffect(() => {
if (props.match.params.id) form.setFieldsValue({value : 'Some values'})
else form.resetFields()
}, [props?.match?.params]);
return (
<Form form={form} onFinish={yourFinishMethod}>
//...your form fields
</Form>
)
}

Can someone help me how to render immediately after the put and delete request

class App extends Component {
constructor() {
super();
this.state = {
currentProduct: null,
items: [],
};
this.handlepostSubmit= this.handlepostSubmit.bind(this);
}
componentDidMount() {
axios.get('http://localhost:3000/api/v1/products.json')
.then(res => {
const items = res.data;
this.setState({ items });
})}
handlepostSubmit = event => {
event.preventDefault();
const product = {
name: event.target[0].value,
style_no: event.target[1].value,
color: event.target[2].value,
material: event.target[3].value,
origin: event.target[4].value,
};
let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
axios.defaults.headers.common['X-CSRF-Token'] = token;
axios.defaults.headers.common['Accept'] = 'application/json'
axios.put(`http://localhost:3000/api/v1/products/${this.state.currentProduct.id}`, {product})
.then(res => {
console.log(res);
console.log(res.data);
})
}
handleSubmit = event => {
event.preventDefault();
axios.delete
(`http://localhost:3000/api/v1/products/${this.state.currentProduct.id}`)
.then(res => {
})
}
render() {
const products = []
this.state.items.map(person =>
products.push(person))
return (
<div>
<div>
<Sidebar products={products} onSelect={product => this.setState({currentProduct: product})}/>
</div>
<div>
<Form product={this.state.currentProduct} />
</div>
<div>
<form onSubmit={this.handlepostSubmit}>
<label>Name:<input type="text" /></label>
<label>Style_no:<input type="text"/></label>
<label>Color:<input type="text" /></label>
<label>material<input type="text" /></label>
<label>Orgin<input type="text" /></label>
<input type="submit" value="Edit" />
</form>
</div>
<button onClick={this.handleSubmit}>Delete</button>
</div>
);}}
export default App
Right now, I am facing difficulties with how to render the component after the put and delete request. In the code above, after I click the edit and delete button, it does not render on the page immediately. I have to refresh the page to get the new information. Can someone give me information how to do this kind of stuff.
// APP COMPONENT
import React, { Component } from 'react';
import axios from 'axios';
import Sidebar from './sidebar';
import Form from './form';
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentProduct: null,
products: [],
name: '',
styleNo: '',
color: '',
material: '',
origin: '',
};
this.handlepostSubmit = this.handlepostSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleProductSelected = this.handleProductSelected.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
// forceUpdateHandler(){
// this.forceUpdate();
// };
handleChange(e) {
const name = e.target.name;
this.setState({
[name]: e.target.value,
});
}
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts').then(res => {
const items = res.data;
console.log(items);
this.setState({ products: items });
});
// http://localhost:3000/api/v1/products.json
}
handlepostSubmit(e) {
e.preventDefault();
const { name, styleNo, color, material, origin } = this.state;
console.log('selected name ', name);
const product = {
name,
style_no: styleNo,
color,
material,
origin,
};
console.log(product);
// let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// axios.defaults.headers.common['X-CSRF-Token'] = token;
// axios.defaults.headers.common['Accept'] = 'application/json'
// axios.put(`http://localhost:3000/api/v1/products/${this.state.currentProduct.id}`, {product})
// .then(res => {
// })
}
handleDelete(e) {
e.preventDefault();
console.log('on delete');
// axios.delete(`http://localhost:3000/api/v1/products/${this.state.currentProduct.id}`)
// .then(res => {
// })
}
// forceUpdate =event=>{
// location.reload(true);
// }
handleProductSelected(product) {
this.setState({
currentProduct: product,
});
}
render() {
const { products } = this.state;
return (
<div>
<div>
<Form product={this.state.currentProduct} />
</div>
<div>
<form onSubmit={this.handlepostSubmit}>
<label>
Name:
<input type="text" name="name" onChange={this.handleChange} />
</label>
<label>
Style_no:
<input type="text" name="styleNo" onChange={this.handleChange} />
</label>
<label>
Color:
<input type="text" name="color" onChange={this.handleChange} />
</label>
<label>
material
<input type="text" name="material" onChange={this.handleChange} />
</label>
<label>
Orgin
<input type="text" name="origin" onChange={this.handleChange} />
</label>
<input type="submit" value="Edit" onChange={this.handleChange} />
</form>
</div>
<div>
<button onClick={this.handleDelete}>Delete</button>
</div>
<div>
<Sidebar products={products} onSelect={this.handleProductSelected} />
</div>
</div>`enter code here`
);
}
}
export default App;
//Sidebar Component
import React from 'react';
class SideBar extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(product) {
const { onSelect } = this.props;
onSelect(product);
}
render() {
const { products } = this.props;
return products.map(product => {
// let boundItemClick = onItemClick.bind(this, x);
return (
<li key={product.id} onClick={() => this.handleClick(product)}>
<p> {product.id} </p>
{/* <p> {product.style_no}</p>
<p> {product.color}</p> */}
</li>
);
});
}
}
export default SideBar;
import React from 'react';
class Form extends React.Component {
render() {
const { product } = this.props;
return (
<section id="product">
<p>selected product: {product ? product.id : 'no product'} </p>
</section>
);
}
}
export default Form;

How to update the state of array of objects from form fields?

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>
);
}

Access state of parent component in a child component?

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 />);
}

Categories