3

I am wondering if it's possible to store a function as a jotai atom, and if so, how. Simply replicating the standard pattern for creating and consuming jotai atom-states, which works fine when the value of the state is an array, does not work when the value of the state is a function.

"./jotaiStore.js"

import { atom } from "jotai";

function test(x) {return x + 4;}
let testa = atom(test);
export { testa }; 

./App.js

import { testa } from "./jotaiStore.js";
import { useAtom } from "jotai";

export default function App() {
  
  const [locTest, locSetTest] = useAtom(testa);
  console.log(locTest);
  console.log(locTest(1));

  return (
    <div className="App">
    </div>
  );
}

See https://codesandbox.io/s/thirsty-brook-n1ucjr?file=/src/App.js:24-493.

The resulting log for console.log(locTest) is:

function (a) {
        dependencies.add(a);
        var aState = a === atom ? getAtomState(version, a) : readAtomState(version, a);

        if (aState) {
          if ('e' in aState) {
            throw aState.e;
          }

          if ('p' in aState) {
            throw aState.p;
          }

          return aState.v;
        }

        if (hasInitialValue(a)) {
          return a.init;
        }

        throw new Error('no atom init');
      }4 

and console.log(locTest(1)) generates an error "locTest is not a function".

EDIT: I found a way to store a function as a jotai state (see below) but not one I can --- or know how to --- update. If someone else has a better updatable solution, I'd still be very interested.

FZS
  • 243
  • 1
  • 8

3 Answers3

2

I've managed to come up with solution for my case with the same requirement, that might help you with your implementation.

The idea is to create Jotai state and derived state. The latter will contain the logic of the function you want to share in the app. You can trigger it by setting the Jotai state, which'll launch the code in derived state.

The code'll be something like this:

store.js:

import { atom } from 'jotai'

export const stateAtom = atom(1)
export const derivedStateAtom = atom(get => get(stateAtom) + 4)

Wherever you want to trigger the function:

Component1.js:

import { useAtom } from 'jotai'
import { stateAtom } from '../store'

export default function Component1() {

  const [ , setState ] = useAtom(stateAtom)

  // pass the argument of function
  setState(x)

  ...
}

Wherever you want to have the results of the function:

Component2.js:

import { useAtom } from 'jotai'
import { derivedStateAtom } from '../store'

export default function Component2() {

  // "derivedState" will be the result of your function
  const [ derivedState ] = useAtom(derivedStateAtom)

  ...
}

If you have states that depend on each other, you can useEffect with dependencies (do something with one state exactly after another state will change)

Alex TechNoir
  • 133
  • 1
  • 4
  • 12
2

Not sure if you still need this, but Daishi actually suggests wrapping the function in an object.

const fnAtom = atom({ fn: () => "your function" })

You can then create a derived readable and/or writable atom based on the above primitive atom.

const doWhateverFnAtom = atom(
  (get) => get(fnAtom),
  (get, set, newFn) => {
    set(fnAtom, { fn: newFn })
  }
)

Source: https://github.com/pmndrs/jotai/discussions/1746#discussioncomment-4872129

vwstang
  • 36
  • 3
1

This explanation on how functions can be stored in React states https://medium.com/swlh/how-to-store-a-function-with-the-usestate-hook-in-react-8a88dd4eede1 seems to carry through to jotai (see below).

Unfortunately, I wasn't yet able to use the same explanation to update function-valued states in jotai (I am getting "Error not writable atom" when I try to adapt the update method described in the above link).

"./jotaiStore.js"

import { atom } from "jotai";

function test(x) {return x + 4;}
let testa = atom( 
                  () => x => test(x) 
                );
export { testa }; 

./App.js

import { testa } from "./jotaiStore.js";
import { useAtom } from "jotai";

export default function App() {
  
  const [locTest, locSetTest] = useAtom(testa);
  console.log(locTest);
  console.log(locTest(1));

  return (
    <div className="App">
    </div>
  );
}

See https://codesandbox.io/s/loving-architecture-693i76?file=/src/App.js.

FZS
  • 243
  • 1
  • 8