Build and Test Sliders with React Hooks

How to build a slider to change the size of a ball and then test it with react-testing-library.

Aditya Agarwal
Bits and Pieces

--

Some wise words by Yoda

I’m sure you have heard of React Hooks by now unless you were living under a rock. They have taken the React world by storm because they offer very simple and powerful primitives for adding state and lifecycle to React functional components. We’ll jump right into building a ball size slider and then look at writing tests in this brave new world of hooked components. But before that, a minor disclaimer.

Use Create React App v2.0 and React v16.8.0 or above for this tutorial. Learn more about hooks: Understanding React Hooks and Code reuse with React Hooks.

Tip- Use Bit to easily share, reuse and sync React components- and even Hooks- across projects and apps. Think of it like using components as reusable Lego bricks, which you and your team can share and use anywhere. Take a look.

What we’ll be building

There is a cute looking ball with some slick shadow below it. The ball can be made larger or smaller with the help of the slider. The end result should look something like this.

A cool GIF demo

If we move the slider all the way to the left, the ball shrinks but to a certain limit only. Similarly, moving the slider to the right makes the ball bigger in size. The ball’s shadow shrinks and grows too when we move the slider because that’s just how science works.

With the requirements nailed down let’s look at all the ingredients needed.

The ingredients

We’ll have a Ball component to show a ball with some shadow below. For the slider, we’ll use the HTML range input. A BallResizer component will embed the Ball component and the slider.

Apart from that, we’ll also make use of CSS custom properties and useState React hook.

Getting the big picture

Moving of the slider causes the value of the range input to change. The new value is communicated to BallResizer component. BallResizer component then updates the size prop of Ball component. The Ball component makes use of CSS custom properties to make the ball look smaller or bigger.

Hmm… the picture is not as big as we all thought but isn’t that a good thing?

Let’s start hacking

We’ll start with a simple React application. It has an App component that will act as a wrapper for our entire hack.

index.js — Entry point to our app

It will import a styles.css that we’ll use to make everything look good. All the CSS will be in this styles.css file only.

styles.css

First, we are resetting the default spacing provided by browsers. Then we make the App fill the whole screen with a grey background. In the end, we center align everything with CSS Flexbox properties.

Create the BallResizer component

The BallResizer component will act as a bridge between the slider and the Ball component. It needs to know the possible minimum and maximum size of the ball and the initial size of the ball.

BallResizer.js

We use the minSize and maxSize props to limit the range of the slider. This way the slider’s value cannot go below minSize or above maxSize.

Also, we assign the initialSize prop to the ballSize variable and use that to tell the Ball component what the size of the ball is and the slider what the initial value is. The BallResizer can be used like this.

index.js (updated)

If we try to run the current code, we’ll get an error because we haven’t yet made the Ball component. Let’s fix that.

Make a Ball component

The Ball component needs a size prop that tells it how large the ball should look. The size is then passed to CSS with the help of newly introduced CSS custom properties.

Ball.js

There is a div with class “ball”. We’ll attach styles to this class to make the div look like a ball.

styles.css (updated)

In the styles, we have initialized the size custom property to 100. We then calculate the height and width of the ball using it.

In CSS, everything is a box by default. But as we learned in Kindergarten, balls are round in shape. To make things round in CSS we set the border-radius to 100%.

At last, we give the ball a cute gradient background which I ripped off from webgradients.com.

Add shadow to the ball

We have a ball and an ugly slider but no shadow under the ball. For the shadow, first, let’s add another div in the Ball component. Since the shadow also grows and shrinks with the ball, we use size custom property here too.

Ball.js (updated)

Next, let’s add some styling for the ball’s shadow.

styles.css (updated)

We use box-shadow to give the shadow, well, a shadowy effect. To spread it horizontally we multiply the size by 0.75 but the width only by 0.3. Adding border-radius to that, we get an ellipse.

Another thing to notice is that we have added a flex-direction declaration in the App class. That is to make the shadow appear below the ball. By default, Flexbox places things one after the other horizontally. But here we don’t want the shadow to appear to the right side of the ball. Setting flex-direction to “column”, places the shadow below the ball.

Add styles to the slider

The ball with its shadow is looking so slick but the slider looks nothing less than an abomination right now. Thanks, web browsers for nothing.

If you noticed, I slipped up a “slider” class in the range input when we were making the BallResizer component and we’ll use that to make the slider look pretty.

styles.css (updated)

All the styles are copied straight from W3Schools. If anything regarding this is unclear, please take a look at there tutorial as it’s beyond the scope of this article. Also, we have positioned the slider to the bottom of the App component. So far, we have arrived at this result.

Result of the code so far

No matter how hard we try we are unable to move the slider or change the ball size. That’s because we gave the slider a fixed value.

We need to convert the slider into a controlled component. That way, we’ll keep the size state in the BallResizer component. The size state will act as the single source of truth and will be used as the value of the slider and the ball size. Moving the slider will update the size state.

But wait if need state in the BallResizer component, do we need to convert it to a class component? If you asked me this some weeks ago, I would have said yes, but that’s not the case anymore.

As mentioned in the beginning, we’ll use the newly added React hooks to add state in the BallResizer component. Specifically, we’ll use the useState hook. The code will look something like this.

BallResizer.js (updated)

We first pass the initialSize prop to the useState method. The useState method then returns an array of two items. First item i.e. ballSize is a state getter and the second item i.e. setBallSize() is a state setter. When the component mounts, the ballSize state getter is assigned the value of initialSize.

Next, we define a handleChange() function that extracts the slider value from the event parameter and uses setBallSize() to set the ballSize state to that slider value. The handleChange function is assigned to onChange so everytime we move the slider and the input value changes, handleChange is called.

Since we have assigned the ballSize state as the size prop of Ball component, the size of the ball (and the shadow) will change when we move the slider.

We have thus completed the implementation. I hope so far it didn’t feel like drawing this horse.

Next, we’ll look at how to test everything we have built so far.

Adding Tests to our hack

We all know how important tests are. They let us work on new features without worrying about breaking the existing application. UI Testing is also becoming popular because modern tools like Jest and react-testing-library make testing delightfully easy.

Deciding what to test

react-testing-library is built around the idea that we should test UI from the perspective of the user. The user is only concerned with the behavior and not with how we implement things.

So we can make out the following —

  1. Testing implementation details have no advantages.
  2. Each and every component doesn’t need to be tested.

On the basis of the above, we can conclude that testing BallResizer component will be sufficient. One lingering question though…

How hooks affect our UI tests

The biggest benefit of this test methodology is that we don’t face any difference whether we use hooks in our component or if use class components or functional components. react-testing-library lets us test all of them the same way.

Note — Enzyme is also popular for testing React components. However, it favors testing implementation details and exploiting shallow rendering. For that reason, React Hooks don’t currently work with Enzyme.

Writing tests with Jest and react-testing-library

We will import the BallResizer component and test it with the help of react-testing-library.

BallResizer.test.js

Before each test, we render the BallResizer component and then get a reference to the DOM nodes of both the slider and the ball.

Then in our first test, we just check if the initial size of the ball i.e. it’s width and height is equal to the initialSize prop passed to the BallResizer component.

As we’ll write more tests, we’ll render BallResizer component multiple times but react-testing-library maintains the DOM across tests. To avoid that and reset the DOM after every test we add afterEach(cleanup) in the beginning.

Now, let’s add tests to check if moving the slider causes any change in ball size.

BallResizer.test.js (updated)

We use the fireEvent method provided by react-testing-library to simulate a movement in the slider. As we move the slider to value 220, we check if the ball’s height and width become 220px or not.

At last, we test that it’s not possible to set the value of the slider to something outside the range of minSize and maxSize.

Conclusion

In this tutorial, we learned how to add state in functional components easily with React Hooks and how we can use react-testing-library to test these hooked components. I encourage everyone to experiment with hooks. The code is available in this codesandbox. Here are some useful hooks with Bit.

If you like my work, don’t be shy about clapping. Follow me on Twitter and Medium or subscribe to my newsletter to get updates on my latest content. Thanks for reading 🙏 Please feel free to comment and ask anything…

--

--