Innei / jojoo

A utils and extra react hooks for Jotai v2.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Jojoo

A utils and extra react hooks for Jotai v2.

Install

pnpm i jojoo

Usage

If you want to use custom store, should setGlobalStore first.

import { setGlobalStore } from 'jojoo'
import { createStore, getDefaultStore } from 'jotai/vanilla'

// if you use custom store
const store = createStore()
setGlobalStore(store)

React Hooks

createAtomsContext

You can use createAtomsContext to implement a simple Store. Pass in an object of atoms as state. An optional second argument is an action that accepts a context object. You can access the current scope's atoms through ctx.atoms and then change the value of an atom in the current context using set.

Note

The atoms may not be the passed-in globalAtoms; they might be atoms that are overridden by a Provider. See createOverrideAtomsContext for details.

Here is a simple example:

import 'jojoo/react'

const globalAtoms = {
  aAtom: atom(0),
  bAtom: atom(false),
}

const context = createAtomsContext(globalAtoms, (ctx) => {
  const { atoms, set } = ctx
  return {
    increment: () => {
      set(atoms.aAtom, (current) => current + 1)
    },
  }
})

const [Provider, hooks, atoms, actions] = context
const [useContextAtoms, useStoreValue, useContextActions] = hooks

// Wrap `Provider` for your component

const App = () => {
  return (
    <Provider>
      <Count />
      <IncrementButton />
    </Provider>
  )
}

const Count = () => {
  const aCount = useStoreValue('aAtom')
  return <span>{aCount}</span>
}

const IncrementButton = () => {
  const inc = useContextActions().increment
  return <button onClick={inc}>Plus</button>
}

You can directly call actions returned by createAtomsContext outside of the component.

const context = createAtomsContext(globalAtoms, (ctx) => {
  const { atoms, set } = ctx
  return {
    increment: () => {
      set(atoms.aAtom, (current) => current + 1)
    },
  }
})

const [Provider, hooks, atoms, actions] = context

// not in React component.
// do something..
actions.increment()

createOverrideAtomsContext

In some cases, atoms created via createAtomsContext are used to manage data in a global store, which might be a global post state manager. However, when there exists a nested post on the page, you can't manage them globally.

At this point, you can use createOverrideAtomsContext to isolate global states, enabling state isolation between child components.

Here is a simple example:

const context = createAtomsContext(
  {
    postId: atom('0'),
    text: atom('global post text'),
  },
  ({ atoms, set }) => {
    return {
      setText: (text: string) => {
        set(atoms.text, text)
      },
    }
  },
)

const [GlobalDataProvider, [useAtoms, useDataValue, useDataActions]] = context
const OverrideProvider = createOverrideAtomsContext(context, {
  text: atom('override post text'),
  postId: atom('1'),
})

const DataRender: FC<{}> = ({ testId }) => {
  const text = useDataValue('text')
  const id = useDataValue('postId')
  return (
    <div>
      <p>
        Data Id:
        <span>{id}</span>
      </p>

      <p>
        Data Text:
        <span>{text}</span>
      </p>
    </div>
  )
}

const DataActions: FC<> = (props) => {
  const { testId } = props
  const { setText } = useDataActions()
  const text = useDataValue('text')
  return (
    <div>
      <button onClick={() => setText(`${text} updated`)}></button>
    </div>
  )
}

// ReactNode structure like:
const App = () => (
  <GlobalDataProvider>
    <DataRender />
    <DataActions />

    <OverrideProvider>
      <DataRender />
      <DataActions />
    </OverrideProvider>
  </GlobalDataProvider>
)

Child components wrapped by OverrideProvider will use the overridden atoms, isolated from global atoms. Of course, you can also use it in a nested manner.

// ReactNode structure like:
;<GlobalDataProvider>
  <DataRender />
  <DataActions />

  <OverrideProvider>
    <DataRender />
    <DataActions />

    <OverrideProvider>
      <DataRender />
      <DataActions />
    </OverrideProvider>
  </OverrideProvider>
</GlobalDataProvider>

createModelDataContext

Create a dataset context through createModelDataContext, which can manage data with Jotai, and then pass it to descendants through React.context. Utilize the feature of React.context to isolate state in multiple scenarios.

A simple usage example:

interface NoteModel {
  title: string
}

const {
  ModelDataProvider,
  ModelDataAtomProvider,
  getGlobalModelData: getModelData,
  setGlobalModelData: setModelData,
  useModelDataSelector,
  useSetModelData,
} = createModelDataProvider<NoteModel>()

export {
  ModelDataProvider as CurrentNoteDataProvider,
  ModelDataAtomProvider as CurrentNoteDataAtomProvider,
  getModelData as getCurrentNoteData,
  setModelData as setCurrentNoteData,
  useModelDataSelector as useCurrentNoteDataSelector,
  useSetModelData as useSetCurrentNoteData,
}

const App = () => {
  return (
    <>
      <CurrentNoteDataProvider data={data} />
      <DataRender />
    </>
  )
}

const DataRender = () => {
  const title = useCurrentNoteDataSelector((n) => n.title)
  return <span>{title}</span>
}

You can also use ModelDataAtomProvider for scope isolation. In this way, the internal data of ModelData in both App and AnotherData are completely independent.

const App = () => (
  <>
    <CurrentNoteDataProvider data={data} />
    <DataRender />
    <AnotherData />
  </>
)

const AnotherData = () => {
  const overrideAtom = useMemo(() => atom(null as null | NoteModel), [])

  return (
    <CurrentNoteDataAtomProvider overrideAtom={overrideAtom}>
      <CurrentNoteDataProvider data={data} />
      <DataRender />
    </CurrentNoteDataAtomProvider>
  )
}

License

2023 © Innei, Released under the MIT License.

Personal Website · GitHub @Innei

About

A utils and extra react hooks for Jotai v2.

License:Other


Languages

Language:TypeScript 98.2%Language:JavaScript 1.3%Language:Shell 0.3%Language:CSS 0.2%