React Child List Component - reactjs

I am learning react, what is the best practice for the following scenario?
(Note just typed this up - not perfect, just meant to illustrate what I'm trying to do). Given this data -
const person = {
name: "",
tasks: [
{name: "", done: false }
]
}
I want a form to edit both the name and the tasks at the same time - add, delete and edit the fields of the tasks.
What I was thinking:
<PersonForm>
<PersonName />
<TaskList />
</PersonForm>
The name can be easily edited by the example given by react documentation:
class PersonForm extends React.Component {
constructor(props) {
this.state = {
name: "",
tasks: [
{name: "", done: false }
]
};
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({ [name]: value });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<PersonName name={this.state.name} onChange={this.handleInputChange} />
<TaskList tasks={this.state.tasks}
deleteTask={this.deleteTask}
addTask={this.addTask}
updateTask={this.updateTask}/>
<input type="submit" value="Submit" />
</form>
);
}
}
class PersonName extends
render() {
return (
<label>
Name:
<input type="text" name="name" value={this.props.value} onChange={this.props.onChange} />
</label>
)
}
}
I know the recommendation is lifting state up. So I could put the addTask, removeTask and updateTask callback methods in PersonForm.
class PersonForm extends React.Component {
. . .
addTask = event => {
this.setState(prev => ({ tasks: [...prev, {name: "", done: false}]}));
}
removeTask = key => {
this.setState(prev => ({ tasks: prev.filter(t => t.key !== key) });
}
updateTask = ???...
. . .
But,
It seems to me the best way to encapsulate functionality would be for the addTask, deleteTask, updateTask functionality to be in the TaskList component. Am I wrong?
It seems like otherwise PersonForm would get huge (in a real world example). Would this mean TaskList would need state?
Basically,
What is the best practice for this sub-list scenario?
If callbacks from the top are the answer, how to update the task data?

Moving some logic outside of the component when it starts getting huge makes sense but at the same time it's convenient to keep all form state in one place. You could use ref on child component to retrieve it's state but it's an ugly solution and considered a bad practice. I believe in my experience I haven't encountered a very huge forms so even they were big components it was pretty fine to read/manage all the state there. But if you'd really want to move some logic out of it I think you could, for example, use the new context API to store the form's state (or just tasks list's state) and subscribe to it in PersonForm component (to read) and in TaskList (to read and change).

Related

Call method of all chlidren items in a list of React PureComponent

I'm new to React. I have a list component that is composed of an array of editable items and I wanted to use Pure Components for the item component.
I want to serialize the whole list calling a serialize method in every item of the list whitch returns an object.
Something like:
class List extends React.Component {
render() {
const items = [
{ name: 'first' },
{ name: 'second' }
];
const itemComponents = items.map((elem, index) => {
return <Item
key={index}
name={elem.name}></Item>
});
return <div>
{itemComponents}
<button onClick={this.serialize}>Serialize</button>
</div>
}
serialize() {
console.log(); //todo: serialized list??
}
}
class Item extends React.PureComponent<{ name: string }, { checked: boolean }> {
constructor(props: { name: string }) {
super(props);
this.state = { checked: false }
}
render() {
return <div>
<h3>{this.props.name}</h3>
<input
type='checkbox'
checked={this.state.checked}
onChange={() => this.setState(prevState => ({ checked: !prevState.checked }))} />
</div>
}
// cannot be accessed from parent
serialize() {
return {
name: this.props.name,
checked: this.state.checked
}
}
}
Looking around I found that a solution may be to 'lift the state' and have the event handling of the items defined in the List component. That may be fine in this example but my actual items are more complex and I wanted to avoid having one massive component.
Another solution that may be possible uses the useRef() hook but I get a compilation error with the PureComponent and defies the purpose of using them. As far as I am concerned the Item component is Pure since does not have side effects.
Am I missing something? Should I be doing things completely differently?

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.

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: why binding value makes input readonly?

given this initial state:
this.state = {
person: { id: 1, name: "Liero" }
};
when I bind input like this: <input value={this.state.person.name} />
Then I makes the input non editable. I understand that it would make state and DOM element out of sync.
However, when I use defaultValue: <input defaultValue={this.state.person.name} />,
I loose posibility to change the person's name from code (e.g. when I want to reset the form).
When I manually sync the state and input value:
<input value={this.state.person.name}
onChange={(evt) => this.state.person.name = evt.target.value }/>
it does not work until I call setState, or force refresh?
Do I really need to force render of entire component each time the input value changes, when I want to have the posibility to control the person.name from code?
Instead of set this.state.person.name directly. call this.setState.
This will trigger another render cycle and then bind this.state.person.name to value:
<input value={this.state.person.name}
onChange={(evt) => {
this.state.person.name = env.target.value;
this.setState({person:this.state.person});
}}/>
Did you mean to setState() instead of mutate the state? like this:
class MyComponent extends Component {
state = { person: { id: 1, name: "Liero" } }
updateName = (e) => {
this.setState({
person: {...this.state.person, name: e.target.value}
})
}
render (){
return (
<input type="text" onChange={this.updateName} value={this.state.person.name} />
);
}
}

How to recursively pass up all data from form?

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

Resources