React Forms

Part 2, Lesson 7



In this lesson, we'll create a new functional component for adding a new user....


Within flask-microservices-client, add a new file called AddUser.jsx to the "components" directory:

import React from 'react';

const AddUser = (props) => {
  return (
    <form>
      <div className="form-group">
        <input
          name="username"
          className="form-control input-lg"
          type="text"
          placeholder="Enter a username"
          required
        />
      </div>
      <div className="form-group">
        <input
          name="email"
          className="form-control input-lg"
          type="email"
          placeholder="Enter an email address"
          required
        />
      </div>
      <input
        type="submit"
        className="btn btn-primary btn-lg btn-block"
        value="Submit"
      />
    </form>
  )
}

export default AddUser;

Import the component in index.js:

import AddUser from './components/AddUser';

Then update the render method:

render() {
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <br/>
          <h1>All Users</h1>
          <hr/><br/>
          <AddUser/>
          <br/>
          <UsersList users={this.state.users}/>
        </div>
      </div>
    </div>
  )
}

Ensure the dev machine is up and running and the REACT_APP_USERS_SERVICE_URL environment variable is properly assigned to the IP associated with the dev machine. Run npm start to test. If all went well, you should see the form along with the users.

Now, since this is a single page application, we want to prevent the normal browser behavior when a form is submitted to avoid a page refresh.

Steps:

  1. Handle form submit event
  2. Obtain user input
  3. Send AJAX request
  4. Update the page

Handle form submit event

To handle the submit event, simply update the form element in AddUser.jsx:

<form onSubmit={(event) => event.preventDefault()}>

Try submitting the form. Nothing should happen, which is exactly what we want - we prevented the normal browser behavior.

Next, add the following method to the App component:

addUser(event) {
  event.preventDefault();
  console.log('sanity check!')
}

Since AddUser is a functional component, we need to pass this method down to it via props. Update the AddUser element like so:

<AddUser addUser={this.addUser.bind(this)}/>

Here, we bound the context of this manually via bind(). Without it, the context of this inside the method will not have the correct context. Want to test this out? Simply add console.log(this) to the addUser() and then submit the form. What's the context? Remove the bind and test it again. What's the context now?

For more on this, review Handling Events from the official React docs.

Update the form element again:

<form onSubmit={(event) => props.addUser(event)}>

Test it out in the browser. You should see sanity check! in the JavaScript console on form submit.

Obtain user input

We'll use controlled components to obtain the user submitted input.

Start by adding two new properties to the state object:

this.state = {
  users: [],
  username: '',
  email: ''
}

Then pass them through to the component:

<AddUser
  username={this.state.username}
  email={this.state.email}
  addUser={this.addUser.bind(this)}
/>

These are accessible now via props, which can be used as the current value of the input like so:

<div className="form-group">
  <input
    name="username"
    className="form-control input-lg"
    type="text"
    placeholder="Enter a username"
    required
    value={props.username}
  />
</div>
<div className="form-group">
  <input
    name="email"
    className="form-control input-lg"
    type="email"
    placeholder="Enter an email address"
    required
    value={props.email}
  />
</div>

So, this defines the value of the inputs from the parent component. Test out the form now. What happens? You shouldn't see anything being typed since the value is being "pushed" down from the parent.

What do you think will happen if the initial state of those values was set as test rather than an empty string? Try it.

How do we update the state in the parent component then?

First, add a handleChange method to the App component:

handleChange(event) {
  const obj = {};
  obj[event.target.name] = event.target.value;
  this.setState(obj);
}

Pass it to the component:

<AddUser
  username={this.state.username}
  email={this.state.email}
  handleChange={this.handleChange.bind(this)}
  addUser={this.addUser.bind(this)}
/>

Add it to the form inputs:

<div className="form-group">
  <input
    name="username"
    className="form-control input-lg"
    type="text"
    placeholder="Enter a username"
    required
    value={props.username}
    onChange={props.handleChange}
  />
</div>
<div className="form-group">
  <input
    name="email"
    className="form-control input-lg"
    type="email"
    placeholder="Enter an email address"
    required
    value={props.email}
    onChange={props.handleChange}
  />
</div>

Test the form out now. It should be working. You can see the value of the state by logging it to the console in the addUser method:

addUser(event) {
  event.preventDefault();
  console.log(this.state);
}

Now that we have the values, let's send the AJAX request so the data can be added to the database and then update the DOM...

Send AJAX request

Turn back to flask-microservices-users. What do we need to send in the JSON payload to add a user - username and email, right? Let's use Axios to send the POST request:

addUser(event) {
  event.preventDefault();
  const data = {
    username: this.state.username,
    email: this.state.email
  }
  axios.post(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`, data)
  .then((res) => { console.log(res); })
  .catch((err) => { console.log(err); })
}

Test it out. It should work as long as the email address is unique.

If you have problems, analyze the response object from the "Network" tab in Developer Tools. You can also fire up flask-microservices-users outside of Docker and debug using the Flask debugger or with print statements.

Update the page

Finally, let's update the list of users on a successful form submit and then clear the form.

addUser(event) {
  event.preventDefault();
  const data = {
    username: this.state.username,
    email: this.state.email
  }
  axios.post(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`, data)
  .then((res) => {
    this.getUsers();
    this.setState({ username: '', email: '' });
  })
  .catch((err) => { console.log(err); })
}

That's it. Test it out. Review and then commit your code.


React Forms

In this lesson, we'll create a new functional component for adding a new user....


Within flask-microservices-client, add a new file called AddUser.jsx to the "components" directory:

import React from 'react';

const AddUser = (props) => {
  return (
    <form>
      <div className="form-group">
        <input
          name="username"
          className="form-control input-lg"
          type="text"
          placeholder="Enter a username"
          required
        />
      </div>
      <div className="form-group">
        <input
          name="email"
          className="form-control input-lg"
          type="email"
          placeholder="Enter an email address"
          required
        />
      </div>
      <input
        type="submit"
        className="btn btn-primary btn-lg btn-block"
        value="Submit"
      />
    </form>
  )
}

export default AddUser;

Import the component in index.js:

import AddUser from './components/AddUser';

Then update the render method:

render() {
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <br/>
          <h1>All Users</h1>
          <hr/><br/>
          <AddUser/>
          <br/>
          <UsersList users={this.state.users}/>
        </div>
      </div>
    </div>
  )
}

Ensure the dev machine is up and running and the REACT_APP_USERS_SERVICE_URL environment variable is properly assigned to the IP associated with the dev machine. Run npm start to test. If all went well, you should see the form along with the users.

Now, since this is a single page application, we want to prevent the normal browser behavior when a form is submitted to avoid a page refresh.

Steps:

  1. Handle form submit event
  2. Obtain user input
  3. Send AJAX request
  4. Update the page

Handle form submit event

To handle the submit event, simply update the form element in AddUser.jsx:

<form onSubmit={(event) => event.preventDefault()}>

Try submitting the form. Nothing should happen, which is exactly what we want - we prevented the normal browser behavior.

Next, add the following method to the App component:

addUser(event) {
  event.preventDefault();
  console.log('sanity check!')
}

Since AddUser is a functional component, we need to pass this method down to it via props. Update the AddUser element like so:

<AddUser addUser={this.addUser.bind(this)}/>

Here, we bound the context of this manually via bind(). Without it, the context of this inside the method will not have the correct context. Want to test this out? Simply add console.log(this) to the addUser() and then submit the form. What's the context? Remove the bind and test it again. What's the context now?

For more on this, review Handling Events from the official React docs.

Update the form element again:

<form onSubmit={(event) => props.addUser(event)}>

Test it out in the browser. You should see sanity check! in the JavaScript console on form submit.

Obtain user input

We'll use controlled components to obtain the user submitted input.

Start by adding two new properties to the state object:

this.state = {
  users: [],
  username: '',
  email: ''
}

Then pass them through to the component:

<AddUser
  username={this.state.username}
  email={this.state.email}
  addUser={this.addUser.bind(this)}
/>

These are accessible now via props, which can be used as the current value of the input like so:

<div className="form-group">
  <input
    name="username"
    className="form-control input-lg"
    type="text"
    placeholder="Enter a username"
    required
    value={props.username}
  />
</div>
<div className="form-group">
  <input
    name="email"
    className="form-control input-lg"
    type="email"
    placeholder="Enter an email address"
    required
    value={props.email}
  />
</div>

So, this defines the value of the inputs from the parent component. Test out the form now. What happens? You shouldn't see anything being typed since the value is being "pushed" down from the parent.

What do you think will happen if the initial state of those values was set as test rather than an empty string? Try it.

How do we update the state in the parent component then?

First, add a handleChange method to the App component:

handleChange(event) {
  const obj = {};
  obj[event.target.name] = event.target.value;
  this.setState(obj);
}

Pass it to the component:

<AddUser
  username={this.state.username}
  email={this.state.email}
  handleChange={this.handleChange.bind(this)}
  addUser={this.addUser.bind(this)}
/>

Add it to the form inputs:

<div className="form-group">
  <input
    name="username"
    className="form-control input-lg"
    type="text"
    placeholder="Enter a username"
    required
    value={props.username}
    onChange={props.handleChange}
  />
</div>
<div className="form-group">
  <input
    name="email"
    className="form-control input-lg"
    type="email"
    placeholder="Enter an email address"
    required
    value={props.email}
    onChange={props.handleChange}
  />
</div>

Test the form out now. It should be working. You can see the value of the state by logging it to the console in the addUser method:

addUser(event) {
  event.preventDefault();
  console.log(this.state);
}

Now that we have the values, let's send the AJAX request so the data can be added to the database and then update the DOM...

Send AJAX request

Turn back to flask-microservices-users. What do we need to send in the JSON payload to add a user - username and email, right? Let's use Axios to send the POST request:

addUser(event) {
  event.preventDefault();
  const data = {
    username: this.state.username,
    email: this.state.email
  }
  axios.post(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`, data)
  .then((res) => { console.log(res); })
  .catch((err) => { console.log(err); })
}

Test it out. It should work as long as the email address is unique.

If you have problems, analyze the response object from the "Network" tab in Developer Tools. You can also fire up flask-microservices-users outside of Docker and debug using the Flask debugger or with print statements.

Update the page

Finally, let's update the list of users on a successful form submit and then clear the form.

addUser(event) {
  event.preventDefault();
  const data = {
    username: this.state.username,
    email: this.state.email
  }
  axios.post(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`, data)
  .then((res) => {
    this.getUsers();
    this.setState({ username: '', email: '' });
  })
  .catch((err) => { console.log(err); })
}

That's it. Test it out. Review and then commit your code.