Intro to Functional State, Controlled Forms, and Conditional Rendering

Intro to Functional State (hooks)

Complete source code for this tutorial is available at this repository in the 03-functional-state-start branch

State allows us to keep track of the “state” of our application at any given time. Here’s an example of state for my personal being:

myState = {
  isHungry: false,
  isStanding: true,
  energyLevel: 5
}

Each component has the ability to have “state”. To add state for “functional” components we’ll use a “hook” provided to us by React called useState. Let’s set some state for our App component. In App.js, adjust your React import and add the following code like below:

import React, { useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h3>The current count is: {count}</h3>

The useState hook allows us to create a state variable and a function to update that state variable. We’re creating “count” and “setCount” in the above code. Anytime we want to change the state of “count” we just pass in the new count state into “setCount”. Usually you’ll want to do this inside of a function. Let’s create a button that triggers an update of state that increases the count by 1. We do that with the following code: (PS. the useState(0) means the default state of “count” is 0)

import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";

function App() {
  const [count, setCount] = useState(0);

  const increaseCount = () => {
    setCount(count + 1);
  };

  return (
    /* I cannot put anything out here because functions are only supposed to return one thing. That's why everything is wrapped in a div */
    <div className="App">
      <h3>The current count is: {count}</h3>
      <button onClick={increaseCount}>count + 1</button>
//...

First look at the function increaseCount. In it we’re passing in the current state + 1 into increaseCount. In order to update the state, we just need to call that function. So in our return statement we return a button, and pass in the increaseCount function into the button’s onClick handler.

Challenge Time: allow users to decrease count state by 1.

Don’t look at the code below until you’ve given it a fair try.

function App() {
  const [count, setCount] = useState(0);

  const increaseCount = () => {
    setCount(count + 1);
  };

  const decreaseCount = () => {
    setCount(count - 1);
  };

  return (
    /* I cannot put anything out here because functions are only supposed to return one thing. That's why everything is wrapped in a div */
    <div className="App">
      <h3>The current count is: {count}</h3>
      <button onClick={increaseCount}>count + 1</button>
      <button onClick={decreaseCount}>count - 1</button>

Introduction to Controlled Forms:

In React we typically use state to control our forms, meaning each field in a form has a “state” that is updated when a user enters input. Let’s see it in action. In the below code we’ll provide a form field to put in the users name. Then we’ll have some content that uses the name field.

First, create a state variable and state variable updater with useState:

function App() {
  const [count, setCount] = useState(0);
  const [textField, setTextField] = useState("");
  const [firstName, setFirstName] = useState("");

Then inside the “return” statement, put your markup for the form field:

return (
    <div className="App">
      <h3>The current count is: {count}</h3>
      <button onClick={increaseCount}>count + 1</button>
      <button onClick={decreaseCount}>count - 1</button>

      <form>
        <label>
          <input
            type="text"
            placeholder="Your name"
            value={textField}
            name="name"
          />
        </label>
      </form>

      <h3>Hello {firstName}! How are you today?</h3>

If you save the code here you should see a text field for “Your name”, but if you try to enter information into the field it doesn’t update. Also, the “Hello How are you today?” code looks awful because we aren’t greeting anyone.

You’re not able to fill in the name field because the value of the textbox is set to textField which is currently empty. HTML form fields in React have the ability to detect changes. So if you type “J” into the box, it will have the letter “J” stored in event.target.value. So all we have to do is add the event.target.value to the current state of textField to see our changes happen in real time.

So Just like how we changed count with setCount, let’s change “textField” with our “setTextField” function.

  const handleNameFieldChange = (event) => {
    setTextField(event.target.value);
  };

  return (
    /* I cannot put anything out here because functions are only supposed to return one thing. That's why everything is wrapped in a div */
    <div className="App">
      <h3>The current count is: {count}</h3>
      <button onClick={increaseCount}>count + 1</button>
      <button onClick={decreaseCount}>count - 1</button>

      <form>
        <label>
          <input
            type="text"
            placeholder="Your name"
            value={textField}
            onChange={handleNameFieldChange}
            name="name"
          />
        </label>

All we’re doing above is creating a function “handleNameFieldChange” that triggers every time the text field is changed. That function then updates the state of the text field.

Challenge Time: Add an OnSubmit to update firstName

In this section let’s try to get that “Hello how are you today?” text to appropriately use the firstName. You’ll want to add a “submit” button to the form, and use an “onSubmit” handler. Give it a try before looking at the code below.

  const handleSubmit = (event) => {
    event.preventDefault();
    setFirstName(textField);
    setTextField("");
  };

  return (

    <div className="App">
      <h3>The current count is: {count}</h3>
      <button onClick={increaseCount}>count + 1</button>
      <button onClick={decreaseCount}>count - 1</button>

      <form onSubmit={handleSubmit}>
        <label>
          <input
            type="text"
            placeholder="Your name"
            value={textField}
            onChange={handleNameFieldChange}
            name="name"
          />
        </label>
        <input type="submit" value="Submit" />
      </form>

Pretty similar to what we’ve been doing already. The thing you can’t forget is what happens when you submit a form. By default the page refreshes which would reset our state, so you can prevent that with event.preventDefault(); Also, if the user has entered their name and hit submit, you’ll probably want to clear out the text field by setting the textField state to an empty string.

Challenge Time: Allow the user to update count by a number of their choice

You could just give the user a plus and minus button and a number field, but I wanted to give the user more choice, so I used a select field to allow them to select between add, subtract, multiply, or divide. I didn’t bother with error handling.

Note: I added some simple inline styles so you can see the basics of styling.

In the wild you’ll frequently see inline functions to save space. No need to define a named function for one-liners, so we’ll do stuff like this instead:

onChange={(event) => {
  setOperator(event.target.value);
}}

The above code can feel weird, but it’s the same thing as defining a function with a name. the onChange event passes in the event that occurred as a parameter to the function. Then we run the functions code when a user triggers the onChange. Anyways… Here’s my crude solution: I encourage you to create several different alternative solutions to really get this down.

function App() {
  const [count, setCount] = useState(0);
  const [textField, setTextField] = useState("");
  const [firstName, setFirstName] = useState("");
  const [countInput, setCountInput] = useState(0);
  const [operator, setOperator] = useState("subtract");

  const updateCount = (event) => {
    event.preventDefault();
    switch (operator) {
      case "add":
        setCount(count + countInput);
        break;
      case "subtract":
        setCount(count - countInput);
        break;
      case "divide":
        setCount(count / countInput);
        break;
      case "multiply":
        setCount(count * countInput);
        break;
      default:
        setCount(count + countInput);
    }
  };

  const handleNameFieldChange = (event) => {
    setTextField(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    setFirstName(textField);
    setTextField("");
  };


  return (
    /* I cannot put anything out here because functions are only supposed to return one thing. That's why everything is wrapped in a div */
    <div className="App">
      <div
        style={{
          background: "lightgreen",
          display: "inlineBlock",
          margin: "20px",
        }}
      >
        <h3>The current count is: {count}</h3>

        <form onSubmit={updateCount}>
          <label htmlFor="operator">Choose an operator:</label>
          <select
            value={operator}

            onChange={(event) => {
              setOperator(event.target.value);
            }}
            name="operands"
            id="operands"
          >
            <option value="add">+</option>
            <option value="subtract">-</option>
            <option value="divide">/</option>
            <option value="multiply">*</option>
          </select>
          <input
            type="number"
            value={countInput}
            onChange={(event) => {
              setCountInput(parseInt(event.target.value));
            }}
          />
          <input type="submit" value="Submit" />
        </form>
      </div>

      <div style={{ background: "lightblue" }}>
        <form onSubmit={handleSubmit}>
          <label>
            <input
              type="text"
              placeholder="Your name"
              value={textField}
              onChange={handleNameFieldChange}
              name="name"
            />
          </label>
          <input type="submit" value="Submit" />
        </form>
        <h3>Hello {firstName}! How are you today?</h3>
      </div>

Brief Intro to Conditional Components:

We’ll go more in depth on this later, but you probably don’t like how “Hello ! How are you today” shows up when nobody has filled out the form. We can use conditional rendering to display components based on certain conditions. In our case, if the firstName state is empty then we should show a different component. Here’s one of a few different ways to do that. Right where the existing markup for the “Hello how are you” shows up, replace that with the following code:

        </form>
        {firstName ? (
          <h3>Hello {firstName}! How are you today?</h3>
        ) : (
          <h3>Nobody has filled out the form yet!</h3>
        )}
      </div>

This is “inline” rendering. It’s honestly pretty nasty looking, but it’s a bit more convenient. We can run expressions inside of our return statement by adding the curly brackets. So anything between { and } will act as an expression. In the above code we’re using a ternary operator to run one block of markup if firstName exists, and another block if firstName does not exist.

function SayHi(props) {
  if (props.firstName) {
    return <h3>Hello {props.firstName}! How are you today?</h3>;
  } else {
    return <h3>Nobody has filled out the form!</h3>;
  }
}

<SayHi firstName={firstName} />

Check out the 03-functional-state-done branch for the complete working source code

Leave a Comment

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

want more details?

Fill in your details and we'll be in touch

%d bloggers like this: