How to recursively pass up all data from form? - reactjs

I've got a form, it looks like this:
export default class BookingForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: props.data};
}
render() {
const {booking, vehicleSelect, vehicleData, customer, drivers, fees, occasions} = this.props;
return (
<form className="grid-form">
<div className="row">
<div className="col">
<label>Is this a new or returning customer?</label>
<RadioMenu name="repeat_customer">
<RadioButton value="NEW">New Customer</RadioButton>
<RadioButton value="EXIST">Returning Customer</RadioButton>
</RadioMenu>
</div>
</div>
<div className="row new-customer-row">
<div className="col-1-2">
<label htmlFor="customer.first_name">First Name</label>
<Input id="customer.first_name" name="customer.first_name" type="text"/>
</div>
<div className="col-1-2">
<label htmlFor="customer.last_name">Last Name</label>
<Input id="customer.last_name" name="customer.last_name" type="text"/>
</div>
</div>
// .. more inputs ..
Where <RadioMenu> renders a list of <RadioButton>s which in turn contain an <Input>.
<Input> just looks like this:
export default function Input(attrs) {
return <input {...attrs}/>;
}
I made it a React component hoping I can do something useful with it.
Basically, I want all the form data to be stuffed into this.data.INPUT_NAME as soon as the input is changed. If the input name contains a . then I want to put it into a sub-object. For example, customer.last_name will be stored in this.state.data.customer.last_name. I also want to to use this.state.data to set the initial value for all the Input elements without having to explicitly add a value attribute to each of them; it should just know what value to pull out of the data object by using the input's name.
I don't know how to approach this. My first thought is that instead of returning the <form> I should put it into a variable, and then pre-process it, adding onChange and value attributes to anything of type Input, but even I try that, I don't think it would work on my RadioMenu because RadioMenu is not of type Input and I don't think I could recurse down into its children.
I could try using this context feature but the warnings are scaring me away.
I haven't looked into Flux/Reflux/Redux/xyz yet, but I don't think I really want to incorporate another framework this early in the game; I want to understand how to approach this properly before tucking it away.
So, how can I get all my form data into this.state.data?
The radio widgets look like this. I'm open to changing them if necessary. This is my first custom input widget.
// RadioMenu.jsx
import React from 'react';
import {cloneWithProps} from '../helpers/react-helpers';
import Input from './Input';
export default class RadioMenu extends Input {
constructor(props) {
super(props);
this.state = {value: props.value};
}
onChange = ev => {
this.setState({value: ev.target.value});
if(this.props.onChange) {
this.props.onChange(ev);
}
};
render() {
let {children, name, onChange, ...attrs} = this.props;
return (
<div className="radio-horizontal radio-menu" {...attrs}>
{cloneWithProps(children, btn => ({
name,
checked: btn.props.value == this.state.value,
onChange: this.onChange
}))}
</div>
);
}
}
// RadioButton.jsx
export default function RadioButton({children, ...attrs}) {
return (
<label className="checkable">
<input type="radio" {...attrs}/>
<span>{children}</span>
</label>
);
}
I was trying to use inheritance has so I could pluck out all the Inputs, regardless if they're custom or not, but I can't seem to get this to work in React. mycomp.type instanceof Input doesn't return true for sub-classes. I know React suggests composition over inheritance, but I don't know how to make that work.

This kind of problem is the reason we have libraries/patterns like Redux/Flux, but that doesn't mean it's not possible to solve without React, just a little bit harder.
In this specific case, you have a few options.
Child-Parent Events
If you change your <RadioButton /> component to accept an onChange handler, then you can listen for changes to the button and put them straight into your state.
function RadioButton(props) {
return (
// pass the onChange prop down
<input type="radio" onChange={props.onChange} />
);
}
Then update your <BookingForm /> component to make use of this new handler prop.
const setRadioState = e => this.setState({ radio: e.target.value });
// ...
<RadioMenu name="repeat_customer">
<RadioButton value="NEW" onChange={setRadioState}>New Customer</RadioButton>
<RadioButton value="EXIST" onChange={setRadioState}>Returning Customer</RadioButton>
</RadioMenu>
Accessing the Form
You can listen to the form for the submit event then iterate through the form's elements to build up an object you can put in your state.
render() {
// ...
<form onSubmit={this.onSubmit}>
// ...
},
onSubmit(e) {
const form = e.target;
const elements = form.elements;
// remove numeric keys
const keys = Object.keys(elements).filter(k => /[^\d]/.test(k);
const data = {};
keys.forEach(k => data[k] = elements[k].value);
this.setState(data);
}
If you aren't listening to the submit event and want to submit with say, a button press, then you'll need to use refs to get a instance of the form.
I'm more or less just making this approach up off the top of my head, so be wary of edge cases.

ReactLink will do what you want, but it's on its way out the door. Fortunately, it's easy to recreate this functionality in just a few lines of code.
Instead of using <input>, you can use this component:
import React from 'react';
export default class LinkedStateInput extends React.Component {
render() {
const {value, ...attrs} = this.props;
return <input {...attrs} value={value.value} onChange={ev => value.requestChange(ev.target.value)} />;
}
}
Usage example:
<LinkedStateInput value={this.linkState('passenger_count')} type="text"/>
Now just add a method to your BookingForm to handle the state updates:
linkState(name) {
return {
value: _.get(this.state.data, name, ''),
requestChange: value => {
let data = _.clone(this.state.data);
_.set(data, name, value);
this.setState({data})
}
}
}
I've used lodash here to handle deep sets/gets.
RadioMenu becomes even simpler because now it doesn't even have to remember its own state:
export default function RadioMenu({children, name, valueLink}) {
return (
<div className="radio-horizontal radio-menu">
{
valueLink
? cloneWithProps(children, btn => ({
name,
checked: btn.props.value === valueLink.value,
onChange: ev => valueLink.requestChange(ev.target.value)
}))
: cloneWithProps(children, {name})
}
</div>
);
}

Related

How to store Key-Value pairs in a list or library in React JS

I want to store unique key and value in my class component state(selectedFeatures) as a list of key-value pairs, by taking the arguments which are passed from the body. ex: {“user1”:"opt1", “user2”:"opt3"}. The key must be unique, which means if the same key received from the body value should be updated to the relevant key which is stored previously
I did it in this way and it gives an error as “this.state.selectedFeatures is not iterable”. Therefore how to resolve this.
import React, { Component} from 'react';
import {features} from '../../services';
class UserData extends Component {
constructor(props) {
super(props)
this.state = {
featureTypes:[],
selectedFeatures:{}
}
}
getSelectedFeatures =(event,keyValue)=>{
const features = {};
features[keyValue] = event.target.value
this.setState({selectedFeatures: features})
}
componentDidMount(){
//http request from service component
features().then(response => {
this.setState({featureTypes:response})
})
.catch(error => {
console.log(error)
})
}
render() {
const {featureTypes} = this.state
return (
<div>
{featureTypes.map((feature, i) => (
<div key={i}>
<label>
<h4>
{feature.feature}
</h4>
</label>
<select
defaultValue="select-feature"
onChange={(event) => this.getSelectedFeatures(event, feature.feature)}>
{feature.types.map((type, i) => (
<option key={i} value={type}>
{type}
</option>
))}
</select>
</div>
))}
</div>
)
}
}
export default UserData
Without creating an array just simply adding to an object can be achieved via this method
addTo = (event, val) => {
this.setState((prev) => ({
selectedFeatures: {
...prev.selectedFeatures,
[event.target.value]: val
}
}));
};
NOTE I changed the name from getSelectedFeatures to addTo from simplicity + because get would mean it returns something. In this case, you send and add it.
the prev is a previews state that was before the state change.
Also I went a step further and created a demo project https://codesandbox.io/s/flamboyant-lake-4rvfz?file=/src/App.js
Inside of it there are multiple different containers that you may click and it will save what has been click as a event.target and a value they themself send to the method. If the container is clicked again OR a different but same type container is clicked, the value is overriten rather then added as a new parameter. Its constructed using class, as in your code. This is a simple quick demo that, after analizing, you may adapt to it as you wish with any code you want.

React wrong & right practices with form elements

Hyall
Can you please point out bad practices / mistakes in the code below?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "default title"
};
this.inputTxt = this.state.title;
this.myRef = React.createRef();
}
componentDidMount() {
this.myRef.current.value = this.inputTxt;
}
handleSubmit = e => {
e.preventDefault();
console.log("submitted");
this.setState({ ...this.state, title: this.inputTxt });
};
handleInput = e => {
this.inputTxt = e.target.value;
};
render() {
return (
<>
<div>{this.state.title}</div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
onChange={this.handleInput}
ref={this.myRef}
></input>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
</>
);
}
}
And some special questions:
is it ok to use this.somevar properties of component class to store variables' values? how to avoid naming collisions?
is it normal to use refs to set input's value?
if I want to set onChange and value bound to reactive variable in one input control, it will freeze? how to gain [(ngModel)] Angular-like control over input element?
It seems like you're over complicating things. I don't see a need for refs here. I don't think setting a class property will trigger a re-render, so this way of managing input might not work at all regardless of it not being a best practice.
Just use state as the value, and update state on change. To keep things flexible, use the input's name as the state key. Something like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "default title"
};
}
handleSubmit = e => {
e.preventDefault();
console.log("submitted");
// Not sure if thats what you're looking for..
// Also: no need to do {...this.state, }. setState does a merge, not overwrite
this.setState({ title: this.state.input1 });
};
handleChange = e => {
// Use e.target.name as the computed property name,
// so it can be used for infinite number of inputs
this.setState({[e.target.name]: e.target.value});
};
render() {
return (
<>
<div>{this.state.title}</div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="input1" // Give it a unique name for setting state
value={this.state.input1} // Specify the value instead of using a ref
onChange={this.handleChange}
></input>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
</>
);
}
}
Here is the link to the react docs on refs.
The primary they recommend use-cases are:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
Which I don't believe apply, here. So I wouldn't recommend using them here.

React Component for editting data

after getting data from API I want to show them into inputs,edit and update it in DB. I thought that beside the redux state, I should use also local state , but some people here say that is not good practise .So how I can handle my onChange methods and how pass updated data into axios.put method???
class ArticleEdit extends Component {
articleID = this.props.match.params.articleID;
state={
title:'',
text:'',
imgs:[]
}
onChange =(e)=>{}
componentDidMount(){
this.props.getArticleDetails(this.articleID);//get data from API
}
render() {
return (
<Fragment>
{this.props.article===undefined?(<Spin/>):
(
<div >
<div >
<Form onSubmit={this.handleSubmit}>
<Input name='title'
value='this.props.article.title'
onChange={this.onChange}/>
<Textarea
name='text'
value={this.props.article.title}
onChange={this.onChange}/>
<Button htmlType='submit'>Update</Button>
</Form>
</div>
</div>
)}
</Fragment>
)
}
}
const mapStateToProps = state =>({
article: state.articleReducer.articles[0],
})
export default connect(mapStateToProps,{getArticleDetails})
(ArticleEdit);
So how I can handle my onChange methods and how pass updated data into
axios.put method???
Well if that's literally what you want to do, then you can do it like this:
onChange = e => {
try {
const results = await axios.put(someurl, e.target.value)
console.log('results', results)
} catch(e) {
console.log('err', e)
}
}
This will call axios.put after every keystroke - however, I doubt that is what you want.
I've found the solution. I used the static method getDerivedStateFromProps,which calls everytime when props of your component has been changed.
static getDerivedStateFromProps(nextProps, prevState){
if(prevState.title===null && nextProps.article!==undefined){
return{
title:nextProps.article.title,
text:nextProps.article.text
}
}
return null;
}
after that is easy to work with onChange method ,which just call this.setState().

Using Arrow function for class properties in React. Not clear

I came across the arrow function feature being used as Class property in React component. Looking online I found that it makes code more readable and because of the arrow function features we do not have to bind the handlEvents function inside of constructor.
I still have to use the bind method even while using an arrow function for class property as shown in the code below. When i remove the binding in constructor it shows error in console Warning: A component is changing an uncontrolled input of type text to be controlled. and the form errors do not show up as well
class Contact extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur(this);
}
handleBlur = evt => field => {
this.setState({
touched: { ...this.state.touched, [field]: true }
});
render() {
return(
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Label htmlFor="firstname" md={2}>
First Name
</Label>
<Col md={10}>
<Input
type="text"
id="firstname"
name="firstname"
placeholder="First Name"
valid={errors.firstname === ""}
invalid={errors.firstname !== ""}
value={this.state.firstname}
onBlur={event => {
this.handleBlur("firstname");
}}
onChange={this.handleInputChange}
/>
<FormFeedback>{errors.firstname}</FormFeedback>
</Col>
</FormGroup>
</Form>
)
}
Arrow functions for early bindings in classes are not officially supported by the current ECMAScript.
Using arrow functions as class methods will get you in trouble when your class is inherited and the child wants to override a parent method.
However, I would say it is pretty safe to use them in your react components as you will not get into trouble with inheritance here, since with react you usually will not further inherit from your own components (see Composition vs Inheritance):
At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.
Dan Abramov is using arrow functions in component methods as well, however he recommends only to use it if early binding is required.
While it’s still experimental, in my experience it solves the problem fairly nicely. It’s not at all React-specific: I find it useful in any classes that deal with asynchrony and callbacks because the binding problem is common for all JavaScript, not just React. We enabled this syntax proposal in the whole Facebook codebase, and if it gets dropped or changes, we’ll make sure to release an automated codemod to migrate to the new syntax (or, worst case, transform it back into bind calls in constructor).
However as Dan notes, to be on the safe site, stick to early binding in constructors:
If you want to stick to the language standard, manual binding in
constructor is the way to go. It’s tedious but usually you only want
to do this for event handlers, and by convention you start them with
handle* in React, so it’s not too hard to remember to bind those.
Update: regarding your case:
In your case you can either use the solution provided by Anshul Bansal where you pass the fieldname into your handleBlur and make use of the field variable in your closure when you pass the returned function as event callback.
Or you can directly acces the input name of the field via the evt.target (code not tested).
handleBlur = evt => {
const field = evt.target.name;
this.setState({
touched: { ...this.state.touched, [field]: true }
});
You need to change the function a little bit as follow.
class Contact extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur(this);
}
handleBlur = field => () => {
this.setState({
touched: { ...this.state.touched, [field]: true }
});
render() {
return(
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Label htmlFor="firstname" md={2}>
First Name
</Label>
<Col md={10}>
<Input
type="text"
id="firstname"
name="firstname"
placeholder="First Name"
valid={errors.firstname === ""}
invalid={errors.firstname !== ""}
value={this.state.firstname}
onBlur={this.handleBlur("firstname")}
onChange={this.handleInputChange}
/>
<FormFeedback>{errors.firstname}</FormFeedback>
</Col>
</FormGroup>
</Form>
)
}
I would not do it with an arrow function, but you can. I will explain the two methods (there are a few more), the first is the one that I use normally.
Binding with a higher order function (or method)
It is simply a method that returns the event callback, as this is a method is already bound to this. This way you can pass any arguments to the method that is a closure, and these arguments will be present in the callback. That is the case of the field argument. Note that I switched the order of the argument, field should be first because it is called first to return the callback.
handleBlur(field) {
return evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
}
And you can bind it simply with:
onBlur = {this.handleBlur("firstname")}
This has the advantage that you do not need to bind to this in the constructor.
Using an arrow function
The code is similar, but you have to bind to this in the constructor.
handleBlurArrow = field => evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
Binding:
onBlur = {this.handleBlurArrow("firstnameArrow")}
Bind this on constructor:
this.handleBlurArrow = this.handleBlurArrow.bind(this);
Working example
class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleBlurArrow = this.handleBlurArrow.bind(this);
}
handleBlurArrow = field => evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
handleBlur(field) {
return evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
}
render() {
return (<div>
<input type = "text" id = "firstname"
name = "firstname"
placeholder = "First Name"
value = {this.state.firstname}
onBlur = {this.handleBlur("firstname")}
onChange = {this.handleInputChange}
/>
<input type = "text" id = "firstnameArrow"
name = "firstname"
placeholder = "First Name Arrow"
value = {this.state.firstname}
onBlur = {this.handleBlurArrow("firstnameArrow")}
onChange = {this.handleInputChange}
/>
</div>
)
}
}
ReactDOM.render( <Contact /> ,
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>
<div id="root"></div>

React: Get state of children component component in parent

I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.

Resources