NOTE: Interactivity in React — Part 2

emily leung
PROJECT REDBACK
Published in
9 min readOct 4, 2017

NOTE: 0018 — Wednesday 13 September 2017

Now that we are able to build components into a React App. We should now look into creating user interface components. This will follow through to realising the potential of connecting it with an external API. It may or may not be explained in this post or the next.

PROCESS

STEP 1 — Create a new Component in the ‘Components’ folder

We’re going to start with creating a component that allows us to add new projects in the list of already defined projects. Almost the equivalent of a To-do-list!

To start, in the ‘Components’ folder, create a new file — ‘AddProject.js

STEP 2 — Add in the backbone

Now copy over the lines of code we produced in ‘Projects.js’ to ‘AddProject.js’. Make sure to remove:

  • the imported ‘ProjectItem’ component
  • the lines of code between the ‘render’ and ‘return’ function to create a clean slate
  • and replace ‘Projects’ with ‘AddProject’

STEP 3 — Import the AddProject component into App.js

import AddProject from './Components/AddProject';

To add it into the App, situate the element at the top of the application.

render() {
return (
<div className="App">
<AddProject />
<Projects projects={this.state.projects} />
</div>
);
}
}

To test that it works, we will add in some visible text in an empty div (with no className) in the render function within ‘AddProject.js’:

import React, { Component } from 'react';class AddProject extends Component {
render() {
return (
<div>
<h3>Add Project</h3>
</div>
);
}
}
export default AddProject;

Check back on the app to see if it’s visible. If it works, let’s add in a space to input data by creating a form:

import React, { Component } from 'react';class AddProject extends Component {
render() {
return (
<div>
<h3>Add Project</h3>
<form>
<div>
<label>Title</label><br />
<input type="text" ref="title" />
</div>
</form>

</div>
);
}
}
export default AddProject;

Then to make things interesting, let’s ask for the categories to come out as a dropdown. That way we limit the types of categories the user can enter by using a <select> element.

import React, { Component } from 'react';class AddProject extends Component {
render() {
return (
<div>
<h3>Add Project</h3>
<form>
<div>
<label>Title</label><br />
<input type="text" ref="title" />
</div>
<div>
<label>Category</label><br />
<select ref="category">

</select>
</div>

</form>
</div>
);
}
}
export default AddProject;

I want to categories to be a property component. To do this, we need to first set up some static defaultProps:

class AddProject extends Component {
static defaultProps = {
categories: ['Web Design', 'Web Development', 'Mobile Development']
}

We’ll then need to grab the options and map through them, so that they pump out as one category per option.

render() {
let categoryOptions = this.props.categories.map(category => {
return <option key={category} value="category">{category}</option>
});

Now, in our select element, we can now insert the categoryOption property into the dropdown menu to be selected:

import React, { Component } from 'react';class AddProject extends Component {
static defaultProps = {
categories: ['Web Design', 'Web Development', 'Mobile Development']
}
render() {
let categoryOptions = this.props.categories.map(category => {
return <option key={category} value="category">{category}</option>
});
return (
<div>
<h3>Add Project</h3>
<form>
<div>
<label>Title</label><br />
<input type="text" ref="title" />
</div>
<div>
<label>Category</label><br />
<select ref="category">
{categoryOptions}
</select>
</div>
</form>
</div>
);
}
}
export default AddProject;

Now, to submit the form, we need to add a submit button.

<input type="submit" value="Submit" />

The add in the existing form tag a handler called ‘onSubmit’.

<form onSubmit={this.handleSubmit}>

Then make sure to bind the change to the submit button.

<form onSubmit={this.handleSubmit.bind(this)}>

Of course, the handleSubmit function doesn’t exist, so we’ll need to create that right above the render function:

handleSubmit() {
console.log('Submitted');
}

Those specific lines of code won’t do anything until we make sure to include the event:

handleSubmit(e) {
console.log('Submitted');
e.preventDefault();
}

The ‘e.preventDefault( );’ actually prevents the form from actually submitting, but allows us to see the console.log action happen.

STEP 4 — App to store the changes made in submitting data

In order to start this, we need to create a constructor method and define the initial state:

class AddProject extends Component {
constructor() {
super();
this.state = {
newProject: {}
}
}

But we don’t want to define them already, we instead want the data input to be connected to what is submitted. So, let’s jump back into the handleSubmit function and extract the value of the title being submitted:

handleSubmit(e) {
console.log(this.refs.title.value);
e.preventDefault();
}

Now when you type in data into the input, then hit submit, the console will log the data.

We also don’t want the user to submit a blank title, so we can add in a validation method to alert the need to input by again going into the handleSubmit function and replacing the console.log with:

handleSubmit(e) {
if(this.refs.title.value === '') {
alert('Title is required');
}

Above basically means that if the input for the title is equal to nothing, then an alert will pop up saying ‘Title is required’.

We’ll also need to include an ‘else’, which will be actual submission of the form and make that a new state:

handleSubmit(e) {
if(this.refs.title.value === '') {
alert('Title is required');
} else {
this.setState({newProject:{
title: this.refs.title.value,
category: this.refs.category.value
}});

The setState can take in a second parameter which in this case will be a callback function to console.log the resulting new state:

handleSubmit(e) {
if(this.refs.title.value === '') {
alert('Title is required');
} else {
this.setState({newProject:{
title: this.refs.title.value,
category: this.refs.category.value
}}, function() {
console.log(this.state);
});
};

e.preventDefault();
}

The state in the ‘AddProjects’ component is different from the state in the main ‘App’ component.

Each component has its own state

But what we want to do is take the data we submitted and pass it up into the main ‘App’ component and save it in the main in that state. We can send it up through a property — a function inside the properties.

We will do this be first removing the console.log in the handleSubmit function and replace it with obtaining the new state:

handleSubmit(e) {
if(this.refs.title.value === '') {
alert('Title is required');
} else {
this.setState({newProject:{
title: this.refs.title.value,
category: this.refs.category.value
}}, function() {
//console.log(this.state);
this.props.addProject(this.state.newProject);
});
};
e.preventDefault();
}

Then in the <AddProject> element, add a property with the new state binded:

<AddProject addProject={this.handleAddProject.bind(this)} />

The handleAddProject handler is a function we will need to define above the render function:

handleAddProject(project){
console.log(project);
}

In React, the state is immutable meaning that you don’t want to change it, you want to update it, so that we want to get everything that’s in it pushed to it (the new project) and then set it again. So to do that:

handleAddProject(project){
//console.log(project);
let projects = this.state.projects;
projects.push(project);
this.setState({projects:projects});
}

But when you start to add in titles, new bullet points will be made (which is fantastic!) but the selected category does not show. We need to jump back into the ‘AddProjects’ component and replace the value from the returned option property for value from “category” to {category}.

render() {
let categoryOptions = this.props.categories.map(category => {
return <option key={category} value={category}>{category}</option>
});

Now it should work!

You could potentially push the data into some sort of external API or server. But React focuses on the user interface part of it. The back end is completely separated from the front end, which is really useful!

Now let’s look into removing data in the user interface.

We can do this by first grabbing the unique id for each component. First thing we’ll need to do, is first install the UUID module in Node.js. To do this, stop the localhost app and type in:

npm install --save uuid

Hit enter and allow Node.js to load the module. Then open the program again:

npm start

Then we must jump into ‘App.js’ to import it into the top section of the app:

import uuid from 'uuid';

We can now add unique Id’s to each object that we’ve previously set in the componentWillMount lifecycle method:

componentWillMount(){
this.setState({projects: [
{
id: uuid.v4(),
title: 'Business Website',
category: 'Web Design'
},
{
id: uuid.v4(),
title: 'Social App',
category: 'Mobile Development'
},
{
id: uuid.v4(),
title: 'Ecommerce Shopping Cart',
category: 'Web Development'
}
]});
}

This pretty much allows for the creation of new unique ids

We will also need to include this in our ‘AddProject’ setState function:

handleSubmit(e) {
if(this.refs.title.value === '') {
alert('Title is required');
} else {
this.setState({newProject:{
id: uuid.v4(),
title: this.refs.title.value,
category: this.refs.category.value
}}, function() {
//console.log(this.state);
this.props.addProject(this.state.newProject);
});
};
e.preventDefault();
}

We can check that the unique id is appearing by replacing ‘title’ with ‘id’ in the ‘ProjectItem’ component:

<strong>{this.props.project.id}</strong>: {this.props.project.category}

Now, we can create a delete function for all objects.

We can do this by creating a link in the ProjectItem component and within it, an event handler:

<strong>{this.props.project.title}</strong>: {this.props.project.category} <a href="#" onClick={this.deleteProject.bind(this)}>X</a>

We now will need to define the ‘deleteProject’ function above the render function still within ‘ProjectItem.js’.

deleteProject() {
console.log('test');
}

The console.log will tell us if it reacts to the click.

The process needs to be done within the main app component. In this case it will need to be passed two times in order to reach the main app component before deleting it.

To do this, we need to update the deleteProject function by generating a property for the id:

deleteProject(id) {
//console.log('test');
this.props.onDelete(id);
}

We’ll then also need to update the resulting binding element to include an additional property:

<a href="#!" onClick={this.deleteProject.bind(this, this.props.project.id)}>X</a>

Now we’ll need to add onDeleteKey to the Projects component as a property on the <ProjectItem>:

<ProjectItem onDelete={this.deleteProject.bind(this)} key={project.title} project={project} />

We’ll also need to create the function ‘deleteProject’ in the Projects Component:

deleteProject(id) {
//console.log('test');
this.props.onDelete(id);
}

Finally, in the App component, we’ll add the ‘onDelete’ property to the <Projects /> element

<Projects projects={this.state.projects} onDelete={this.handleDeleteProject.bind(this)} />

We can now add and delete objects in our user interface!

Prop Types — From testing it, I think it’s outdated and is a module that we can reference in.

Are pretty much like a validation for our properties. This is seen in our Projects Component. We can see that our Projects has a property and the deleteProject function has a property.

AddProject.propTypes = {
categories: React.PropTypes.array,
addProject: React.PropTypes.func
}

One last thing, is the ability to bring in data from an external API.

Let’s first install JQuery using Node.js. We must first close the application and the type:

npm install jquery --save

This will run and install JQuery. Now you can reopen your application.

Now we must import JQuery in a line of code in the App component:

import $ from 'jquery';

We can grab our data from a fake data resource such as https://jsonplaceholder.typicode.com/ to test it out!

We want to make the request in both lifecycle methods — componentWillMount and componentDidMount. We’ll first start with creating 2 new functions:

getTodos(){
}

as well as

getProjects(){
this.setState({projects: [
{
id: uuid.v4(),
title: 'Business Website',
category: 'Web Design'
},
{
id: uuid.v4(),
title: 'Social App',
category: 'Mobile Development'
},
{
id: uuid.v4(),
title: 'Ecommerce Shopping Cart',
category: 'Web Development'
}
]});
}

and we’ll call them in the 2 life cycle methods:

componentWillMount(){
this.getProjects();
this.getTodos();
}
componentDidMount(){
this.getTodos();
}

In the ‘getTodos’ function, this is where we will make our request to grab data. There’s so many modules we can use to make http requests, but we’re going to use JQuery. Now that we already have it installed:

getTodos(){
$.ajax(){
url: 'https://jsonplaceholder.typicode.com/todos',
dataType: 'json',
cache: false,
success: function(data){
this.setState({todos: data}, function(){
console.log(this.state)
});
}.bind(this),
error: function(xhr, status, err){
console.log(err);
}
});
}

Make sure to add ‘todos’ in the properties of the initial state:

class App extends Component {
constructor() {
super();
this.state = {
projects: [],
todos: []
}
}

Duplicate ‘Projects.js’ and ‘ProjectItem.js’ and replace the word project with ‘todo’ in however way it was described in the original file. It must follow the same representation, otherwise the code will not work.

Final App using the React Tutorial

Now the user is able to add project names, place it into a category, as well as create their own To Do List. The “X” on the right of each project or bullet point allow for deleting each item.

You can find the resulting code here.

© Emily Y Leung and Project Redback, 2017. Unauthorized use and/or duplication of this material without express and written permission from this site’s author and/or owner is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to Emily Y Leung and Project Redback with appropriate and specific direction to the original content.

Last modified on Wednesday 13 September 2017

--

--