Understanding How Reducers are Used in Redux

A reducer is a function that determines changes to an application’s state. It uses the action it receives to determine this change. We have tools, like Redux, that help manage an application’s state changes in a single store so that they behave consistently.

Why do we mention Redux when talking about reducers? Redux relies heavily on reducer functions that take the previous state and an action in order to execute the next state.

We’re going to focus squarely on reducers is in this post. Our goal is to get comfortable working with the reducer function so that we can see how it is used to update the state of an application — and ultimately understand the role they play in a state manager, like Redux.

What we mean by “state”

State changes are based on a user’s interaction, or even something like a network request. If the application’s state is managed by Redux, the changes happen inside a reducer function — this is the only place where state changes happen. The reducer function makes use of the initial state of the application and something called action, to determine what the new state will look like.

If we were in math class, we could say:

initial state + action = new state

In terms of an actual reducer function, that looks like this:

const contactReducer = (state = initialState, action) => {
  // Do something
}

Where do we get that initial state and action? Those are things we define.

The state parameter

The state parameter that gets passed to the reducer function has to be the current state of the application. In this case, we’re calling that our initialState because it will be the first (and current) state and nothing will precede it.

contactReducer(initialState, action)

Let’s say the initial state of our app is an empty list of contacts and our action is adding a new contact to the list.

const initialState = {
  contacts: []
}

That creates our initialState, which is equal to the state parameter we need for the reducer function.

The action parameter

An action is an object that contains two keys and their values. The state update that happens in the reducer is always dependent on the value of action.type. In this scenario, we are demonstrating what happens when the user tries to create a new contact. So, let’s define the action.type as NEW_CONTACT.

const action = {
  type: 'NEW_CONTACT',
  name: 'John Doe',
  location: 'Lagos Nigeria',
  email: 'johndoe@example.com'
}

There is typically a payload value that contains what the user is sending and would be used to update the state of the application. It is important to note that action.type is required, but action.payload is optional. Making use of payload brings a level of structure to how the action object looks like.

Updating state

The state is meant to be immutable, meaning it shouldn’t be changed directly. To create an updated state, we can make use of Object.assign or opt for the spread operator.

Object.assign

const contactReducer = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return Object.assign({}, state, {
      contacts: [
        ...state.contacts,
        action.payload
      ]
    })
    default:
      return state
  }
}

In the above example, we made use of the Object.assign() to make sure that we do not change the state value directly. Instead, it allows us to return a new object which is filled with the state that is passed to it and the payload sent by the user.

To make use of Object.assign(), it is important that the first argument is an empty object. Passing the state as the first argument will cause it to be mutated, which is what we’re trying to avoid in order to keep things consistent.

The spread operator

The alternative to object.assign() is to make use of the spread operator, like so:

const contactReducer = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    default:
      return state
  }
}

This ensures that the incoming state stays intact as we append the new item to the bottom.

Working with a switch statement

Earlier, we noted that the update that happens depends on the value of action.type. The switch statement conditionally determines the kind of update we're dealing with, based on the value of the action.type.

That means that a typical reducer will look like this:

const addContact = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    case 'UPDATE_CONTACT':
      return {
        // Handle contact update
      }
    case 'DELETE_CONTACT':
      return {
        // Handle contact delete
      }
    case 'EMPTY_CONTACT_LIST':
      return {
        // Handle contact list
      }
    default:
      return state
  }
}

It’s important that we return state our default for when the value of action.type specified in the action object does not match what we have in the reducer — say, if for some unknown reason, the action looks like this:

const action = {
  type: 'UPDATE_USER_AGE',
  payload: {
    age: 19
  }
}

Since we don’t have this kind of action type, we’ll want to return what we have in the state (the current state of the application) instead. All that means is we’re unsure of what the user is trying to achieve at the moment.

Putting everything together

Here’s a simple example of how I implemented the reducer function in React.

See the Pen
reducer example
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

You can see that I didn’t make use of Redux, but this is very much the same way Redux uses reducers to store and update state changes. The primary state update happens in the reducer function, and the value it returns sets the updated state of the application.

Want to give it a try? You can extend the reducer function to allow the user to update the age of a contact. I’d like to see what you come up with in the comment section!

Understanding the role that reducers play in Redux should give you a better understanding of what happens underneath the hood. If you are interested in reading more about using reducers in Redux, it’s worth checking out the official documentation.

The post Understanding How Reducers are Used in Redux appeared first on CSS-Tricks.

Using Immer for React State Management

We make use of state to keep track of application data. States change as users interact with an application. When this happens, we need to update the state that is displayed to the user, and we do this using React’s setState.

Since states are not meant to be updated directly (because React’s state has to be immutable), things can get really complicated as states become more complex. They become difficult to understand and follow.

This is where Immer comes in and that’s what we’re going to look at in this post. Using Immer, states can be simplified and much easier to follow. Immer makes use of something called "draft" which you can think of as the copy of your state, but not the state itself. It’s as though Immer hit CMD+C on the state and then cmd+V’d it somewhere else where it can be safely viewed without disturbing the original copy. Any updates you need to make happen on the draft, and the parts of the current state that change on the draft is updated.

Let’s say your application’s state looks like this;

this.state = {
  name: 'Kunle',
  age: 30,
  city: 'Lagos,
  country: 'Nigeria'
}

This user happens to be celebrating his 31st birthday and which means we need to update the age value. With Immer running behind the scenes, a replica of this state will be made.

Now imagine the replica is made and handed over to a messenger, who gives the newly copied version of the state to Kunle. It means there are now two copies available — the current state and the draft copy that was handed over. Kunle then changes the age on the draft to 31. The messenger then returns to the application with the draft, compares both versions, and only updates the age since that’s the only part of the draft that changed.

It does not break the idea of an immutable state, as the current state does not get updated directly. Immer basically makes it convenient to work with immutable state.

Let’s look at an example of this at work

Say you want to build a traffic light for your community, you can give it a shot using Immer for your state updates.

See the Pen
Traffic Light Example with Reactjs
by CarterTsai (@CarterTsai)
on CodePen.

Using Immer, the component will look like this:

const {produce} = immer

class App extends React.Component {
  state = {
    red: 'red', 
    yellow: 'black', 
    green: 'black',
    next: "yellow"
  }

  componentDidMount() {
    this.interval = setInterval(() => this.changeHandle(), 3000);
  }
  
  componentWillUnmount()  {
    clearInterval(this.interval);
  }

  handleRedLight = () => {
    this.setState(
      produce(draft => {
        draft.red = 'red';
        draft.yellow = 'black';
        draft.green = 'black';
        draft.next = 'yellow'
      })
    )
  }
  
  handleYellowLight = () => {
    this.setState(
      produce(draft => {
        draft.red = 'black';
        draft.yellow = 'yellow';
        draft.green = 'black';
        draft.next = 'green'
      })
    )
  }
  
  handleGreenLight = () => {
    this.setState(
      produce(draft => {
        draft.red = 'black';
        draft.yellow = 'black';
        draft.green = 'green';
        draft.next = 'red'
      })
    )
  }

  changeHandle = () => {
    if (this.state.next === 'yellow') {
      this.handleYellowLight()
    } else if (this.state.next === 'green') {
      this.handleGreenLight()
    } else {
      this.handleRedLight()
    }
    
  }

  render() {
    return (
      <div className="box">
        <div className="circle" style={{backgroundColor: this.state.red}}></div>
        <div className="circle" style={{backgroundColor: this.state.yellow}}></div>
        <div className="circle" style={{backgroundColor: this.state.green}}></div>
      </div>
  );
}
};

produce is the default function we get from Immer. Here, we pass it as a value to the setState() method. The produce function takes a function which accepts draft as an argument. It is inside this function that we can then set the draft copy with which we want to update our state.

If that looks complicated, there is another way to write this. First, we create a function.

const handleLight = (state) => {
  return produce(state, (draft) => {
    draft.red = 'black';
    draft.yellow = 'black';
    draft.green = 'green';
    draft.next = 'red'
  });
}

We are passing the current state of the application, and the function which accepts draft as arguments to the produce function. To make use of this inside our component, we do this;

handleGreenLight = () => {
  const nextState = handleLight(this.state)
  this.setState(nextState)
}

Another example: A shopping list

If you have been working with React for a while now, then you’re not a stranger to the spread operator. With Immer, you need not make use of the spread operator, especially when working with an array in your state.

Let’s explore that a little further by creating a shopping list application.

See the Pen
immer 2 - shopping list
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Here’s the component we’re working with:

class App extends React.Component {
  constructor(props) {
      super(props)
      
      this.state = {
        item: "",
        price: 0,
        list: [
          { id: 1, name: "Cereals", price: 12 },
          { id: 2, name: "Rice", price: 10 }
        ]
      }
    }

    handleInputChange = e => {
      this.setState(
      produce(draft => {
        draft[event.target.name] = event.target.value
      }))
    }

    handleSubmit = (e) => {
      e.preventDefault()
      const newItem = {
        id: uuid.v4(),
        name: this.state.name,
        price: this.state.price
      }
      this.setState(
        produce(draft => {
          draft.list = draft.list.concat(newItem)
        })
      )
    };

  render() {
    return (
      <React.Fragment>
        <section className="section">
          <div className="box">
            <form onSubmit={this.handleSubmit}>
              <h2>Create your shopping list</h2>
              <div>
                <input
                  type="text"
                  placeholder="Item's Name"
                  onChange={this.handleInputChange}
                  name="name"
                  className="input"
                  />
              </div>
              <div>
                <input
                  type="number"
                  placeholder="Item's Price"
                  onChange={this.handleInputChange}
                  name="price"
                  className="input"
                  />
              </div>
              <button className="button is-grey">Submit</button>
            </form>
          </div>
          
          <div className="box">
            {
              this.state.list.length ? (
                this.state.list.map(item => (
                  <ul>
                    <li key={item.id}>
                      <p>{item.name}</p>
                      <p>${item.price}</p>
                    </li>
                    <hr />
                  </ul>
                ))
              ) : <p>Your list is empty</p>
            }
          </div>
        </section>
      </React.Fragment>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

As items are added to the list, we need to update the state of the list to reflect those new items. To update the state of list using setState(), we’ll have to do this:

handleSubmit = (e) => {
  e.preventDefault()
  const newItem = {
    id: uuid.v4(),
    name: this.state.name,
    price: this.state.price
  }
  this.setState({ list: [...this.state.list, newItem] })
};

If you have to update multiple states in the application, you’ll have to do a ton of spreading to create a new state using the old state and the additional value. Which can look more complex as the number of changes increases. With Immer, it becomes very easy to do that, as we did in the example above.

What if we want to add a function that gets called as a callback after the state update?In this case, let’s say we are keeping a tally of the number of items in the list and the total price of all the items.

See the Pen
immer 3 - shopping list
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Say we want to calculate the amount that will be spent based on the price of items in the list, we can have the handleSubmit function look like this:

handleSubmit = (e) => {
  e.preventDefault()
  const newItem = {
    id: uuid.v4(),
    name: this.state.name,
    price: this.state.price
  }
  
  this.setState(
    produce(draft => {
      draft.list = draft.list.concat(newItem)
    }), () => {
      this.calculateAmount(this.state.list)
    }
  )
};

First, we create an object using the data entered by the user, which we then assign to newItem. To update our application’s state, we make use of .concat() which will return a new array that's comprised of the previous items and the new item. This updated copy is now set as the value of draft.list, which can then be used by Immer to update the state of the application.

The callback function gets called after the state update. It’s important to note that it makes use of the updated state.

The function we want to call will look like this:

calculateAmount = (list) => {
  let total = 0;
    for (let i = 0; i < list.length; i++) {
      total += parseInt(list[i].price, 10)
    }
  this.setState(
    produce(draft => {
      draft.totalAmount = total
    })
  )
}

Let’s look at Immer hooks

use-immer is a hook that allows you to manage state in your React application. Let’s see this in action using a classic counter example.

import React from "react";
import {useImmer} from "use-immer";

const Counter = () => {
  const [count, updateCounter] = useImmer({
    value: 0
  });

  function increment() {
    updateCounter(draft => {
      draft.value = draft.value +1;
    });
  }

  return (
    <div>
      <h1>
        Counter {count.value}
      </h1>
      <br />
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default Counter;

useImmer is similar to useState. The function returns the state and an updater function. When the component loads at first, the value of the state (which is count in this example), is the same as the value passed to useImmer. Using the updater function which is returned, we can then create an increment function to increase the value of the count.

There is also a useReducer-like hook for Immer.

import React, { useRef } from "react";
import {useImmerReducer } from "use-immer";
import uuidv4 from "uuid/v4"
const initialState = [];
const reducer = (draft, action) => {
  switch (action.type) {
    case "ADD_ITEM":
      draft.push(action.item);
      return;
    case "CLEAR_LIST":
      return initialState;
    default:
      return draft;
  }
}
const Todo = () => {
  const inputEl = useRef(null);
  const [state, dispatch] = useImmerReducer(reducer, initialState);
  
  const handleSubmit = (e) => {
    e.preventDefault()
    const newItem = {
      id: uuidv4(),
      text: inputEl.current.value
    };
    dispatch({ type: "ADD_ITEM", item: newItem });
    inputEl.current.value = "";
    inputEl.current.focus();
  }
  
  const handleClear = () => {
    dispatch({ type: 'CLEAR_LIST' })
  }
  
  return (
    <div className='App'>
      <header className='App-header'>
        <ul>
          {state.map(todo => {
            return <li key={todo.id}>{todo.text}</li>;
          })}
        </ul>
        <form onSubmit={handleSubmit}>
          <input type='text' ref={inputEl} />
          <button
            type='submit'
          >
            Add Todo
          </button>
        </form>
        <button
          onClick={handleClear}
        >
          Clear Todos
        </button>
      </header>
    </div>
  );
}
export default Todo;

useImmerReducer takes in a reducer function and the initial state, and it returns both state and the dispatch function. We can then loop through the state to display the items we have. We dispatch an action when submitting a todo item and clearing the list of them. The dispatched action has a type which we use in determining what to do in the reducer function.
In the reducer function, we make use of draft like we did before, instead of state. With that, we have a convenient way of manipulating the state of our application.

You can find the code used in the above example on GitHub.

That’s a look at Immer!

Going forward, you can begin to make use of Immer in your next project, or even slowly begin to use it in the current project you’re working on. It has proven to aid in making state management convenient.

The post Using Immer for React State Management appeared first on CSS-Tricks.

Creating Animations Using React Spring

Have you ever needed animation in your React application? Traditionally, implementing animation has not an easy feat to accomplish. But now, thanks to Paul Henschel, we there’s a new React tool just for that. react-spring inherits from animated and react-motion for interpolations, optimized performance, and a clean API.

In this tutorial, we will be looking at two of the five hooks included in react-spring, specifically useSpring and useTrail. The examples we’ll implement make use of both APIs.

If you want to follow along, install react-spring to kick things off:

## yarn
yarn add react-spring

## npm
npm install react-spring --save

Spring

The Spring prop can be used for moving data from one state to another. We are provided with a from and to prop to help us define the animation’s starting and ending states. The from prop determines the initial state of the data during render, while we use to in stating where it should to be after the animation completes.

In the first example, we will make use of the render prop version of creating spring animation.

See the Pen
react spring 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

On initial render, we want to hide a box, and slide it down to the center of the page when a button is clicked. It’s possible to do this without making use of react-spring, of course, but we want to animate the entrance of the box in to view and only when the button is clicked.

class App extends React.Component {
  state = {
    content: false
  }

  displayContent = (e) => {
    e.preventDefault()
    this.setState({ content: !this.state.content })
  }

  render() {
    return (
      <div className="container">
          // The button that toggles the animation
          <div className="button-container">
            <button
              onClick={this.displayContent}
              className="button">
                Toggle Content
            </button>
          </div>
          {
            !this.state.content ?
              (
                // Content in the main container
                <div>
                  No Content
                </div>
              )
            : (
              // We call Spring and define the from and to props
              <Spring
                from={{
                  // Start invisible and offscreen
                  opacity: 0, marginTop: -1000,
                }}
                to={{
                  // End fully visible and in the middle of the screen
                  opacity: 1, marginTop: 0,
                }}
              >
                { props => (
                  // The actual box that slides down
                  <div  className="box" style={ props }>
                    <h1>
                      This content slid down. Thanks to React Spring
                    </h1>
                  </div>
              )}
            </Spring>
            )
        }
      </div>
    )
  }
}

Most of the code is basic React that you might already be used to seeing. We make use of react-spring in the section where we want to conditionally render the content after the value of content has been changed to true. In this example, we want the content to slide in from the top to the center of the page, so we make use of marginTop and set it to a value of -1000 to position it offscreen, then define an opacity of 0 as our values for the from prop. This means the box will initially come from the top of the page and be invisible.

Clicking the button after the component renders updates the state of the component and causes the content to slide down from the top of the page.

We can also implement the above example using the hooks API. For this, we’ll be making use of the useSpring and animated hooks, alongside React’s built-in hooks.

const App = () => {
  const [contentStatus, displayContent] = React.useState(false);
  // Here's our useSpring Hook to define start and end states
  const contentProps = useSpring({
    opacity: contentStatus ? 1 : 0,
    marginTop: contentStatus ? 0 : -1000
  })
  return (
    <div className="container">
      <div className="button-container">
        <button
          onClick={() => displayContent(a => !a)}
          className="button">Toggle Content</button>
      </div>
        {
          !contentStatus ?
            (
              <div>
                No Content
              </div>
            )
          : (
            // Here's where the animated hook comes into play
            <animated.div className="box" style={ contentProps }>
              <h1>
                This content slid down. Thanks to React Spring
              </h1>
            </animated.div>
          )
        }
    </div>
  )
}

First, we set up the state for the component. Then we make use of useSpring to set up the animations we need. When contentStatus is true, we want the values of marginTop and opacity to be 0 and 1, respectively. Else, they should be -1000 and 0. These values are assigned to contentProps which we then pass as props to animated.div.

When the value of contentStatus changes, as a result of clicking the button, the values of opacity and marginTop changes alongside. This cause the content to slide down.

See the Pen
react spring 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Trail

The Trail prop animates a list of items. The animation is applied to the first item, then the siblings follow suit. To see how that works out, we’ll build a component that makes a GET request to fetch a list of users, then we will animate how they render. Like we did with Spring, we’ll see how to do this using both the render props and hooks API separately.

First, the render props.

class App extends React.Component {
  state = {
    isLoading: true,
    users: [],
    error: null
  };
  
  // We're using the Fetch <abbr>API</abbr> to grab user data
  // https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/
  fetchUsers() {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        // More on setState: https://css-tricks.com/understanding-react-setstate/
        this.setState({
          users: data,
          isLoading: false,
        })
      )
      .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    this.fetchUsers();
  }

  render() {
    const { isLoading, users, error } = this.state;
    return (
      <div>
        <h1>Random User</h1>
        {error ? <p>{error.message}</p> : null}
        {!isLoading ? (
          // Let's define the items, keys and states using Trail
          <Trail
            items={users}
            keys={user => user.id}
            from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
            to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
          >
          {user => props => (
          <div style={props} className="box">
            {user.username}
          </div>
        )}
      </Trail>
        ) : (
          <h3>Loading...</h3>
        )}
      </div>
    );
  }
}

When the component mounts, we make a request to fetch some random users from a third-party API service. Then, we update this.state.users using the data the API returns. We could list the users without animation, and that will look like this:

users.map(user => {
  const { username, name, email } = user;
  return (
    <div key={username}>
      <p>{username}</p>
    </div>
  );
})

But since we want to animate the list, we have to pass the items as props to the Trail component:

<Trail
  items={users}
  keys={user => user.id}
  from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}
  to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}
>
  {user => props => (
    <div style={props} className="box">
      {user.username}
    </div>
  )}
</Trail>

We set the keys to the ID of each user. You can see we are also making use of the from and to props to determine where the animation should start and end.

Now our list of users slides in with a subtle animation:

See the Pen
React Spring - Trail 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

The hooks API gives us access to useTrail hook. Since we are not making use of a class component, we can make use of the useEffect hook (which is similar to componentDidMount and componentDidUpdate lifecycle methods) to fetch the users when the component mounts.

const App = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        setUsers(data)
      )
  }, [])
  
  const trail = useTrail(users.length, {
    from: { marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' },
    to: { marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }
  })

  return (
    <React.Fragment>
      <h1>Random User</h1>
      {trail.map((props, index) => {
        return (
          <animated.div
            key={users[index]}
            style={props}
            className="box"
          >
            {users[index].username}
          </animated.div>
        )
      })}
    </React.Fragment>
  );
}

We have the initial state of users set to an empty array. Using useEffect, we fetch the users from the API and set a new state using the setUsers method we created with help from the useState hook.

Using the useTrail hook, we create the animated style passing it values for from and to, and we also pass in the length of the items we want to animate. In the part where we want to render the list of users, we return the array containing the animated props.

See the Pen
React Spring -Trail 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Go, spring into action!

Now you have a new and relatively easy way to work with animations in React. Try animating different aspects of your application where you see the need. Of course, be mindful of user preferences when it comes to animations because they can be detrimental to accessibility.

While you’re at it, ensure you check out the official website of react-spring because there are tons of demo to get your creative juices flowing with animation ideas.

The post Creating Animations Using React Spring appeared first on CSS-Tricks.

The Circle of a React Lifecycle

A React component goes through different phases as it lives in an application, though it might not be evident that anything is happening behind the scenes.

Those phases are:

  • mounting
  • updating
  • unmounting
  • error handling

There are methods in each of these phases that make it possible to perform specific actions on the component during that phase. For example, when fetching data from a network, you’d want to call the function that handles the API call in the componentDidMount() method, which is available during the mounting phase.

Knowing the different lifecycle methods is important in the development of React applications, because it allows us to trigger actions exactly when they’re needed without getting tangled up with others. We’re going to look at each lifecycle in this post, including the methods that are available to them and the types of scenarios we’d use them.

The Mounting Phase

Think of mounting as the initial phase of a component’s lifecycle. Before mounting occurs, a component has yet to exist — it’s merely a twinkle in the eyes of the DOM until mounting takes place and hooks the component up as part of the document.

There are plenty of methods we can leverage once a component is mounted: constructor() , render(), componentDidMount() and static getDerivedStateFromProps(). Each one is handy in it’s own right, so let’s look at them in that order.

constructor()

The constructor() method is expected when state is set directly on a component in order to bind methods together. Here is how it looks:

// Once the input component is mounting...
constructor(props) {
  // ...set some props on it...
  super(props);
  // ...which, in this case is a blank username...
  this.state = {
    username: ''
  };
  // ...and then bind with a method that handles a change to the input
  this.handleInputChange = this.handleInputChange.bind(this);
}

It is important to know that the constructor is the first method that gets called as the component is created. The component hasn’t rendered yet (that’s coming) but the DOM is aware of it and we can hook into it before it renders. As a result, this isn’t the place where we’d call setState() or introduce any side effects because, well, the component is still in the phase of being constructed!

I wrote up a tutorial on refs a little while back, and once thing I noted is that it’s possible to set up ref in the constructor when making use of React.createRef(). That’s legit because refs is used to change values without props or having to re-render the component with updates values:

constructor(props) {
  super(props);
  this.state = {
    username: ''
  };
  this.inputText = React.createRef();
}

render()

The render() method is where the markup for the component comes into view on the front end. Users can see it and access it at this point. If you’ve ever created a React component, then you’re already familiar with it — even if you didn’t realize it — because it’s required to spit out the markup.

class App extends React.Component {
  // When mounting is in progress, please render the following!
  render() {
    return (
      <div>
        <p>Hello World!</p>
      </div>
    )
  }
}

But that’s not all that render() is good for! It can also be used to render an array of components:

class App extends React.Component {
  render () {
    return []
      <h2>JavaScript Tools</h2>,
      <Frontend />,
      <Backend />
    ]
  }
}

...and even fragments of a component:

class App extends React.Component {
  render() {
    return (
      <React.Fragment>
        <p>Hello World!</p>
      </React.Fragment>
    )
  }
}

We can also use it to render components outside of the DOM hierarchy (a la React Portal):

// We're creating a portal that allows the component to travel around the DOM
class Portal extends React.Component {
  // First, we're creating a div element
  constructor() {
    super();
    this.el = document.createElement("div");
  }
  
  // Once it mounts, let's append the component's children
  componentDidMount = () => {
    portalRoot.appendChild(this.el);
  };
  
  // If the component is removed from the DOM, then we'll remove the children, too
  componentWillUnmount = () => {
    portalRoot.removeChild(this.el);
  };
  
  // Ah, now we can render the component and its children where we want
  render() {
    const { children } = this.props;
    return ReactDOM.createPortal(children, this.el);
  }
}

And, of course, render() can — ahem — render numbers and strings...

class App extends React.Component {
  render () {
    return "Hello World!"
  }
}

...as well as null or Boolean values:

class App extends React.Component {
  render () {
    return null
  }
}

componentDidMount()

Does the componentDidMount() name give away what it means? This method gets called after the component is mounted (i.e. hooked to the DOM). In another tutorial I wrote up on fetching data in React, this is where you want to make a request to obtain data from an API.

We can have your fetch method:

fetchUsers() {
  fetch(`https://jsonplaceholder.typicode.com/users`)
    .then(response => response.json())
    .then(data =>
      this.setState({
        users: data,
        isLoading: false,
      })
    )
  .catch(error => this.setState({ error, isLoading: false }));
}

Then call the method in componentDidMount() hook:

componentDidMount() {
  this.fetchUsers();
}

We can also add event listeners:

componentDidMount() {
  el.addEventListener()
}

Neat, right?

static getDerivedStateFromProps()

It’s kind of a long-winded name, but static getDerivedStateFromProps() isn’t as complicated as it sounds. It’s called before the render() method during the mounting phase, and before the update phase. It returns either an object to update the state of a component, or null when there’s nothing to update.

To understand how it works, let’s implement a counter component which will have a certain value for its counter state. This state will only update when the value of maxCount is higher. maxCount will be passed from the parent component.

Here’s the parent component:

class App extends React.Component {
  constructor(props) {
    super(props)
    
    this.textInput = React.createRef();
    this.state = {
      value: 0
    }
  }
  
  handleIncrement = e => {
    e.preventDefault();
    this.setState({ value: this.state.value + 1 })
  };

  handleDecrement = e => {
    e.preventDefault();
    this.setState({ value: this.state.value - 1 })
  };

  render() {
    return (
      <React.Fragment>
        <section className="section">
          <p>Max count: { this.state.value }</p>
          <button
            onClick={this.handleIncrement}
            class="button is-grey">+</button>
          <button
            onClick={this.handleDecrement}
            class="button is-dark">-</button>          
        </section>
        <section className="section">
          <Counter maxCount={this.state.value} />
        </section>
      </React.Fragment>
    )
  }
}

We have a button used to increase the value of maxCount, which we pass to the Counter component.

class Counter extends React.Component {
  state={
    counter: 5
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.counter < nextProps.maxCount) {
      return {
        counter: nextProps.maxCount
      };
    } 
    return null;
  }

  render() {
    return (
      <div className="box">
        <p>Count: {this.state.counter}</p>
      </div>
    )
  }
}

In the Counter component, we check to see if counter is less than maxCount. If it is, we set counter to the value of maxCount. Otherwise, we do nothing.

You can play around with the following Pen below to see how that works on the front end:

See the Pen
getDerivedStateFromProps
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.


The Updating Phase

The updating phase occurs when a component when a component’s props or state changes. Like mounting, updating has its own set of available methods, which we’ll look at next. That said, it’s worth noting that both render() and getDerivedStateFromProps() also get triggered in this phase.

shouldComponentUpdate()

When the state or props of a component changes, we can make use of the shouldComponentUpdate() method to control whether the component should update or not. This method is called before rendering occurs and when state and props are being received. The default behavior is true. To re-render every time the state or props change, we’d do something like this:

shouldComponentUpdate(nextProps, nextState) {
  return this.state.value !== nextState.value;
}

When false is returned, the component does not update and, instead, the render() method is called to display the component.

getSnapshotBeforeUpdate()

One thing we can do is capture the state of a component at a moment in time, and that’s what getSnapshotBeforeUpdate() is designed to do. It’s called after render() but before any new changes are committed to the DOM. The returned value gets passed as a third parameter to componentDidUpdate().

It takes the previous state and props as parameters:

getSnapshotBeforeUpdate(prevProps, prevState) {
  // ...
}

Use cases for this method are kinda few and far between, at least in my experience. It is one of those lifecycle methods you may not find yourself reaching for very often.

componentDidUpdate()

Add componentDidUpdate() to the list of methods where the name sort of says it all. If the component updates, then we can hook into it at that time using this method and pass it previous props and state of the component.

componentDidUpdate(prevProps, prevState) {
  if (prevState.counter !== this.state.counter) {
    // ...
  }
}

If you ever make use of getSnapshotBeforeUpdate(), you can also pass the returned value as a parameter to componentDidUpdate():

componentDidUpdate(prevProps, prevState, snapshot) {
  if (prevState.counter !== this.state.counter) {
    // ....
  }
}

The Unmounting Phase

We’re pretty much looking at the inverse of the mounting phase here. As you might expect, unmounting occurs when a component is wiped out of the DOM and no longer available.

We only have one method in here: componentWillUnmount()

This gets called before a component is unmounted and destroyed. This is where we would want to carry out any necessary clean up after the component takes a hike, like removing event listeners that may have been added in componentDidMount(), or clearing subscriptions.

// Remove event listener 
componentWillUnmount() {
  el.removeEventListener()
}

The Error Handling Phase

Things can go wrong in a component and that can leave us with errors. We’ve had error boundary around for a while to help with this. This error boundary component makes use of some methods to help us handle the errors we could encounter.

getDerivedStateFromError()

We use getDerivedStateFromError() to catch any errors thrown from a descendant component, which we then use to update the state of the component.

class ErrorBoundary extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }

    return this.props.children;
  }
}

In this example, the ErrorBoundary component will display “Oops, something went wrong” when an error is thrown from a child component. We have a lot more info on this method in a wrap up on goodies that were released in React 16.6.0.

componentDidCatch()

While getDerivedStateFromError() is suited for updating the state of the component in cases where where side effects, like error logging, take place, we ought to make use of componentDidCatch() because it is called during the commit phase, when the DOM has been updated.

componentDidCatch(error, info) {
  // Log error to service
}

Both getDerivedStateFromError() and componentDidCatch() can be used in the ErrorBoundary component:

class ErrorBoundary extends React.Component {
  
constructor(props) {
  super(props);
  this.state = {
    hasError: false
  };
}

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Log error to service
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }

    return this.props.children;
  }
}

And that’s the lifecycle of a React component!

There’s something neat about knowing how a React component interacts with the DOM. It’s easy to think some “magic” happens and then something appears on a page. But the lifecycle of a React component shows that there’s order to the madness and it’s designed to give us a great deal of control to make things happen from the time the component hits the DOM to the time it goes away.

We covered a lot of ground in a relatively short amount of space, but hopefully this gives you a good idea of not only how React handles components, but what sort of capabilities we have at various stages of that handling. Feel free to leave any questions at all if anything we covered here is unclear and I’d be happy to help as best I can!

The post The Circle of a React Lifecycle appeared first on CSS-Tricks.