DEV Community

Cover image for Stress Testing React Easy State
Miklos Bertalan
Miklos Bertalan

Posted on • Originally published at Medium

Stress Testing React Easy State

The first part is my usual intro the React Easy State. Skip ahead to the next section if you are already familiar with it.

React Easy State is a transparent reactive state management library with two functions and two accompanying rules.

  1. Always wrap your components with view().
  2. Always wrap you state store objects with store().
import React from 'react'
import { store, view } from 'react-easy-state'

const counter = store({
  num: 0,
  incr: () => counter.num++
})

export default view(() =>
  <button onClick={counter.incr}>{counter.num}</button>
)
Enter fullscreen mode Exit fullscreen mode

This is enough for it to automatically update your views when needed. It doesn’t matter how you structure or mutate your state stores, any syntactically valid code works.

Backing up the claims

It doesn't matter how you structure or mutate your state stores, any syntactically valid code works.

The final sentence of the intro was not a rhetorical statement and I would like to prove it with a simple App.

import React from 'react'
import { view, store } from 'react-easy-state'

const users = window.users = store([])

function UsersApp () {
  return users.map((user, idx) =>
    <div key={idx}>{user.name} <small>{user.email}</small></div>
  )
}

export default view(UsersApp)
Enter fullscreen mode Exit fullscreen mode

This snippet creates a users array and exposes it globally on window.users, then UsersApp maps the array to a simple React view.

We will mutate the users array from the developer console in increasingly exotic ways and see how UsersApp reacts. Hint: it should automatically re-render when users are added, removed or modified in any way.

#1. Warm up: Adding a new user

Let’s start by adding a simple user to the users array and then changing their email address.

users.push({ name: 'Bob', email: 'bob@gmail.com' })
users[0].email = 'bOb1.gmail.com'
Enter fullscreen mode Exit fullscreen mode

Easy State knows that UsersApp iterates the users array during its render and it knows that user.email is rendered for every user. From this, it deduces that UsersApp should re-render after the above state changes.

Adding a new user

Try the live demo!

You can follow along with the live example app. Don’t be surprised, it’s a nearly empty page. You can fill it with users from your developer console — as I did.

#2. Dynamic properties

The first example was not that impressive. Let’s see something that is exclusive to ES6, by pasting these lines — one by one — to the console.

users[1] = { name: 'Ann' }
users[1].email = 'ann@windowslive.com'
delete users[1].email
Enter fullscreen mode Exit fullscreen mode

Dynamic properties

This time Ann was added by users[1] = newUser instead of push and initially, she didn’t have an email. Adding new properties dynamically — like the email — can only be tracked by ES6 Proxies. Older transparent state management libraries won’t re-render the app in these cases.

delete is another operation that was impossible to track until recently. Easy State re-renders the UsersApp if you delete a property which is used during its render.

#3. Property accessors and enumeration

It’s time to take a deeper dive. For this example, we will need some preparation code.

const Dave = {
  name: 'Dave',
  emails: { primary: 'dave@gmail.com' },
  get email () {
    const result = []
    for (const type in this.emails) {
      result.push(`${type}: ${this.emails[type]}`)
    }
    return result.join(', ')
  }
}
Enter fullscreen mode Exit fullscreen mode

Dave is a user with multiple email addresses. A property getter is used to dynamically generate his email list by enumerating the emails object and constructing a string from it.

users.push(Dave)
users[2].emails.secondary = 'dave@risingstack.com'
delete users[2].emails.primary
Enter fullscreen mode Exit fullscreen mode

Property accesors

Easy State knows that Dave’s email is generated from all of the properties in his emails collection. If a new email is added to the collection it re-renders the UsersApp to reflect it. The same happens when an email is deleted or modified.

#4. The final boss: Inheritance

Let’s see what happens if we throw in prototypal inheritance to the mix. This example will also need a preparation script.

const John = {
  email: 'john@gmail.com',
  name: 'John'
}

Object.setPrototypeOf(John, users[1])
Enter fullscreen mode Exit fullscreen mode

John — the new user — inherits his name and email from Ann — the second user.

users.push(John)
delete users[3].name
users[1].name = 'Ben'
users[3].name = 'John Jr.'
Enter fullscreen mode Exit fullscreen mode

Inheritance

  • If we delete John’s name, it falls back to 'Ann' because of the inheritance.
  • Then editing Ann’s name changes both Ann’s and John’s name.
  • Finally, we add an own name for John again.

Easy State re-renders the App after each of these operations — since all of them result in a different view.

Conclusion

The above examples feature basic JavaScript operations and patterns, that most people don’t even consider for state management. I think that part of the reason is the added complexity from third party state management APIs.

By using Easy State you can focus your brain capacity on inventing your personal, reusable and pure JavaScript patterns, instead of learning new APIs and concepts.

If this article captured your interest please help by sharing it. Also check out the Easy State repo and leave a star before you go.

Thanks!
(This article was originally published on Medium)

Top comments (2)

Collapse
 
nestedsoftware profile image
Nested Software

This looks awfully nice - it looks cleaner than the built-in context api to me. Do you have any general thoughts on easystate vs the context api?

Collapse
 
solkimicreb profile image
Miklos Bertalan

Thanks!

I think they are pretty different, context API is used for (buffed up) DI and most state management libs use it as a part of their functionality - usually combined with HOCs. I don't think raw context should be used for state management, but it is a nice building block in some cases.

Easy State does not have built-in DI, because I don't think it's necessary. "Global state is global" and I don't see the point of injecting global, singleton objects in most use cases. This is why I advise everyone to use simple ES6 exports and imports instead. I think context is best used for spread-out local state (where you have to share it between a branch of components but don't want to make it global).

If you need, you can combine Easy State with Context to add some DI to the mix though. As an example, this could be a naive implementation of react-redux for easy state:

import React from 'react'
import { view } from 'react-easy-state'

const { Provider, Consumer } = React.createContext()

function connect(mapStateToProps) {
  return Comp =>
    view(props => (
      <Consumer>
        {state => <Comp {...mapStateToProps(state)} {...props} />}
      </Consumer>
    ))
}

export { Provider, connect }

Summary: I don't think context is enough for state management in its own, but it can be a nice building block. With a little effort, you can make a new state management lib on top of it. Some state management libs do this and use context behind the scenes, easy state is not one of them though.