4.1.21 • Published 3 years ago

@dbeining/react-atom v4.1.21

Weekly downloads
2,001
License
MIT
Repository
github
Last release
3 years ago

TypeScript npm (scoped) npm bundle size (minified) npm bundle size (minified + gzip)

Build Status codecov npm

NpmLicense Commitizen friendly semantic-release

Description

react-atom provides a very simple way to manage state in React, for both global app state and for local component state: ✨Atoms✨.

Put your state in an Atom:

import { Atom } from "@dbeining/react-atom";

const appState = Atom.of({
  color: "blue",
  userId: 1
});

Read state with deref

You can't inspect Atom state directly, you have to dereference it, like this:

import { deref } from "@dbeining/react-atom";

const { color } = deref(appState);

Update state with swap

You can't modify an Atom directly. The main way to update state is with swap. Here's its call signature:

function swap<S>(atom: Atom<S>, updateFn: (state: S) => S): void;

updateFn is applied to atom's state and the return value is set as atom's new state. There are just two simple rules for updateFn:

  1. it must return a value of the same type/interface as the previous state
  2. it must not mutate the previous state

To illustrate, here is how we might update appState's color:

import { swap } from "@dbeining/react-atom";

const setColor = color =>
  swap(appState, state => ({
    ...state,
    color: color
  }));

Take notice that our updateFn is spreading the old state onto a new object before overriding color. This is an easy way to obey the rules of updateFn.

Side-Effects? Just use swap

You don't need to do anything special for managing side-effects. Just write your IO-related logic as per usual, and call swap when you've got what you need. For example:

const saveColor = async color => {
  const { userId } = deref(appState);
  const theme = await post(`/api/user/${userId}/theme`, { color });
  swap(appState, state => ({ ...state, color: theme.color }));
};

Re-render components on state change with the ✨useAtom✨ custom React hook

useAtom is a custom React Hook. It does two things:

  1. returns the current state of an atom (like deref), and
  2. subscribes your component to the atom so that it re-renders every time its state changes

It looks like this:

export function ColorReporter(props) {
  const { color, userId } = useAtom(appState);

  return (
    <div>
      <p>
        User {userId} has selected {color}
      </p>
      {/* `useAtom` hook will trigger a re-render on `swap` */}
      <button onClick={() => swap(appState, setRandomColor)}>Change Color</button>
    </div>
  );
}

Nota Bene: You can also use a selector to subscribe to computed state by using the options.select argument. Read the docs for details.

Why use react-atom?

function Awkwardddd(props) {
  const [name, setName] = useState("");
  const [bigState, setBigState] = useState({ ...useYourImagination });

  const updateName = evt => setName(evt.target.value);
  const handleDidComplete = val => setBigState({ ...bigState, inner: val });

  return (
    <>
      <input type="text" value={name} onChange={updateName} />
      <ExpensiveButMemoized data={bigState} onComplete={handleDidComplete} />
    </>
  );
}

Every time input fires onChange, ExpensiveButMemoized has to re-render because handleDidComplete is not strictly equal (===) to the last instance passed down.

The React docs admit this is awkward and suggest using Context to work around it, because the alternative is super convoluted.

With react-atom, this problem doesn't even exist. You can define your update functions outside the component so they are referentially stable across renders.

const state = Atom.of({ name, bigState: { ...useYourImagination } });

const updateName = ({ target }) => swap(state, prev => ({ ...prev, name: target.value }));

const handleDidComplete = val =>
  swap(state, prev => ({
    ...prev,
    bigState: { ...prev.bigState, inner: val }
  }));

function SoSmoooooth(props) {
  const { name, bigState } = useAtom(state);

  return (
    <>
      <input type="text" value={name} onChange={updateName} />
      <ExpensiveButMemoized data={bigState} onComplete={handleDidComplete} />
    </>
  );
}

Installation

npm i -S @dbeining/react-atom

Dependencies

react-atom has one bundled dependency, @libre/atom, which provides the Atom data type. It is re-exported in its entirety from @dbeining/atom. You may want to reference the docs here.

react-atom also has two peerDependencies, namely, react@^16.8.0 and react-dom@^16.8.0, which contain the Hooks API.

Documentation

react-atom API

@libre/atom API

Code Example: react-atom in action

import React from "react";
import ReactDOM from "react-dom";
import { Atom, useAtom, swap } from "@dbeining/react-atom";

//------------------------ APP STATE ------------------------------//

const stateAtom = Atom.of({
  count: 0,
  text: "",
  data: {
    // ...just imagine
  }
});

//------------------------ EFFECTS ------------------------------//

const increment = () =>
  swap(stateAtom, state => ({
    ...state,
    count: state.count + 1
  }));

const decrement = () =>
  swap(stateAtom, state => ({
    ...state,
    count: state.count - 1
  }));

const updateText = evt =>
  swap(stateAtom, state => ({
    ...state,
    text: evt.target.value
  }));

const loadSomething = () =>
  fetch("https://jsonplaceholder.typicode.com/todos/1")
    .then(res => res.json())
    .then(data => swap(stateAtom, state => ({ ...state, data })))
    .catch(console.error);

//------------------------ COMPONENT ------------------------------//

export const App = () => {
  const { count, data, text } = useAtom(stateAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>

      <button onClick={increment}>Moar</button>
      <button onClick={decrement}>Less</button>
      <button onClick={loadSomething}>Load Data</button>
      <input type="text" onChange={updateText} value={text} />

      <p>{JSON.stringify(data, null, "  ")}</p>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

🕹️ Play with react-atom in CodeSandbox 🎮️

You can play with react-atom live right away with no setup at the following links:

JavaScript SandboxTypeScript Sandbox
try react-atomtry react-atom

Contributing / Feedback

Please open an issue if you have any questions, suggestions for improvements/features, or want to submit a PR for a bug-fix (please include tests if applicable).

4.1.21

3 years ago

4.1.20

3 years ago

4.1.19

3 years ago

4.1.17

3 years ago

4.1.18

3 years ago

4.1.16

3 years ago

4.1.15

3 years ago

4.1.14

3 years ago

4.1.13

3 years ago

4.1.12

3 years ago

4.1.11

4 years ago

4.1.10

4 years ago

4.1.9

4 years ago

4.1.8

4 years ago

4.1.7

4 years ago

4.1.6

4 years ago

4.1.5

4 years ago

4.1.4

4 years ago

4.1.3

4 years ago

4.1.2

5 years ago

4.1.1

5 years ago

4.1.0

5 years ago

4.0.2

5 years ago

4.0.1

5 years ago

4.0.0

5 years ago

3.2.0

5 years ago

3.1.3

5 years ago

3.1.2

5 years ago

3.1.1

5 years ago

3.1.0

5 years ago

3.0.9

5 years ago

3.0.8

5 years ago

3.0.7

5 years ago

3.0.6

5 years ago

3.0.5

5 years ago

3.0.4

5 years ago

3.0.3

5 years ago

3.0.2

5 years ago

3.0.1

5 years ago

3.0.0

5 years ago

2.0.0

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago