Designing React Hooks the Right Way

By Fang Jin
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    Chapter 2: Crafting States in Functions
About this book

React hook creates a unique solution for using states in function components to orchestrate UI communication. They provide you with an easy interface to write custom data management solutions with low development and maintenance costs. Understanding how Hooks are designed enables you to use them more effectively, and this book helps you to do just that.

This book starts with a custom-crafted solution to reveal why Hooks are needed in the first place. You will learn about the React engine and discover how each built-in Hook can manage a persistent value by hooking into it. You will walk through the design and implementation of each hook with code so that you gain a solid understanding. Finally, you'll get to grips with each Hook's pitfalls and find out how to effectively overcome them.

By the end of this React book, you'll have gained the confidence to build and write Hooks for developing functional and efficient web applications at scale.

Publication date:
January 2022
Publisher
Packt
Pages
278
ISBN
9781803235950

 

Chapter 2: Crafting States in Functions

In the previous chapter, we learned how to write function components in React. In this chapter, we will craft a special variable called a state in the function components. We will see what benefits a state can bring us, including requesting a new update, making a variable persistent, listening to a value change, as well as performing tasks upon the mount. We will also see an example of applying a state to a single-page application. In the end, we will look closely at what role the states play within the UI.

We will cover the following topics in this chapter:

  • Crafting a state in a function component
  • Applying states to single-page applications
  • How states work with UIs
  • Questions and answers
 

Technical requirements

Before beginning, I would like you to know about the timeline sketch:

|--x---x---x-x--x--x------> user event

The timeline sketch is a unique illustrative chart type that displays a series of events during a period. The left bar (|) stands for the time origin, representing the first update. The horizontal dash (-) moves with the time from left to right with an arrow > at the end. Each letter or number, such as x , indicates one event that happened in this timeline. In this book, we will use the timeline sketch to better understand a situation when multiple things happen simultaneously along the timeline.

 

Crafting a state in a function component

When you visit a typical web page, it asks for your username and password. After you log in, it displays the content of what the website provides, such as blogs, tweets, or videos, in a chronological order. You can vote on them and put your comments there – a very typical web experience these days.

When you surf a website like that as a user, you don't put too much thought into how any of the actions are implemented, nor do you care about the order in which each is fired. However, when it comes to building the site yourself, each action and the time at which each gets fired starts to become important.

An action handler fires when a user clicks a button, hovers over an icon, scrolls down a paragraph, types on the keyboard, and so on. A typical relationship between a user event and an action handler is illustrated in the following:

|--x---x---x-x--x--x------> user event
|--a---a---a-a--a--a------> action handler

In the preceding sketch, basically, an x in the user event series is followed by an a in the user event series. Based on this, we can start to handle a user action.

Let's turn ourselves to a "Hello World" Title component with a button inside. Each time we click the button, a counter gets incremented by one and appended after Hello World+, as shown in Figure 2.1:

Figure 2.1 – Hello World with no state

Figure 2.1 – Hello World with no state

To implement that, we start with a count variable to store a number initialized as 0:

function Title() {
  let count = 0  
  const onClick = () => {
    count = count + 1
  }
  return (
    <>
      <button onClick={onClick}>+</button>
      <h1>Hello World+{count}</h1>
    </>
  )
}

In the preceding Title component, the response to the user click is implemented via a React event handler, onClick, wired to a button element.

A React event handler is written slightly differently from a DOM event handler. You can tell from the onClick camel case name, rather than the onclick lowercase name. A React event is a synthetic event that is a cross-browser wrapper around the browser native event. In this book, we expect them to behave in exactly the same way.

Thanks to the JavaScript closure, we can directly access any component variable inside the event handler. The count variable does not need to be passed into onClick as a function input argument to be accessed.

If we run the code, we'd expect the title to display Hello World+1 after we click the button. But to our surprise, no matter how many times we clicked the button, it still displayed Hello World+0. To figure out what happened, let's add console.log to two locations.

One is placed before count = count + 1 to confirm what the count is after incrementation. Another one is placed before the return statement to confirm what the updated count is when the Title component is updated. They are marked at and ➁ in the following code:

function Title() {
  let count = 0
  const onClick = () => {
    console.log('clicked', count)      ➀
    count = count + 1
  }
  console.log('updated', count)        ➁
  return ...
}

With these two logs placed, we can rerun the code and generate a new timeline sketch:

|----0--1-2--3-4----5------> clicked   ➀
0--------------------------> updated   ➁

From the preceding printout, a clicked series at ➀ showed the count number when the button was clicked, and it was clicked six times. Let's turn to another log, the updated series at ➁; the count value got updated once as 0, which explains why the display remained as Hello World+0.

The updated series with only one printout at the very beginning indicates that there weren't any more updates after the first one. This is quite a discovery. If there were no more updates, how can we expect to see a change on the screen?

Playground – No State

Feel free to play with this example online at https://codepen.io/windmaomao/pen/jOLNXzO.

As you might already realize, we need to request a new update after the click.

Requesting a new update

To make an update, for the time being, we can borrow the render function provided by React, as we have already used it to update the rootEl element:

ReactDOM.render(<Title />, rootEl)

Let's take a minute to see how React updates the screen in general (see Figure 2.2). The detail involving updates can be quite complex; for now, let's treat it as a black box. We will get into more details later in the book:

Figure 2.2 – React update

Figure 2.2 – React update

When an app starts, it lands on an update. This first update is a bit special. Because all the DOM elements need to be created, we refer to this update as a mount.

What's important to know is that a new update wouldn't arrive unless it's requested, just as we invoke a render function. When people first come to React, they might think it works as a game engine.

For instance, a game engine would request a new update every 1/60 second behind the scenes. But React does not do that! Instead, the developer should get precise control of when a new update is requested. And most of the time, the frequency is a lot lower than 1/60 second, and it's more or less driven by how fast a user acts on the website.

So with this, to bring the new count to the screen, another update needs to be requested manually; if we borrow the render, we can use it after the count is incremented:

  const onClick = () => {
    console.log('clicked', count)      ➀
    count = count + 1
    ReactDOM.render(<Title />, rootEl)
  }

If we run the preceding code with the addition of render, the timeline sketch changes to the following:

|----0--0-0--0-0----0------> clicked   ➀
0----0--0-0--0-0----0------> updated   ➁

To our surprise, all numbers displayed were 0. Looking at the updated series at , note we got seven printouts, which means we got six more updates on top of the first update. However, the clicked series at shows that the count value changed to 0 and stopped to increment any more. Weird?!

How could the count value be stuck at 0? Something must happen to the new update, but the render function can't be the one that resets the count value back to 0, can it?

It's important to know that upon the render function being called and a function component being updated, the function that defines the component gets invoked, as shown in Figure 2.3:

Figure 2.3 – React render for a function component

Figure 2.3 – React render for a function component

With this knowledge, let's take a look at the Title function again:

const Title = () => {
  let count = 0
  // omitting the onClick statement
  console.log('updated', count)       ➁
  // omitting the return statement
}

In the preceding code, we intentionally omit the onClick and return statements to make the code a bit cleaner. What was left became a let count = 0 declaration statement. During each update, the Title function gets invoked, thus creating a new scope of the function. Inside this scope, there's a variable count value created locally to hold a 0 number. So this code doesn't seem to do much.

It's not too difficult to see now why the count value remains at 0, isn't it? It doesn't really matter if we have added the increment logic onClick or return statement. Upon each update, the entire function scope gets a new one with a count value declared and set to 0. That explains why the console.log statement followed a printed 0.

This is actually the reason why a function component was named as a stateless function when it was introduced to React initially. "Stateless" refers to the fact that a function component can't carry or share a value to another update. In a simple word, the function reruns in each update with the same output.

Okay, now we understand the problem. So, it makes us consider saving the count value somewhere and making it persistent for another update.

Making a value persistent

JavaScript supports a function scope: Variables defined inside a function cannot be accessed from anywhere outside the function, thus each function has its own scope. If you invoke a function multiple times, there'll be multiple scopes. But no matter how many times we invoke it, it wouldn't create a different output, such as what happened in the movie Groundhog Day.

Note

The movie Groundhog Day is a 1993 fantasy comedy film, where Phil wakes up every day to find he experiences the previous day's events repeating exactly and believes he is experiencing déjà vu.

For our count value, we can visualize what happened with the two updates in two different scopes in Figure 2.4:

Figure 2.4 – Two function scopes for the two updates

Figure 2.4 – Two function scopes for the two updates

Luckily, JavaScript supports a function scope in a way that it can access all variables defined inside the scope in which it is defined. In our case, if a variable is defined outside of the Title function, we can access this variable inside the Title functions, as this value is shared now between multiple Title functions.

The easiest way of sharing is to create a global variable because the global variable lives in the most outer scope of the JavaScript code, thus it can be accessed inside any function.

Note

Don't be intimidated by a global variable used in this chapter. In Chapter 3, Hooking into React, we will refine this approach and see how React defines the variable in a better location.

This way, each local count value can set/get this global count value, as shown in Figure 2.5:

Figure 2.5 – A shared value among the two updates

Figure 2.5 – A shared value among the two updates

Okay, with this new global variable idea, let's see whether we can break out of our Groundhog Day situation:

let m = undefined
function _getM(initialValue) {
  if (m === undefined) {
    m = initialValue
  }
  return m
}
function _setM(value) { 
  m = value
  ReactDOM.render(<Title />, rootEl)
}

In the preceding code, a global variable, m, is allocated, and it comes with _getM getter and _setM setter methods. The _getM function returns the value but sets the initial value for the first time. The _setM function sets the value and requests a new update. Let's apply _getM and _setM to our Title component:

function Title() {
  let count = _getM(0) 
  const onClick = () => {
    console.log('clicked', count)      ➀
    count = count + 1
    _setM(count)
  }
  console.log('updated', count)         ➁
  return ...
}

Inside the preceding amended Title component, all count variables across updates are linked with the help of _getM and _setM. If we rerun the code, we can see the following timeline sketch:

|----0--1-2--3-4----5------> clicked   ➀
0----1--2-3--4-5----6------> updated   ➁

Wow! The screen changes to Hello World+1 upon the first click and increments further upon more clicks, as shown in Figure 2.6:

Figure 2.6 – Hello World counter using a state

Figure 2.6 – Hello World counter using a state

Congratulations! You just crafted a state inside a function component.

Playground – Count State

Feel free to play with this example online at https://codepen.io/windmaomao/pen/KKvPJdg.

The word "state" refers to the fact that it's persisted for all updates. For our convenience, we also change the state and request a new update afterward to reflect the change to the screen.

So, now we know how to handle a user action with a state. Let's see whether we can expand this idea further to support multiple states instead of one state.

Support multiple states

It's great that we can establish a state persistent within a function component. But we want more states like that. An app normally contains lots of buttons, switches, and actionable items; each requires a state to be persistent. So, it's a must-have to support multiple states in the same app.

So, say we need two buttons and each needs to be driven by a state. Let's extend what we have learned from a single state:

const Title = () => {
  let countH = _getM(0)
  let countW = _getM(0)
  const onClickH = () => {
    countH = countH + 1
    _setM(countH)
  }
  const onClickW = () => {
    countW = countW + 1
    _setM(countW)
  }
  return (
    <>
      <button onClick={onClickH}>+</button>
      <h1>Hello+{countH}</h1>
      <button onClick={onClickW}>+</button>
      <h1>World+{countW}</h1>
    </>
  )
}

In the preceding code, we first created two buttons, one with a Hello label and one with a World label, and each have their separate event handler, onC1ickH and onClickW respectively. Also, we applied _getM and _setM to both of them, and installed a couple of logs to help the debug, as shown in the following timeline sketch:

|----0--1-2----------------> clickedH   
|------------3-4----5------> clickedW   
0----1--2-3--4-5----6------> updatedH   
0----1--2-3--4-5----6------> updatedW   

From the preceding sketch, we clicked the Hello button three times and then clicked the World button three times. The numbers corresponding to both buttons all updated upon clicking, as shown in the updatedH and updatedW series. However, the two series seem to be inseparable and in sync, meaning clicking one button would increment both values at the same time!

Playground – Linked States

Feel free to play with this example online at https://codepen.io/windmaomao/pen/qBXWgay.

Okay, it's not too difficult to find out that we actually made a mistake by wiring the same state to both buttons; no wonder they updated at the same time:

  let countH = _getM(0)
  let countW = _getM(0)

Although this is not what we wanted to achieve, it's interesting to see that a state is shared by two buttons. Visually, we linked two buttons; clicking one triggers the click on another.

So, what can we do if we want to have two separate states with each controlling one button? Well, we can just add another state. This time, we want to be a bit more generic in using a list to hold any number of states.

There are lots of ways to keep track of a list of values in JavaScript; one of the ways is to use a key/value pair, as in an object:

let states = {}
function _getM2(initialValue, key) {
  if (states[key] === undefined) {
    states[key] = initialValue
  }
  return states[key]
}
function _setM2(v, key) {
  states[key] = v
  ReactDOM.render(<Title />, rootEl)
}

In the preceding code, we declare a states object to store all state values. The _getM2 and _setM2 functions are almost similar to the single-value version we crafted earlier, except this time we store each state under states[key] instead of m, thus a key is needed to identify each state. With this change, let's amend the Title component:

function Title() {
  let countH = _getM2(0, 'H')
  let countW = _getM2(0, 'W')
  const onClickH = () => {
    console.log('clickedH', countH)     
    countH = countH + 1
    _setM2(countH, 'H')
  }
  const onClickW = () => {
    console.log('clickedW', countW)     
    countW = countW + 1
    _setM2(countW, 'W')
  }
  console.log('updatedH', countH)         
  console.log('updatedW', countW)          
  return ...
}

In the preceding amended version, we give a key to two states as H and W. We need this key for both set and get when a state is involved. Rerun the code and take a look at the timeline sketch:

|----0--1-2----------------> clickedH  
|------------0-1----2------> clickedW  
0----1--2-3--3-3----3------> updatedH  
0----0--0-0--1-2----3------> updatedW  

Once again, we clicked the Hello button three times and World button three times in a row. The numbers on both buttons all updated upon clicking, but this time, countH and countW are actually incremented separately, as you can see in the updatedH and updatedW series.

After the first three clicks on the Hello button, countH stays at 3 when we click on the World button. This is what we want to have, two separate states, as shown in Figure 2.7:

Figure 2.7 – Hello and World buttons with two states

Figure 2.7 – Hello and World buttons with two states

Playground – Multiple States

Feel free to play with this example online at https://codepen.io/windmaomao/pen/dyzbaVr.

The state we crafted so far requests a new update. This is a very good use of persistency in a function component; since being persistent is actually quite a generic feature, it should be utilized for many different purposes. So, what other things can we do with it? Let's take a look at another usage of a state.

Listen to a value change

You might wonder why we need to listen to a value change. Aren't the developers the ones who control the change of a value? As in the previous example, we use the event handler to change a counter. We know in this case exactly when the value gets changed.

That's true for this case, but there are other cases. You might send a value into a child component via a prop, or there might be two components that touch a value at the same time. In either of these cases, you can lose track of the moment when the value is changed, but you still want to perform an action upon the value change. This means that you want to have the ability to listen to a value change. Let's set up one example to demonstrate this.

Say in our Hello World button example that for any count change, we want to know whether this value has recently been changed:

function Changed({ count }) {
  let flag = 'N'
  return <span>{flag}</span>
}

In the preceding Changed component, there's a count prop that is sent from its parent, say any of the Hello or World buttons that we built earlier. We want to display Y or N, depending on whether the count value has changed. We can use this Changed component in the Title component:

function Title() {
  ...
  return (
    <>
      <button onClick={onClickH}>+</button>
      <h1>Hello+{countH}</h1>
      <Changed count={countH} />
      <button onClick={onClickW}>+</button>
      <h1>World+{countW}</h1>
    </>
  )
}

Note that in the preceding code, we add the Changed component between two buttons, and what we want to see is the Changed component display Y when we click the Hello button, and the Changed component display N when we click on the World button. Essentially, we want to know whether the change is coming from the Hello button or not. But when we ran the code, here's what we got in the timeline sketch:

0----1--2-3--3-3----3------> updatedH   
0----0--0-0--1-2----3------> updatedW   
N----N--N-N--N-N----N------> Changed flag

From the preceding sketch, you can see that no matter which button is clicked, the flag in the Changed flag series displayed N. This comes as no surprise, since you might have already noticed that the flag inside the Changed component is fixed at N, so it wouldn't work the way we wanted. But the reason we wrote N there is because we don't know what to write there to flip the flag.

When the Hello button gets clicked three times, the countH value, as in the updatedH series, increments to 3. Similarly, when the World button gets the next three clicks, the countW value, as in the updatedW series, increments to 3. However, note that as the countW value increments, the countH value also gets printed out; see 3-3-3 in the updatedH series.

This indicates that for each update, every element under the return statement gets updated. Either countW or countH changes; it comes to a new update of the Title component, thus updating all button and h1 elements. The same applies to the Changed component; whichever button changes, the Changed function gets invoked. Therefore, we can't tell whether the update to the Changed component is due to the Hello button or the World button.

If we print out the count prop under the Changed component, it will look the same as in the updatedH series:

0----1--2-3--3-3----3------> count

Looking at the preceding count value, in order to come up with the changed flag for whether it changes from the previous value, we need to make a value persistent again – in this case, to get hold of the previous value. For example, 0 to 1 is a change, but 3 to 3 isn't.

Okay, to put this idea to work, let's borrow the state approach but this time apply it to a prev value:

let prev
function _onM(callback, value) {
  if (value === prev) return 
  callback()
  prev = value
}

In the preceding code, we allocated a prev global variable and a _onM utility function. The onM function is designed to run a callback function when the value changes. It first checks whether the value is equal to the prev value. It returns if there's no change. But if there is, the callback function is then invoked, and the current value replaces the prev value. Let's apply this _onM function to the Changed component:

function Changed({ count }) {
  let flag = 'N'
  _onM(() => { flag = 'Y' }, count)
  return <span>{flag}</span>
}

With the preceding change, we rerun the code and take a look at the updated timeline sketch:

0----1--2-3--3-3----3------> updatedH 
0----0--0-0--1-2----3------> updatedW 
Y----Y--Y-Y--N-N----N------> Changed flag

Interestingly enough, when we clicked the Hello button this time, it displayed Y, and when we clicked the World button afterward, it changed to N, as shown in Figure 2.8:

Figure 2.8 – Listen to value change

Figure 2.8 – Listen to value change

Wonderful! Also, notice the first Y at the mount in the Changed flag series, which is when countH changes from undefined to 0. Please make a note here; we'll talk about it in the next section.

Playground – Listening to State Change

Feel free to play with this example online at https://codepen.io/windmaomao/pen/MWvgxLR.

Being able to listen to a value change is quite useful because it provides us with another way to perform tasks. Without it, we have to rely on an event handler, which is mostly driven by user actions. With _onM, we can perform a task upon a value change, which can come out of any other process.

When listening to a value change, there exists a moment at the mount. This means that we can perform a task at the mount because of it. Let's take a look at it more closely.

Performing a task at the mount

Components mount and un-mount as things show up and disappear based on the business requirement. At the mount, it's common to want to perform a task such as initializing some variables, calculating some formulas, or fetching an API to get some resources over the internet. Let's use an API call as an example.

Say a count value needs to be fetched from an online service called /giveMeANumber. When this fetch returns successfully, we would like to reflect the change to the screen:

fetch('/giveMeANumber').then(res => {
  ReactDOM.render(<Title />, rootEl)
})

The preceding code is what we'd like to do; however, we run into a technical issue right away. Though a new update can be requested, how can we send the returned data to the Title component?

Maybe we can set up a prop on the Title component to send it in. However, doing that would require us to change the component interface. Since we already have had states crafted to issue a new update, let's try that approach:

fetch('./giveMeANumber').then(res => {
  _setM(res.data)
})
function Title() => {
  const count = _getM("")    
  return <h1>{count}</h1>
}

In the preceding code, by using _setM after the fetch returns, we can update a state with the received res.data and request a new update afterward. The new update invokes Title and reads the latest count from the state via _getM.

Currently, we define the fetch function parallel to the Title component, but this is not the right location since we want to fetch only at the mount. To fix that, we can listen to the mount, as we have learned in the previous section:

 _onM(() => { ... }, 0)

Using the preceding line, we can listen for a mount moment. Note that we watched a constant 0 instead of any variable. During the mount, the value that _onM listens to changes from undefined to 0, but for other future updates, the value stays at 0; therefore, the ... callback gets invoked only once at the mount. Let's write fetch inside this callback:

function Title() => {
  const count = _getM(0)
  _onM(() => {
     fetch('./giveMeANumber').then(res => {
       _setM(res.data)
     })
  }, 0) 
  console.log('u')
  return <h1>{count}</h1>
}

If we run the preceding code, the timeline sketch should generate the following:

u-----u-------------------> log

At the mount of the Title component, the count state is set to be 0 initially. A fetch function is performed right away, depicted as the first u in the preceding updates series. Only when fetch returns successfully does the count state get updated to a new value and refreshed to the screen. The new update is depicted as the second u in the updates series.

Playground – Task at Mount

Feel free to play with this example online at https://codepen.io/windmaomao/pen/PoKobVZ.

Between the first and the second update, that's how long it takes for the API to finish. The relationship between the API, the state, and two updates is illustrated in Figure 2.9. Essentially, after the API returns, it communicates to the shared state where the new update picks up later:

Figure 2.9 – Fetch API within the stateful component

Figure 2.9 – Fetch API within the stateful component

Now that we have crafted a state, and also seen how flexible a state can be used to either make a new update or listen to a value change, let's get hands-on and apply what we have learned to an app.

 

Applying states to single-page application

We want to continue what we started in the previous chapter in building a single-page application. We couldn't finish it back then because we lacked a way to switch to a different page other than the home page. We had put together a Nav component:

const Nav = ({ items, selected }) => { ... }

Given a list of pages, the Nav component displays them as links to navigate. The currently selected page needs to be provided as well. Now that we know how to define a state, let's use it to keep track of the selected page:

const App = () => {
  const selected = _getM("home") 
  return ( 
    <div>
      <Nav
        items={menus}
        selected={selected}
        onSelect={_setM}
      />
      ...
    </div>
  )
}

In the preceding App component, we used a state for selected to hold the home key initially, which is then passed into the Nav component. To allow the state to be updated after a user click, we need to modify Nav by adding the support of an onSelect callback function:

const Nav = ({ items, selected, onSelect }) => {
  const isActive = item => item.key === selected
  const onClick = item => () => { 
    onSelect(item.key)
  }
  ...
}

In the preceding amended Nav component, an onSelect prop is passed so that after onClick, the parent App component can be notified to update the selected page via the _setM function.

To confirm that the user does reach a different page, based on the current selected page, we can use a Route component to switch between page content:

const Route = ({ selected }) => {
     return (
       <div>
      {selected === 'home' && <Home />}
      {selected === 'product' && <Product />}
    </div>
     )
}

What the preceding Route component does is display the page content based on the selected page. Note that it uses a && symbol, which is a common line in React code. It's equivalent to the following:

      {selected === 'home' ? <Home /> : false}

If the condition matches on the left part, it returns <Home />; otherwise, it returns false. And according to React, any true, false, null, or undefined values are all valid elements, but when updated, they all get ignored without being displayed. Essentially, if the left part condition doesn't meet, it displays nothing.

Putting the Nav and Route components together, we can amend the App component:

const Home = () => <h1>Home page</h1>
const Product = () => <h1>Product page</h1>
const App = () => {
  const selected = _getM("home")  
  return ( 
    <div>
      <Nav
        items={menus} 
        selected={selected} 
        onSelect={_setM} 
      />
      <Routes selected={selected} />
    </div>
  )
}

Finally, we got two pages working, as shown in Figure 2.10! If you click the Product link, it'll land on the product page:

Figure 2.10 – A single-page application using a state

Figure 2.10 – A single-page application using a state

To recap, the App component defines a selected state to hold the currently selected page. The Nav component is used to display all the links and allow it to choose a different page by clicking on the link. The Route component is used to display a page based on the selected state. Essentially, based on this setup, adding more pages is just a matter of adding new components under the Route component.

Playground – Single-Page Application

Feel free to play with this example online at https://codepen.io/windmaomao/pen/PoKoWPG.

Before we end this chapter, let's take a minute to look at how exactly a state drives the UI under React.

 

How states work with UI

With the introduction of state to the function component, we sometimes can get dizzy by the roles that it plays. We will use three components to elaborate, as shown in Figure 2.11:

Figure 2.11 – Props in components

Figure 2.11 – Props in components

We have three components depicted in solid boxes. The outer component contains the middle one as a child, and the middle one contains the inner one as a child. Props, depicted as arrow lines crossing the boundary of a solid box, pass values from a parent to a child component.

React is a state machine. For a given fixed set of variables, it paints the screen the same way. With props, this is quite straightforward since each component is solely determined by its props. Now, let's add the states to the picture, as shown in Figure 2.12. States, depicted as a symbol with a circle and a dot, are defined inside each component:

Figure 2.12 – States and props in components

Figure 2.12 – States and props in components

Taking the C inner component first, it doesn't have any state defined. So, it's still determined by its props.

The B middle component has one state defined. With a fixed set of its props, the screen corresponding to the component still can vary because this state can take a different value on each update.

The A outer component has two states defined. Similarly, with all its props fixed, the screen corresponding to it can still vary. The variation can come from any of its two states, and it can come from the state of the B component as well because the states of the parent and the child can work independently upon updates.

Therefore, we can conclude that to get the screen painted for the A component, we need to fix all props and states within itself and all its child components underneath. This is not a mathematical theory, but given the states from multiple components, this observation is apparent.

In short, props and states now both serve as the input of the component. The states can be especially vibrant since their values can be, but are not always, wired with an external system. The external system can be browser events or the API fetch, or anything else. Because a state can send to a child component via a prop, the effect of the state can cascade down deep into the app tree quickly.

 

Summary

In this chapter, we started to craft a new thing called a state inside the function component. The state is persistent across updates and can be used to request a new update, listen to a value change, as well as perform a task at the mount. Later, we applied the state we developed to a single-page application to draft a simplified Nav with a route system. In the end, we briefly studied how states work for UI under React.

In the next chapter, we will introduce you to what a React hook is and how this persistent state is designed under the React engine.

 

Questions and answers

Here are some questions and answers to refresh your knowledge:

  1. What is a state?

    For a function component, a state is a value created to be persistent during the life of the component. From each update, including the mount, this value can be accessed from inside a function.

  2. What are the usages of states?

    If a task can't be done within one update, that is the time we can think of using a state to reference a memory that can be accessed in multiple updates. We normally use a state to request a new update, listen to a value change, as well as perform a task at the mount. But the states can be very versatile.

  3. What does a state do to the UI?

    To determine the screen corresponding to a component, we need to know its states as well as its props. While the props are passively defined on the component interface, the states are defined inside the component to actively refine its behavior. Apps built with states can change with time, driven by either user interactions or any other external processes.

About the Author
  • Fang Jin

    Fang Jin is a software engineer that makes reusable and scalable web modules that can be applied to modern browsers and devices. He is an engineering hobbyist with interests ranging from economics and philosophy to software development, architecture, and team development. Fang is a lifelong learner and during his journey, he has helped organizations and teams adopting UI frameworks with consistent branding look and feel across departments to save cost and boost efficiency. In his free time, he likes swimming and surfing YouTube.

    Browse publications by this author
Designing React Hooks the Right Way
Unlock this book and the full library FREE for 7 days
Start now