State Management made simple with hookState in React-Native
Over at Jupiter, we’re building new tools for fitness trainers to make it super simple to manage their clients. We’re working on releasing our app and it’s going to be out pretty soon! Recently we were building a feature to create new workouts which had multiple nested components, where we’d have to dynamically add or delete components as well as take input from the user. After we’d capture the form, we’d need to post it to our backend, which means we’d need to have a single view of all the data.
The Problem
To start with, we had a Workout Object, which is the top level component. We needed to capture a form where users would be able to dynamically add or remove components, as well as update their fields as required. Now, we could do this with Hooks, but we’d need to store the state in the parent object and then pass it down 2 levels to be able to store all the response in the Workout component.
One of the reasons why it must be in the Workout component is because we’d need to collect all of the data before we could submit it back to the backend, so we need to have an overview of all the inputs we’d take in the children components. We tried implementing this with hooks but we knew that we’d have to write a lot of other components in the future which would have similar patterns, and it was worth us investing in a state management library.
The Solution
Enter hookstate. What is hookstate? Well, it’s not the hooks API that was created by React. Maybe that’s why you don’t see this library in the first 2 pages in Google. But it’s a really simple, lightweight library and it can be pretty great for certain use cases. Whilst I personally love hooks, it’s not ideal for nested or deeply nested components.
Here’s the code below for what turned out to be a really simple solution.
//WORKOUT.JS
import { useState } from “@hookstate/core”;//The initial state of the workout component, which creates an //exercise with a name and it has a description. This is an array //which contains components of exercises. Each exercise component //creates an array of set objects.export default function Workout() {const state = useState([{name: “”,desc:””,sets: [{ reps: 10, weight: 0 },],},]);
//We also create a new Exercise component every time the user clicks //a button. By default, each set has 12 reps and 0 additional //weight.const createExercise = () => {state.merge([{name: “”,sets: [{ reps: 12, weight: 0 }],},]);};//Finally we map the exercise component and pass the exercise object //as props, I'll explain the benefits of passing the object in a //second.return(
{state.map((exercise, index) => {return <Exercise key={index} exercise={exercise}></Exercise>;})}<Button title=”Add Exercise” onPress={() => createExercise()} />
);}
This is basic React, and we’re not really doing anything different, except for the fact that we’re using the Hookstate library.
This is where using Hookstate really paid dividends! The concept of scoped state really simplified the codebase. In short, it converts the exercise props into a writeable object (That’s how I think of it anyway!) with just one line of code.
import { useState, none } from "@hookstate/core";export default function Exercise({ exercise }) {const exerciseState = useState(exercise);//An exercise can have multiple sets and we can easily create this //with the merge function, which adds new Set objects to the end of //the array.const handleAddSets = () => {exerciseState.nested(“sets”).merge([{ reps: 10, weight: 0 }]);console.log(“add sets”);};//We also needed to delete an Exercise component. The user needs to //be able to interact with the component. Here we can use //hookstate's set() function and delete the item by using none //object.const handleDeleteExercise = () => {exerciseState.set(none);};//In here, we pass the exercise name value and also track it when //the text changes.return(<TextInputstyle={styles.exerciseDescription}placeholder="Exercise name"value={exerciseState.name.value}onChangeText={(e) => exerciseState.name.set(e)}/>{exerciseState.sets.map((sets, index) => (<Set key={index} sets={sets} index={index + 1} />))}
);}
We’re doing the same thing here where we’re passing the Set object to the Set component, so that we could get the value of reps and weight for each set, as well as the ability to delete Sets.
export default function Set({ sets, index }) {const setsData = useState(sets);//Ability to delete setsconst handleDeleteSets = () => {setsData.set(none);};
So in essence, hookstate made it super easy to manage state across different components. We have 3 components and we can dynamically
- Capture user input
- Add new components
- Delete current components
We can do this with just 20 lines of code.
There’s no boilerplate code and it looks React-ish and it’s easy to read what it does.
I personally struggled to figure out how the API works as there’s not as much documentation about the library as there are for others, so I thought I’d share my experience in case it helps someone else out there. Here’s the final diagram with the final solution. There’s a lot of code in addition that I haven’t added here, but I wanted to keep it simple and highlight how awesome hookState is to the community! Here’s the final component diagram with the functions as well.