React Native with Hooks and Styled Components

Learn by building a stress ball clicker game

Brooklin Myers
11 min readFeb 24, 2020

So you want to learn about styled-components?

Me too — in fact, I’ve been learning about them and using them in projects for a while now and I’ve dedicated the last month to digging into them in the hopes of having my team adopt them. Whether you are totally new or already experienced I hope this tutorial helps you!

I’m going to start by talking you through the essential syntax of styled-components then we’ll work our way into more advanced features like using props, variables, and extending styles.

I love learning by building — so we’re going to build a quick stress ball game as we cover the major concepts. It looks like this:

See the Github Repo

Requirements:

  • A Code Editor (I prefer visual studio code)
  • (optional) A syntax highlighter. try this one for VSC
  • NPM/Node
  • Expo
  • Familiarity with React Native and Hooks is recommended— but I will try to explain everything I cover.

Create A React Native Project

Let's start by creating a new react-native project.

Install Expo

Expo lets you create a react-native project extremely quickly.
go to your terminal and run:

npm install -g expo-cli

If this doesn’t work — make sure that you have npm working on your computer by typing npm -v into your terminal. You should see the current version of your npm installation. Go here to install node and npm if you don’t have it installed.

Create a new Project

Using expo-cli you can quickly create a react-native app.
Go to your projects folder in your terminal and run:

expo init

From here you have a few options of which template for a new react-native app to use. I recommend picking the blank option.

From here you can name your project whatever you like.

Run the project

You may use a simulator. For the sake of keeping this tutorial to-the-point, I’m going to use expo web.

Go to the terminal and start your project:

expo start --web

Metro Bundler should open in your browser, your project should open in a new tab.

If you don’t see your project click “Run in web browser” in Metro Bunder.

Assuming you’re using Chrome, you can view a react native project as though it were on a mobile device. Other browsers have this ability too — But for the sake of brevity, I’m going to walk through the steps to set it up on Chrome only.

Right-click on the page and click inspect.

Click toggle device toolbar

Then select your preferred device to display.

Install Styled Components

Now that you have a project up and running you can install styled-components using your terminal.

yarn add styled-components

Create your first styled component

In App.js we’re going to replace the existing container component with a styled component so that you can compare the syntax.

In order to use styled-components, you have to import it into the file.

make sure that when you import styled-components you import it from the "styled-components/native" path rather than "styled-components". One is for React Native, the other is for the web. I make this mistake all the time.

Create a Container

First, remove all of the boilerplate. then create a new Container component.

Side note, you can also use react native syntax using camelCase rather than hyphening your style attribute names. But you will still have to use units like pixels otherwise your code will break. Because your IntelliSense will work better using hyphens, I recommend sticking to hyphens.

Create the Start Button

One of the great things about styled-components is the ability to create more generic base components and then extend them to create more complex or specific components.

So when creating a new component (in this case the green button) it’s useful to think:

“Can this component be broken down into smaller reusable chunks?”

Create BaseButton

Instead of starting by creating a green SuccessButton let's start by creating a BaseButton component we can reuse.

In fact, I think it would be really cool to have a button component where I can control both the background color and the text color using props. This means I have one highly reusable component without too much additional complexity.

First, we want to have some default styles for all of our button components. Our buttons have a standard height and minimum width when the text is small. They have some padding to avoid placing the text at the edge of the button. Finally, they have centered text using justify-content and align-items

Create BaseText

You cannot have a string as the child of a TouchableOpacity component. Strings must be wrapped with a <Text/> component.

I also prefer the look of white text to black text in this case — however rather than making a single WhiteText component or making it so all buttons have white text, we can use a property to control the color of our text.

the following syntax means that if there is a `color` prop, it will be used as the Text color, otherwise, the color attribute will be undefined and default to black.

Create Button

Now we can use this pattern to create a Button component. The Button component passes props to BaseButton (this is very important! styled-components requires that you pass the style prop so if we want to extend the Button component later it must have this prop to work!)

Button also passes color and children props into BaseText.

Extend SuccessButton component with Button

Now use the Button component to create a SuccessButton component. This will be used for our start button. This is the first example of extending a styled component so far. I’ll also take advantage of this opportunity to teach you how to use .attr on a styled component to pass props.

There’s a lot of concepts coming together here so I will break everything down below.

Extending Styles (see documentation)

Pass the Button component as a parameter to styled: styled(Button) this creates a new TouchableOpacity component that inherits all of the styles applied to the Button component.

Attaching Additional Props with .attrs (see documentation)

Set the color prop through the .attrs method styled(Button.attrs(props => ({color: "white}). This is basically equivalent to

<Button color={"white"}/>

SuccessButton applies its own background property.

`background-color: ${success};`

Improving Reusability

We could have done the same thing with the BaseButton component that we did with BaseText to pass in a background-color, and that probably would have been more consistent and reusable. But, I wanted to take the opportunity to combine many concepts together into a single component.

Render the Start Button

Lets put our new start button onto the page and see how everything looks!

Here’s how your App.js should look. I’m keeping all of the components in the Appfile for the sake of making sure there isn’t any added complexity or issues caused by imports. However, I highly recommend extracting these components into separate files.

And here’s our app

Start the Game

Now you should have a Start button in the center of your page. Right now it doesn’t do anything, but now we’re going to hook it up to start the game.

To do this, we’ll use a state variable that controls if the user is playing, starting. Later on this will also handle winning or losing the game.

We’ll build the game later. for now, we just want to render a placeholder for the game to make sure that our button works correctly.

When you click Start you should see the text Game Will Be Here

Create the Game

The game consists of 2 major parts.

  1. A clickable StressBall component that when clicked reduces stress level
  2. A StressBar which acts as a gauge for the user’s in-game stress level

Create the StressBar

The StressBar is made of 2 smaller components.

  1. A StressContainer component that has an outline of the maximum stress level
  2. A StressLevel component inside of the StressContainer that grows and changes color depending on the users stress level.

Create StressContainer

the outer container will be pretty static, and won’t change dynamically from props in any way

Create StressLevel

The StressLevel component has 2 properties that are dynamically controlled by props: height and color.

Because we’re going to have more properties this time, I’m going to show you a new syntax that is less verbose than using a function in every interpolated string.

Understanding the above block of code requires a bit of understanding of how RGB works in CSS.

rbg takes a numeric value between 0 and 255 for 3 parameters: red, green, and blue. We want our StressLevel component to start at the maximum green value and the minimum red value. Blue is always 0. This is the magic of css-in-js where we can dynamically control styles with javascript.

So when stressLevel is 0 the result is rgb(0, 255, 0). When stressLevel is 255 the result is rgb(255, 0, 0)

Now put everything together and make the StressBar component.

Configure Stress Level with Dynamic Data

We’re going to use an interval to increase an internal stressLevel state value

Intervals with hooks are a bit finicky. Whenever you use an interval inside of a useEffect hook, and you also use a state value — you have to make sure that your state value is listed as a dependency for the useEffect hook otherwise the value for the interval will be stale and never increase (feel free to remove the dependency and see what happens!)

Whenever you use a timeout or interval inside of useEffect you also have to make sure to clear the interval. (otherwise, this could lead to memory leak and a neverending interval)

If you’re not familiar with the useEffect hook then I recommend reading up on it in the React Docs.

For now, it’s good enough to know that whenever useEffect returns a function, that function will be called with the component unmounts.

This acts a lot like componentWillUnmount if you’re familiar with class components.

we also have to make sure that the stressLevel doesn’t increase beyond the height of its container (255). I’ll use Math.min for this.

Here’s the code:

Now if you run the project you should see that the StressBar rises until it hits the top.

Create the Stress Ball

So we’ve got one of our two parts down and now it’s time to add a StressBall so that the user can reduce their stress and win (or lose) the game!

We’re going to reuse the SuccessButton component because it has the same green background color that we want. And this is going to be a great opportunity to show one of the pitfalls of extending styles, and how to overcome it.

good? err..no

Why is this happening? Remember when we made the Base Button component we defined a min-width assuming that all of our buttons would be rectangular.

We don’t want to remove or alter this property because it would change the styles of any buttons built off of it.

Essentially we’ve coupled all of the button components to this one BaseButton component. Extending styles is powerful and useful but it suffers from the problems of inheritance. You necessarily couple your child components to your parent components.

Now we could simply make a new CircleButton component and that would be a great solution.

But I’m going to take this opportunity to show you how you can override styles when you have to.

Now here’s our corrected StressBall code:

Awesome. Now we can place that inside of our game and here’s our Stress Game so far. Again, I HIGHLY Recommend extracting out components for readability, but I want to make it so you can go to this code gist and copy it directly if you’re having issues.

Create the Reduce Stress FN

Right now, when the user clicks the StressBall component we don’t actually do anything yet.

Let’s make areduceStress function that triggers when the user presses the StressBall.

We have the same problem to solve here that we did where the stressLevel shouldn’t rise above 255, except now we don’t want it to ever be smaller than 0. We can solve this using Math.max

Win or Lose

The user can play the game. They press Start and the stress level starts to rise or fall when they click the StressBall.

Add won and lost to GameState

It would be great if they could actually win or lose though! in order to do that we’re going to have 2 more potential states for GameState

Render the lost and won views based on gameState

When the user wins we want to render a SuccessButton that says “You Won! Play Again?” that will start the game over when clicked.

When the user loses we want to render a DangerButton (which we will create) that says “You Lost! Try again?”

Create DangerButton

Render won and lost states

Win and lose functions

create a loseGame and winGame function in App. These set the gameState to won or lost

const winGame = () => setGameState(GameState.won);
const loseGame = () => setGameState(GameState.lost);

Pass them to the Game component

<Game loseGame={loseGame} winGame={winGame} />

Handle winning and losing in Game component

Here’s everything together. I’ve put it all into one file to avoid any complications with imports. If you’re having any trouble getting your code to work feel free to follow the link to this gist. Try copy and pasting it into your App.js to see if it works! You can also clone the repo

Thank you for reading!

Congrats! Provided everything worked correctly for you, you should have a playable game and a better understanding of how to apply styled-components.

If you have any questions please feel free to leave a comment and I will do my best to provide a helpful answer!

If you have any recommendations or notice any mistakes please let me know how you think I can improve! I’m still new to writing and I really appreciate the feedback to help me get better.

Also, if you enjoyed the article I would really appreciate a clap! You can hold down the button if you really liked it!

--

--

Brooklin Myers

Software Engineer. I create educational content focused on technology for mobile and web applications.