Practical React & Redux · Part 2

This is the second part of the React workshop series created by our team. Let's learn about how to create stateful components using React hooks.

This is the second part of the React workshop series created by our team. Let's learn about how to create stateful components using React hooks.

It's been a while but finally awe have released the second part of our React workshop. Remember that you can download the code from the first part in here. If you want to follow along just checkout the part-1 tag and update the code as you read.

Introduction

In the first part we created our first components using React, but we ended the workshop in a bit of a cliffhanger and encountered a bug 🐛. Today we are going to continue our application and start writing some stateful components.

Stateful components with React hooks

We have created a bunch of stateless components or usually also called "dumb components". Starting now we are going to learn how to add some state to them. First we need to learn what is a React hook!

useWhat? First overview of hooks

A hook is just a function, period. We can write our hooks and the convention is that they start with the prefix use. The hooks can only be executed inside components (or other hooks) so usually the hook just return something that can be used by the component.

A common use of hooks is to reuse some business logic. First let's create a Cat 😸 component (you need to create the corresponding folder structure as we did before):

// src/cats/components/cat/Cat.js
import React from 'react'
import PropTypes from 'prop-types'

const Cat = ({ name, score }) => {
  const onClick = () => {
    score += 1
    console.log(`This is your new score: ${score}`)
  }

  return (
    <div>
      <p>{name}</p>
      <p>Score: {score}/10</p>
      <button onClick={onClick}>Increase score</button>
    </div>
  )
}

Cat.propTypes = {
  name: PropTypes.string.isRequired,
  score: PropTypes.number.isRequired,
}

export default Cat

Then, use the Cat component in our application to add a new cat to the view (😸 are also awesome so give them a good score!):

// src/App.js
import React from 'react'
import Dog from './dogs/components/dog/Dog'
import Cat from './cats/components/cat/Cat'

const App = () => {
  return (
    <>
      <Dog name="Boira" score={13} />
      <Dog name="Black" score={13} />
      <Cat name="Uhura" score={13} />
    </>
  )
}

export default App

Nothing really fancy here! Now we have two components that are mostly identical. Let's fix this!. Let's create a pets module with a hooks folder, and we are going to create our first hook. We are going to copy the onClick function from the Dog component and use it inside our hook.

// src/pets/hooks/useScore.js
const useScore = (score) => {
  return {
    increaseScore: () => {
      score += 1
      console.log(`This is your new score: ${score}`)
    },
  }
}

export default useScore

Notice that the hook is just a function that receives some value, score in this case, and returns a new object. I added the increaseScore function to the object so now we can use it inside our components.

Let's use it in the Dog component:

// src/dogs/components/dog/Dog.js
import React from 'react'
import PropTypes from 'prop-types'

import useScore from '../../../pets/hooks/useScore'

const Dog = ({ name, score }) => {
  const { increaseScore } = useScore(score)

  return (
    <div>
      <p>{name}</p>
      <p>Score: {score}/10</p>
      <button onClick={increaseScore}>Increase score</button>
    </div>
  )
}

Dog.propTypes = {
  name: PropTypes.string.isRequired,
  score: PropTypes.number.isRequired,
}

export default Dog

Do the same for the Cat component, and we are done here! We have moved some common logic to a hook so now we can focus on fixing our problem. We need to make our view aware of that change in the score or, in other words, make the component stateful!

Stateful components using useState

React already includes some hooks. We are going to use useState to add some state to our components. The difference between a prop and the state is that we can change the state to re-render our component when something changes!

Remember that we can use hooks inside other hooks so let's use the useState hook inside our custom hook useScore like this:

// src/pets/hooks/useScore.js
import { useState } from 'react'

const useScore = (baseScore) => {
  const [score, setScore] = useState(baseScore)

  return {
    score,
    increaseScore: () => {
      setScore(score + 1)
      console.log(`This is your new score: ${score}`)
    },
  }
}

export default useScore

Hold up! There is a lot to digest in the previous snippet so let's review it step by step. First, we rename the hook argument to baseScore and passed it to the useState method, this is the initial value of our state. The useState function returns an array where the first element is our state and the second element is a function to replace our state. I used the word "replace" intentionally because we need to provide a new object always, otherwise it will not be updated (embrace the immutability!).

Then I added the score to the object that our custom hook is returning, so we have access to it in our component. Finally, I used the setScore function to set a new state when the increaseScore function is called. Neat, right?

Let's see how to use it in our components. I am gonna change the Dog component, and I am gonna leave the Cat component for yourselves as an excercise:

// src/dogs/components/dog/Dog.js
import React from "react";
import PropTypes from "prop-types";

import useScore from "../../../pets/hooks/useScore";

const Dog = ({ name, initialScore }) => {
  const { score, increaseScore } = useScore(initialScore);

  return (
    <div>
      <p>{name}</p>
      <p>Score: {score}/10</p>
      <button onClick={increaseScore}>Increase score</button>
    </div>
  );
};

Dog.propTypes = {
  name: PropTypes.string.isRequired,
  **initialScore: PropTypes.number.isRequired,**
};

export default Dog;

I also changed the Dog's score prop to initialScoreso we need to update our application as well:

// src/App.js
import React from 'react'
import Dog from './dogs/components/dog/Dog'
import Cat from './cats/components/cat/Cat'

const App = () => {
  return (
    <>
      <Dog name="Boira" initialScore={13} />
      <Dog name="Black" initialScore={13} />
      <Cat name="Uhura" initialScore={13} />
    </>
  )
}

export default App

After making all the changes you can refresh your browser and test your application. The view is also updated when we press the button! 🎉

React includes a small list of hooks: https://reactjs.org/docs/hooks-reference.html but creating your own is straightforward.

Conclusion

In this article we learned about how to add state to our a React application and written our first React hooks. Our application is basic but for a bigger application is better to rely on a state management library and that's the topic for our next article!.

You can find the code examples in this repository. I also tagged the progress for part 2 in case you want to check the repository at this specific moment.

Cover photo by Henar Langa

View all posts tagged as