particule


Fine-grained atomic React state management library


yarn add particule

Particule is an atomic React state management library inspired by the best of Recoil, Jotai and Redux. You can choose which component subscribe to which state and so avoid useless re-render and computations.

✨ Features

  • Super-easy API
  • TypeScript ready
  • Suspense support
  • Minimal footprint (1kB gzipped)
  • Hooks to add functionality

🚀 Examples

Basic

const textAtom = atom('Hello world!')

function App() {
  const [text, setText] = useAtom(textAtom)

  return (
    <>
      <p>{text}</p>
      <button onClick={() => setText('Updated!')}>Update</button>
    </>
  )
}

Fine-grained

const textAtom = atom('Hello world!')

function Text() {
  const text = useGetAtom(textAtom)

  return <p>{text}</p>
}

// Won't re-render!
function Button() {
  const setText = useSetAtom(textAtom)

  return <button onClick={() => setText('Updated!')}>Update</button>
}

// Won't re-render!
function App() {
  return (
    <>
      <Text />
      <Button />
    </>
  )
}

Composition

const eurosAtom = atom(10)
const dollarsAtom = atom(get => get(eurosAtom) * 1.15)

function App() {
  const [euros, setEuros] = useAtom(eurosAtom)
  const [dollars, setDollars] = useAtom(dollarsAtom)

  return (
    <>
      <input onChange={({ target }) => setEuros(target.value)} value={euros} />
      <input onChange={({ target }) => setDollars(target.value)} value={dollars} />
    </>
  )
}

Suspense

const nameAtom = atom(async () => {
  const json = await (await fetch("https://randomuser.me/api/")).json();

  return json.results[0].name.first;
});

function Name() {
  const name = useGetAtom(nameAtom)

  return <p>My name is {name}</p>
}

function App() {
  return (
    <Suspense fallback='Loading...'>
      <Name />
    </Suspense>
  )
}

Dispatch

const counterAtom = atom(0)
const dispatchCounter = dispatch(counterAtom, value => ({
  increment: (newValue: number) => value + newValue,
  decrement: (newValue: number) => value - newValue,
}))

function App() {
  const counter = useGetAtom(counterAtom)

  return (
    <>
      <p>{counter}</p>
      <button onClick={() => dispatchCounter('increment', 1)}>Increment</button>
      <button onClick={() => dispatchCounter('decrement', 1)}>Decrement</button>
    </>
  )
}

Custom atom with hooks

const noZeroAtom = createAtom({
  beforeValueSet: (_, value) => {
    if (value === 0) {
      throw new Error('Cannot set value to 0')
    }

    return value
  }
})

const counterAtom = noZeroAtom(3)

function App() {
  const [count, setCount] = useAtom(counterAtom)

  return (
    <>
      <p>{count}</p>
      <button onClick={() => setCount(count => count - 1)}>Reduce</button>
    </>
  )
}