Skip to content

Latest commit

 

History

History
692 lines (428 loc) · 21.1 KB

intro-to-state.md

File metadata and controls

692 lines (428 loc) · 21.1 KB

Title: Intro to State
Duration: 1.5 - 2hrs
Creator: Joe Keohan


Intro to State

Learning Objectives

After this lesson you will be able to:

  • Explain what state is and how to implement it in a React Component
  • Create state using the useState Hook
  • Update state to trigger Component to re-render

Framing

The best analogy to understand React state is to answer the following question: How are you feeling this very moment?

  • Are you happy to be in class learning a new topic?
  • Are you feeling tired after a long day at work?
  • Did some random person say hello out of the blue and make you smile?

The answer to any of those questions has a direct impact on your state of mind. A happy state will be reflected in your smile, tone of voice, and perhaps even being nice to others in return. An unhappy state will have the opposite effect.

As with all human beings our state of mind can change on the fly which is almost always reflected in our physical expressions or actions. Applications also have a state which is reflected in the UI presented to the user.

Therefore updating an applications state is our control mechanism for how we update the UI.

Props Recap

So far we've worked with props and used them to pass values down from a parent > child Component. This pattern of passing data down is consistent with React's unidirectional data flow pattern.

We also know that the props passed down to a child are organized, by React, into an object where every prop becomes a key:value pair.

Props are a great way to pass data but have the following limitations:

  • Props must be passed down from parent > child
  • Reassigning a prop value will have no effect in the Component.
  • Child Components cannot use props to pass data to their parent or other sibling Components

🔍 - Check for Understanding

Take a a minute to think about the following question:

  • Is there any best practice you can think of when creating and passing a prop?

When asked slack your answer(s) in a thread created by the instructor


Intro To State

In our attempt to provide a coherent framing of React state the point was made that what you see on the page is the current version of the applications state. Any changes to state will then be reflected in the UI.

One important thing to note here is that any changes to state will cause the Component to re-render. This is essentially how the UI is updated.

This is a very important concept to keep in mind as a re-render can also initiate additional function calls, something we will discuss as part of Reacts lifecycle methods.

React Lifecycle

React Hooks Lifecycle

Working With State

So updating state will, most often, require the user to interact with the application. Hence, the user performs some action, like clicking a button, and the component responds by doing a thing which results in changing some values and then updating state. The steps are as follows:

A Simple Counter Component

We'll walk through building a very simple Counter Component which will do the following:

  • provide the user 2 buttons to increment or decrement
  • display the initial and updated value as it changes

Spin Up A New CodeSandbox

For this demo you will spin up a new CodeSandbox. To do this just click on the blue Create Sandbox button on the right of the page.

Now scroll down to the Official Templates section and choose React by CodeSandbox.


👍 Click on the thumbs up when your done.

Creating The Counter Component


⏰ Activity - 5min

Since you already have experience creating Components take a minute to perform the following activity.

Counter Component

  • Create a new file in src called Counter.js
  • Import React from 'react'
  • Create the Counter Component
  • Return the following HTML:
<>
    <span>Current Count: 0</span>
    <section>
        <button>+</button>
        <button>-</button>
    </section>
</>
  • Export the Component

App Component

  • Import the Counter Component into App.js
  • Replace all the html inside of className="App" with the Counter Component.
<div className='App'>
	<Counter />
</div>

Once your done React should render the following:

That HTML looks like it could use a little styling. So lets copy/paste the following css to styles.css

CSS
.App {
	font-family: sans-serif;
	text-align: center;
	width: 160px;
	margin: auto;
	display: flex;
	flex-direction: column;
}

section {
	display: flex;
}

button {
	flex: 1;
}

span {
	font-size: 20px;
}

And now the design should look like this:


The useState Hook

In order to add state to the Counter Component we will first need to import the useState Hook from React. useState is one of the 3 Basic Hooks as per the Official React Doc.

A Word On Hooks

Hooks were introduced in React Version 16.8. Before hooks, state was only available to a Class component.

Hooks introduce state management to Functional components, using a simpler and more flexible API that let you split one component into smaller functions based on what functionality was needed.


⏰ Activity - 2min

Since we will be working with Hooks solely in this class let's take a minute to review the following React Docs:


Importing useState

Now it's time to import useState into the Counter Component. The useState function resides inside the React object so let's first peak inside.

import React from 'react';
console.log('this is React', React)

Here are a few of the Hooks available, including useState

From the console log output it's clear to see that these Hooks are key:value pairs inside the React object.

Since we were introduced to Object Destructuring let's use it to elicit the useState function and store in a variable.

import React, { useState } from 'react';

Just so that we get a better idea of what useState actually is let's console log it as well.

const Counter = () => {
  console.log('useState - ', useState)
  // ...rest of code
}

The output should look like the following:



It appears that useState is a function that takes in initialState and returns dispatcher.useState(initialState).

We won't get into the underlying code here but one thing to highlight is the keyword dispatcher.

We will revisit this concept later when we cover the useReducer hook as it uses a similar naming convention of dispatch for its setState function.


useState Rules and Best Practices

Let's take a moment to once again review the rules of useState and cover some best practices as well.

🚔 - Rules

  • the state value is never updated directly
  • the state value is only updated using it's corresponding setState function
  • the state value must always be overwritten with a new value

⭐ - Best Practices

  • Use Array Destructuring when initializing the state variables
  • Name the initial state based on what it contains
  • Use the same name for the setState function but precede it with the word set
  • Give thought as to what needs to be in state and how that state should be organized
  • Always use ...spread operator to copy object and array values to the new state

Creating An Instance Of State

With useState imported it's time to create an instance of state. To do this we will call useState() and pass it an initial starting value of 0.

⭐ - Name the initial state based on what it contains.

const countState = useState(0);

Once again let's add a console log and see what it returns.

const countState = useState(0);
console.log('countState -', countState);

We should see the following:

So it appears countState is set to an array that contains the following elements:

  • 0 - the initial state value we defined
  • a function - which will be used to update state.

Array Destructuring

⭐ - Use Array Destructuring when initializing the state variables: Array Destructuring.

Array Destructuring elicits the values from the array based on their position and stores them in variables.

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

Using State

Now that our initial value is been assigned to the count variable let's update the JSX to use that value instead of a current hard coded value of 0.

Of course JSX requires that all JavaScript be surrounded in curly braces.

return (
  <div>
    <span>Current Count: {count}</span>
    ... rest of code
  </div>
);

⏰ Activity - 1min

With our event handlers in place let's take a look at React DevTools

If you highlight Counter it should look like the following:


Updating State

With our state value in place it's time to provide some functionality to the buttons and allow the user a means to interact with the app and update state.

In the case of our Counter the only way to update count is to call the setCount function and pass it a new value.

🚔 - Always use the setState function to update state

There are 2 ways to perform this action:

// grab the current version of state
setCount(count + 1);

// OR

// use a callback function and pass the previous version of state
setCount((prevState) => prevState + 1);

In the second example the setter function takes in a callback function that is passed the previous value of state and returns a new value altogether.

The argument in this example is called prevState by convention but you can name it anything you want.

There are scenarios when the callback function version is required, such as when state is being updated within the callbacks of either a setTimeout() or setInterval(). Since that isn't the case here we will use the first example to update state.

Adding an onClick Event

In order to allow the user to interact with the buttons we will need to add an event listener.

React provide synthetic events which correspond to underlying JS events.

Events you might have worked with before are:

  • click => onClick
  • submit => onSubmit
  • change => onChange
  • mouseover => onMouseOver

For now we will add an onClick event listener that calls setCount to update state.

Also, as with plain JavaScript or jQuery we will use an anonymous callback to pause the execution until the click event has occurred.

  return (
    <div>
      <span>Current Count: {count}</span>
      <section>
        <button onClick={() => setCount(count + 1)}>+</button>
        <button onClick={() => setCount(count - 1)}>-</button>
      </section>
    </div>
  )

If we test out the app we should see that the count value will change based on user input.


Instructor Demo

The instructor will demo where React is caught in an infinite loop that was triggered by updating state without using the onClick callback function


Event Handlers

Working with React certainly requires that we write code in very opinionated ways which is why it's considered a Framework. That is a good thing in that we can quickly examine code and expect some consistency in how it is written.

But some React code is written solely based on the adoption of the community at large. One such pattern is the naming convention when creating event handler functions. The convention is to precede their name with word handle.

Let's give that a try by creating the following supporting functions:

  • handleIncrement
  • handleDecrement
const handleIncrement = () => {};

const handleDecrement = () => {};

Now let's move the setState function calls into their corresponding handler functions and update the onClick to reflect this refactor.

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

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

<section>
	<button onClick={handleIncrement}>+</button>
	<button onClick={handleDecrement}>-</button>
</section>;

The other added benefits of using these supporting handler functions are:

  • its a much better way to organize our code
  • we now have a function that can be passed down to a child Component as props

This concept of passing a function down to a child Component is how we lift data from a child to oa parent and a concept knows as lifting state. This concept will be covered in another lecture.


⏰ Activity - 2min

With our event handlers in place let's take a look at React DevTools

If you highlight Counter it should look like the following:

Now try incrementing the value a few times and you should see it update.


State Update Delay

Although the updates to state appear immediate there is one thing to note. After calling setCount the new value assigned to state isn't available until the Component re-renders.

So if we were to console log count immediately after it's been updated we would see it outputs the previous version of state.

const hanndleIncrement = () => {
    setCount(count + 1);
    console.log('handleIncrement - count:', count)
};

Here we can clearly see that count is now 2 but the console logs show that count is one value behind.

There will be instances where some logic needs to be run based on the new value stored in state but we can only do this after a re-render and it requires using the ** useEffect()** hook.

The useEffect hook is a much broader topic and delves into the React Component Lifecycle methods, something we will learn about in upcoming lectures.


⏰ Activity - 10min

Let's try a quick activity that should help you apply some of these concepts.

  • Add a new Reset button to the Component
  • Create a new handler function
  • Inside the handler call setCount to update state 0

👍 - Click on the thumbs up when your done.

Bonus

  • Try refactoring to include the callback function with SetCount.
setCount((prevState) => prevState + 1);

🔍 - Check for Understanding

Take a few minutes to think about the following question:

  • What is the difference between state and props and provide an example of when to use each.

Note: Do not slack your answers until the instructor has given the ok.

When asked slack your answer(s) in a thread created by the instructor


Bonus - Using Conditional Logic and Ternary Operators

Since React is a JavaScript library we can use all of our previous JS expertise when trying to implement additional, non React specific, logic. Let's take a look at a few ways to implement conditional logic:

  • IF/ELSE
  • Switch Statement
  • Ternary Operator

Let's add some basic conditional logic to the handleIncrement/handleDecrement that will reset the count to 0 if it meets a specific threshold.

IF/ELSE

const handleIncrement = () => {
  if(count === 3) {
      handleReset()
    } else {
      setCount(count + 1)
  }
}

Switch Statements

Another form of conditional logic is to use a switch statement which is the conditional logic of choice for the useReducer Hook.

For now let's refactor the code to use a switch statement.

const handleIncrement = () => {
   switch(true){
      case count === 3: 
        handleReset();
        break;
      default:
        setCount(count + 1)
    }
}

Ternary Operator

Most often React developers prefer to write a single if/else conditional as a ternary operator. So let's perform our last refactor.

handleIncrement

const handleIncrement = () => {
  count === 3 ? handleReset() : setCount(count + 1)
}

Final Working Solution: CodeSandbox

State of Transition

We are currently in a state of transition in world of React. Hooks were a game changer and have become the preferred way of writing Components in React.

Keep in mind however that there is way more code out there written in the previous syntax and any research you perform on React will almost certainly show Class based solutions unless you include the keyword Hook in your search query.

Instructor Demo

The instructor will perform a small demo of performing a Google search for updating state in React, with and without the keyword Hook

Resources