Code Buckets

Buckets of code

React

Accessing form data with React.js

Accessing form data seems a standard thing to be doing so I disappointed myself and my entire family when I couldn’t do it straight away with when using React. After a little thought and a bit of practice I implemented 3 ways of doing it thus redeeming myself in the eyes of my relatives and co-workers. For additional redemption I’ve written up the 3 ways with an extra one tagged on the bottom.

The Demo Application

The demo application is a Todo list implementation. We can add and delete our todo tasks from the list. We can even change their colour (though that’s not hugely relevant for this but I’m bizarrely quite pleased with it).

The react component hierarchy is a bit more complex than my last demo app but nothing to get too alarmed about


  • Application
    • Header
    • TodoList
      • TodoItem
    • TodoInsert

The application state is held at the application level and all changes are handled there. I don’t want the application state spread out willy nilly throughout the components.

The Task

When I add a task then I want my application to access the name I type in the select box. The state is handled at the application level so the form data (todo task name) needs to propagate up to here. Easy – well let’s see.

Solution 1: Using ref to access the text box

The principle here is the use of the ref attribute to expose the textbox DOM node to React. Once this is done I can grab the value and pass it on.

Full code

TodoInsert component

The majority of the task insert magic is done by the TodoInsert component shown below.

class TodoInsert extends React.Component{
{
 //.. constructor omitted

 handleClick(){
 this.props.addTask(this.textInput.value);
 }


 render(){

   return <fieldset className="form-group">
           <h3>Add Task V3</h3>
           <div>
             <div>
               <input ref={input => this.textInput = input} type="text" 
                                          defaultValue="New Task" />
             </div>
            <div>
            <button onClick={this.handleClick}>Add Task</button>
           </div>
         </div>
       </fieldset>;
 
 } 
}

Application Layer

The application layer receives the textbox value and passes it into the application state.

class Application extends React.Component{

 addTask(input){

   var newTask = { 
     id: this.state.todoitems.length + 1, 
     task: input
   }
   var todoitems = this.state.todoitems;
   todoitems.push(newTask);
  
   this.setState({ 
     todoitems:todoitems
   });
  }

 removeTask(taskId)
 {
   //.. detail omitted
 }

 //.. constructor omitted

 render(){
   return <div >
     <FullRow>
       <h2>{this.props.label}</h2>
     </FullRow>
     <TodoList todoitems={this.state.todoitems} 
                    removeTask={this.removeTask} />
     <TodoInsert addTask={this.addTask}/>
     </div>;
  } 
};

Code explanation 

The key thing is the use of refs within the TodoInsert component

<input ref={input =>  this.textInput = input} type="text" defaultValue="New Task" />

This makes the input box DOM available within the component i.e

this.textInput

which we can access on the click handler

  handleClick(){
    this.props.addTask(this.textInput.value);
  }

And grab the value and pass onto the addTask method we have passed in from the application layer i.e.

class Application extends React.Component{

 addTask(input){

 //.. more logic
 }
 
 render(){
 
 return <Application>
 <TodoList />
 <TodoInsert addTask={this.addTask}/>
 </Application>

 }
}

So the value passes to addTask method on the application layer which we can then use within the application to set and update the state. So the textbox value becomes available to the application layer. Job done ….

Evaluation

Well kind of job done. I’ve read people really objecting to the use of ref and tying the application tightly to the DOM. With those objections in mind here is an alternatively implementation without refs.

Solution 2: Using onChange to track the state of the text box

This time we are going to fire an OnChange event whenever the text changes in the text box. The general flow is

  1. Text is typed into the text box. When the text is typed in onChange event fires.
  2. The onChange event updates the state of the component. The component has it’s own state in addition to the state of the main application component
  3. When the form is submitted the form triggers an onSubmit method.
  4. The onSubmit method picks out the value from the state and passes it onto the Application layer.

Full Code

Todo Insert component

Note we are now using an entire form in this component and it now has it’s own state

class TodoInsert extends React.Component{

 //.. constructure omitted

 handleChange(e){

 this.setState({ 
 todoText: e.target.value
 });

 }

 handleSubmit(e){
 e.preventDefault();
 this.props.addTask(this.state.todoText);
 }

 render(){

 return <form onSubmit={this.handleSubmit}> 
 <h3>Add Task</h3>
 <div>
   <div>
     <input onChange={this.handleChange} type="text" 
      value={this.state.todoText} />
   </div>
   <div>
     <input type="submit" value="Add Task" />
   </div>
 </div>
 </form>;
 
 } 
}

Application layer

The application layer is unchanged from the first example but I’ll reproduce for completeness

class Application extends React.Component{

 addTask(input){

   var newTask = { 
     id: this.state.todoitems.length + 1, 
     task: input
   }
   var todoitems = this.state.todoitems;
   todoitems.push(newTask);
  
   this.setState({ 
     todoitems:todoitems
   });
  }

 removeTask(taskId)
 {
   //.. detail omitted
 }

 //.. constructor omitted

 render(){
   return <div >
     <FullRow>
       <h2>{this.props.label}</h2>
     </FullRow>
     <TodoList todoitems={this.state.todoitems} 
                    removeTask={this.removeTask} />
     <TodoInsert addTask={this.addTask}/>
     </div>;
  } 
};

Code explanation 

Let’s work through the application flow again and link it to the relevant pieces of code.

  1. When the text is typed into the text box an onChange event fires.
<input onChange={this.handleChange} type="text" />
  1. The onChange event updates the state of the component.
 handleChange(e){

 this.setState({ 
 todoText: e.target.value
 });

 }
  1. When the form is submitted the form triggers an onSubmit method.
<form onSubmit={this.handleSubmit}> 
<!-- form stuff -->
</form>
  1. The onSubmit method picks out the value from the state and passes it onto the Application layer.
handleSubmit(e){
 e.preventDefault();
 this.props.addTask(this.state.todoText);
 }

Remember the props.addTask method is passed in from the Application layer – so this is the link back up the stack into the main Application section.

Evaluation

This works perfectly well with no ref usage. It does cause the TodoInsert render method to fire frequently. This is only going to update the text node of the textbox so doesn’t cause any notable performance issues. We’ll reused code from the previous 2 examples for our final work through.

Solution 3: Accessing the state of the text box from the parent control

The final method is to change focus and access the component state from the parent application. It can be done and it will be done.

Full Code

Todo Insert component

This time we have omitted the form tags again and the button just handles the clicks.

class TodoInsert extends React.Component{

 //..constructor omitted

 handleChange(e){

 this.setState({ 
 todoText: e.target.value
 });

 }

 handleClick(){
 this.props.addTask();
 }

 render(){
 
 return <div>
 <h3>Add Task</h3>
   <div>
     <input onChange={this.handleChange} type="text" 
                          value={this.state.todoText} />
   </div>
   <div>
     <button onClick={this.handleClick}>Add Task</button>
   </div>
 </div>;
 } 
}

Application Layer

This time the application layer has changed slightly as well. The application layer is now responsible for accessing the state in the child component.

class Application extends React.Component{


 addTask(){

   var newTask = { 
     id: this.state.todoitems.length + 1, 
     task: this.todoInsert.state.todoText
   }
   var todoitems = this.state.todoitems;
   todoitems.push(newTask);
 
   this.setState({ 
     todoitems:todoitems
   });
 }

 removeTask(taskId)
 {
 //.. code omitted

 }

 //.. constructor omitted


 render(){
 
 return <div >
 <FullRow>
   <h2>{this.props.label}</h2>
 </FullRow>
 <TodoList todoitems={this.state.todoitems} 
    removeTask={this.removeTask} />
 <TodoInsert addTask={this.addTask} 
    ref={input => this.todoInsert = input} />
 </div>;
 
 } 
};

Code explanation

Working through the flow of the application ….

Text is typed into the input box and the onChange event is fired

 <input onChange={this.handleChange} type="text" value={this.state.todoText} />

The click event handler now tracks value within the components own state as in the previous example.

 handleChange(e){

 this.setState({ 
 todoText: e.target.value
 });

 }

When the add button is pressed then a click handler is fired

 <button onClick={this.handleClick}>Add Task</button>

This then triggers a method on the application component. We don’t pass up the state this time – we are just notifying the application layer that it is time to save.

 handleClick(){
    this.props.addTask();
  }

Back to the application layer we are now putting a ref instruction into our entire TodoInsert component

 <TodoInsert addTask={this.addTask} 
          ref={input =>  this.todoInsert = input} />

Which then allows us to reference the state within the todoInsert component when we are adding the Todo task

 addTask(){
    var newTask = {
        id: this.state.todoitems.length + 1,
        task: this.todoInsert.state.todoText
    }

    var todoitems = this.state.todoitems;
    todoitems.push(newTask);

    this.setState({  
      todoitems:todoitems
   });
  }

The important part being this

this.todoInsert.state.todoText

i.e. we are accessing the state of the TodoComponent itself – this is how we are passing around the form values.

Evaluation

Although the is more complex then the previous two examples I like this one. It enables us to manage the form values within the component and pick them out from higher up in the application hierarchy. I feels nicely encapsulated too me and I can see it extending nicely.

Additional thoughts

All three methods of passing form state around work so take your pick. I’ve used them all in various places. If I had to pick my favourite it would be the one that I’m not writing about at the moment – flux architecture. Putting in flux architecture would enable me to access the values anywhere via a store – a topic for another day perhaps. A foreshadowing teaser if you will.

Notes on sample code

I’ve simplified the code throughout and put it comments where omission occur. The entire source code is available on my github site and I’d encourage interested parties to look there for working code samples. Specific amendments and shortcuts are ….

  1. ES6 classes are used throughout. A little bit of syntactical sugar to simplify.
  2. There is a FullRow component that I use to simplify the markup but it is just that – the markup for a fullrow in the UI so just read it as that.
  3. I’ve removed all css classes from the markup. For our purposes they are just noise and serve to distract
  4. I have omitted a lot of the setup in the constructor. Again it’s boilerplate and a distraction but for the interested here is an example of what you are missing
constructor(props) {
 super(props);
 this.props = props;
 this.state = 
 {
    todoitems :[]
 };

  this.addTask = this.addTask.bind(this);
 this.removeTask = this.removeTask.bind(this);
 }

Useful links

https://scotch.io/tutorials/better-javascript-with-es6-pt-ii-a-deep-dive-into-classes
ES6 classes. Used throughout the code samples.

https://stackoverflow.com/questions/35303490/uncaught-typeerror-cannot-read-property-props-of-null
I missed out the constructors in the code samples. One of the things is the boilerplate code to bind the this keyword to the scope of the function in the classes i.e.

this.addTask = this.addTask.bind(this);

The above link is a good post on what that’s all about plus some ES7 syntax to render this unnecessary

https://facebook.github.io/react/docs/refs-and-the-dom.html
Official guidance on the use and abuse of refs from Facebook.

https://facebook.github.io/flux/docs/overview.html
Flux architecture articles again from Facebook. Another method to pass around form data and perhaps my preferred one.

https://github.com/timbrownls20/Demo/tree/master/React/TodoList
As ever, all code is on my GitHub site.

LEAVE A RESPONSE

Your email address will not be published. Required fields are marked *