Skip to content

Latest commit

 

History

History

arbor-react

Arbor React Binding

"Buy Me A Coffee"

Official React binding for @arborjs/store.

Installation

Via npm

npm install @arborjs/react

Or via yarn

yarn add @arborjs/react

Make sure you have @arborjs/store installed.

Usage

Similar to how you'd do in a Vanilla JS app, using Arbor in a React app can be done in 3 simple steps.

Note

Make sure you take a look at the documentation for @arborjs/store before using this biding. Also, take a moment to get familiar with some of the caveats of working with Arbor.

Take the following React app as an example:

export default function App() {
  return (
    <div>
      <Counter />
      <Actions />
    </div>
  )
}
  1. Create your Arbor store:
import { Arbor, useArbor } from "@arborjs/react"

const store = new Arbor({
  count: 0,
})
  1. Use the useArbor hook to connect React components to the store:
function Counter() {
  // This component will automatically re-render when the value of the counter changes
  const counter = useArbor(store)

  return <h1>Count: {counter.count}</h1>
}
  1. Mutate the store state using plain JS APIs:

Note

We don't need to useArbor here because this component does not need to re-render when the store changes.

function Actions() {
  return (
    <div>
      <button onClick={() => store.state.count--} value="Decrement">
        -1
      </button>
      <button onClick={() => store.state.count++} value="Increment">
        +1
      </button>
    </div>
  )
}

Stores can be as complex as you may need them to be, holding arrays, complex objects, or using classes to build a more complex data model for your application.

Optimal Re-Renders

Arbor implements a path tracking mechanism that allows for scoped store references. This is what powers the useArbor hook, enabling Arbor to determine exactly which React components must re-render when a specific part of the state is changed, avoiding unnecessary re-render by default.

Also, Arbor ensures that object and method references are stable, e.g. their memory reference is kept the same across re-renders unless the object changes, this means you can safely pass store objects via prop or use their methods as event handlers to components memoized with React's memo to prevent unnecessary re-render of component subtrees.

useArbor vs useState

You may choose to use useArbor instead of useState to manage the local state of your React components and leverage Arbor's reactive API, removing boilerplate while letting you focus on application logic.

// Application state held in an Arbor store
const store = new Arbor(new TodosApp())

function NewTodoForm() {
  // Use Arbor to manage the component's local state
  const form = useArbor({
    input: {
      value: "",
      onChange(e: ChangeEvent<HTMLInputElement>) {
        this.value = e.target.value
      },
    },
    onSubmit(e: FormEvent) {
      // Merge the local state back into the application store
      store.state.todos.push(new Todo(this.input.value))
      this.input.value = ""
    },
  })

  return (
    <form onSubmit={form.onSubmit}>
      <input {...form.input} />
      <button>Add</button>
    </form>
  )
}

By using Arbor to manage your component's local state, you can make that local state reactive, while being able to leverage all the simplicity of using JS standard constructs to interact with the state such as regular assignments, or other mutable APIs like Array#push.

Detached Fields vs useRef

Sometimes, it's useful to track values across component re-renders without triggering more re-renders as you change that value. Normally in React, you'd resort to useRef for that purpose.

Arbor allows you to decorate class fields with @detached to inform the store that the value should be detached from its state tree. This makes detached values non-reactive, e.g. changes to them will not trigger subscription notifications, in the context of React that translates to components not reacting to these changes, thus no re-rendering.

Imagine a hypothetical todos app where we need to track whether the priority of a todo has been changed for analytics purposes but that information does not have to be displayed anywhere on the UI, we can achieve that using @detached on the Todo type itself:

import { detached, proxiable } from "@arborjs/store"

@proxiable
class Todo {
  ...
  // Detached fields are "detached" from the store's state tree so changing
  // its value will not notify susbcribers.
  @detached priorityChanged = false
  ...
}

Changes to Todo#priorityChanged will not trigger mutation events in the store, preventing any components from re-rendering.

Learn By Example

We've put together a couple of code sandboxes with examples of how to use Arbor in a React app with code comments further explaining some of the concepts that you may find helpful.

Support This Project

"Buy Me A Coffee"

License

Arbor is MIT licensed.