How To Master React State & Event Handlers (Part 4)

by kleamerkuri
React state and event handlers tutorial project.

I hope you all are excited to finally bring interactivity to your React apps 😁

If you’ve been following along with this series, you should be up and running with:

  1. a project created with create-react-app
  2. custom JSX components
  3. scoped styles using CSS module

Today, we hit part four which is all about the exciting world of event handlers and working with React state.

Event handlers are essential tools that allow us to respond to user actions, such as button clicks, form submissions, or keyboard inputs.

When combined with React state, we can create components that update and re-render based on user interactions, making our apps more responsive and user-friendly.

In this blog post, we’ll cover the basics of using event handlers in React, how to handle events in functional components, and how to manage component states to create dynamic user interfaces.

So, ready your fingers and prep your keyboards to create interactive web applications that captivate your users!

React State

React runs through the code executing each function one time. It won’t reevaluate upon an event unless specified.

What this means is that if we start with a name that’s an empty string (indicating an empty name input on our form), but then want to update the name to reflect a user’s input, we can’t.

Setting a new value for the variable name won’t cause React to update the initial value (as is the case with Vanilla JS).

Instead, we’ll use the updating function to trigger a reevaluation that will update the variable value for us.

This is how React works, it takes care of updating stuff but you gotta tell it to do so 💁‍♀️

As a little test run, let’s collect the name and age from the two form inputs.

  1. Import useState from “react” in the Form component file.
  • useState is a React hook that you call inside of the component function.
  • It must run directly in the component function – you can’t place it outside of const Form = () ⇒ {}.
  1. Call useState inside of the component, specifying a default value.
  2. useState returns an array of the value itself and an updating function.
  • Use array destructuring to access the two returned items.
  • The order is important! It’s always the case that the first returned item is a value while the second is the updating function.
let [name, setName] = useState('')
let [age, setAge] = useState('');

Read: 14 ES6 Features You Need To Know To Refresh Your JavaScript

  1. Execute the updating function in the handler to schedule a change on rerun.
  • The updating function schedules a re-evaluation so any other lines of code following the function call get evaluated prior to the update taking effect.
  • Console logging name or age will yield the default values of empty strings.
const nameHandler = (e) => {
    setName(e.target.value);
  };

  const ageHandler = (e) => {
    setAge(+e.target.value);
  };

Note 👀
In the sample above, e.target.value is essentially extracting the input value. This isn’t React-specific but something we often see in Vanilla JS. Further, I use a unary operator to turn the string type of age into an integer.

Different ways to store React states

You can have multiple states per component like I’m doing here, or you can initialize a state with an object and group different states.

If grouping different states, keep in mind they’re managed as one piece of state so every time one piece is updated, all pieces need to be “updated” whether to new or existing values.

We can do that by spreading data of the existing state object; if we don’t, we’ll lose the data 😢

Example:

let [inputs, setInputs] = useState({ name: "", age: "" });

const nameHandler = (e) => {
    // setName(e.target.value);
    setInputs({...inputs, name: e.target.value})
  };

  const ageHandler = (e) => {
    // setAge(+e.target.value);
    setInputs({...inputs, age: e.target.value})
  };

For the rest of the project, I’ll manage multiple states per component due to preference – you do you 👍

Event handlers with React components

React uses special “on” props for tags that represent event handlers. These props take a function as a value to execute when an event occurs.

The event handler points to a function defined outside of the return statement. They don’t execute the function to avoid premature execution (hence the omitted parenthesis).

Unlike the inline JS in HTML, React event handlers:

  1. Have special syntax (i.e. onClick instead of onclick)
  2. Act as pointers to an executable function

The functions they point to conventionally end with “Handler”, though, this is unnecessary and varies across React developers.

As a little demo, let’s add onChange events to the name and age inputs.

onChange={nameHandler}
onChange={ageHandler}

Logical walkthrough: When there’s a change in either input, the onChange event triggers. Depending on the input affected, the pointed handler fires updating the input values to the entry value.

Similarly, listen for the form submission, onSubmit={formSubmitHandler} , and, in the formSubmitHandler console log the two input variables.

Updating state of inputs React.

Child-to-parent component communication

Next, we need to get the updated inputs onto our USERS list defined in the App component file.

The flow of the data will occur from child, Form.js, to parent, App.js. The question is, how do we get the two input variables to the App component?

Well, it’s not all that complicated. We can achieve the child-to-parent dataflow in three steps:

  1. On the parent, define a custom event with a value that is a handler function.
  2. The handler function receives data from the child, spreads it, and, optionally, adds additional data.
  3. In the child, execute the handler function inherited from props, passing child data to the parent as a parameter.

Note ‼️
Props can’t skip intermediary components so information flows across React components must go up or down each component of a branch.

What this looks like is this 👇

Initialize state in App.js

Set the initial state for the USERS array and make sure the users passed onto the List component point to the newly assigned users variable instead of USERS.

👉 let [users, setUsers] = useState(USERS)

Define a custom event with a handler function

On the Form component, define a custom event that points to what’ll be the handler function accepting the child data.

👉 newUser={newUserDataHandler}

Follow by defining the handler like so:

const newUserDataHandler = (data) => {
    // data => {name, age} from form
    setUsers((currUsers) => [...currUsers, data]);
  };

The function newUserDataHandler accepts some data, then updates users by copying the existing array using the spread operator before adding the incoming data.


Note: Updating a React State Based on an Existing State

When updating a state that depends on a previous state, as in the example above, use a callback function.

The callback automatically gets the previous state snapshot through a default argument. You can call the default argument whatever you desire, I went with currUsers.

Here, currUsers references the existing state of the users. When I spread it into the new array, I’m retaining all data as it currently exists before adding the new data.

Since USERS is an array of objects, the new information coming from the form submission will be in the form of an object with two properties – name and age.


Tip: Always get the latest state as React keeps scheduled updates in mind. Otherwise, your logic might depend on outdated state!

Pass data from child-to-parent

Moving to Form.js, instead of console logging the inputs in formSubmitHandler, let’s store them in a variable called data as an object.

Then pass data into props.newUser that accepts an argument since newUser points to a handler expecting one.

let data = { name, age };

props.newUser(data);

Heading back to App.js, if I console log the new users we get:

Child to parent data flow React.

Clearly, THT is the younger of the two no matter how you shuffle the integers 😔

Rundown: Define a function with a parameter in the parent component. Pass the function as a prop to a child component where you access it via props. Execute using data as an argument.

Two-way binding

There’s one more little, yet, important bit that needs addressing on the Form component and that’s a concept called “two-way binding”.

Two-way binding refers to when you feed a state change back to an input or other component creating a controlled component.

It’s great for forms because it allows (a) to gather user input and (b) change it on submission.

Why you need two-way binding

Take our work so far as an example. We’re now successfully gathering user input, updating and storing it in a variable that we pass along to the parent component where we proceed to save it in our “database”.

But when the form is submitted, the entered values remain on the input fields prompting a user to either refresh the page or manually delete the existing inputs to enter new ones.

I’ll let you on to a little secret: Expecting a user to do much of anything other than click and enter is going too far 🤫

Instead, when the form is submitted, the inputs should clear to ready themselves for another entry. This means another state change for name and age after the data is passed onto the parent.

Tip 🧐
If you update the input states prior to sending over the collected data, you’ll overwrite the information with the defaults!

A completed formSubmitHandler :

const formSubmitHandler = (e) => {
    e.preventDefault();

    let data = { name, age };

    props.newUser(data);

    setName('');
    setAge('')
  };

Notice it’s not enough to update the input states – the entered user info still populates the inputs. They need a reset!

We must go a step further to implement two-way binding by passing the values of name and age back to the inputs so they reflect current information.

A completed form element:

<form className={`${styles.form}`} onSubmit={formSubmitHandler}>
        <label for="name">Name</label>
        <input
          type="text"
          id="name"
          name="name"
          value={name}
          onChange={nameHandler}
        />

        <label for="age">Age</label>
        <input
          type="number"
          id="age"
          name="age"
          max="100"
          min="1"
          value={age}
          onChange={ageHandler}
        />

        <Button type="submit">Add User</Button>
</form>

And it’s a wrap! We’re now four parts into this project 🎊 

This post gave substance and made our app “work” through event handlers and data processing.

Aren’t we bloody awesome 🙃

The full source code is on GitHub, but keep a lookout for part five where we tackle rendering lists and implementing conditionals.

TTFN 👋

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Related Posts