Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] API #27

Closed
dai-shi opened this issue Sep 11, 2020 · 11 comments · Fixed by #83
Closed

[Docs] API #27

dai-shi opened this issue Sep 11, 2020 · 11 comments · Fixed by #83

Comments

@dai-shi
Copy link
Member

dai-shi commented Sep 11, 2020

Here's a draft for API docs.

atom

atom is a function to create an atom config. It's an object and the object identity is important. You can create it from everywhere. Once created, you shouldn't modify the object. (Note: There might be an advanced use case to mutate atom configs after creation. At the moment, it's not officially supported though.)

const primitiveAtom = atom(initialValue)
const derivedAtomWithRead = atom(readFunction)
const derivedAtomWithReadWrite = atom(readFunction, writeFunction)
const derivedAtomWithWriteOnly = atom(null, writeFunction)

There are two kinds of atoms: a writable atom and a read-only atom
Primitive atoms are always writable. Derived atoms are writable if writeFunction is specified.
The writeFunction of primitive atoms is equivalent to the setState of React.useState.

The signature of readFunction is (get) => Value | Promise<Value>, and get is a function that takes an atom config and returns its value stored in Provider described below.
Dependency is tracked, so if get is used for an atom at least once, then whenever the atom value is changed, the readFunction will be reevaluated.

The signature of writeFunction is (get, set, update) => void | Promise<void>.
get is similar to the one described above, but it doesn't track the dependency. set is a function that takes an atom config and a new value, and update the atom value in Provider. update is an arbitrary value that we receive from the updating function returned by useAtom described below.

Provider

Atom configs don't hold values. Atom values are stored in a Provider. A Provider can be used like React context provider. Usually, we place one Provider at the root of the app, however you could use multiple Providers, each storing different atom values for its component tree.

const Root = () => (
  <Provider>
    <App />
  </Provider>
)

useAtom

The useAtom hook is to read an atom value stored in the Provider. It returns the atom value and an updating function as a tuple, just like useState. It takes an atom config created with atom(). Initially, there is no value stored in the Provider. At the first time the atom is used via useAtom, it will add an initial value in the Provider. If the atom is a derived atom, the read function is executed to compute an initial value. When an atom is no longer used, meaning the component using it is unmounted, the value is removed from the Provider.

const [value, updateValue] = useAtom(anAtom)

The updateValue takes just one argument, which will be passed to the third argument of writeFunction of the atom. The behavior is totally depends on how the writeFunction is implemented.

@dai-shi
Copy link
Member Author

dai-shi commented Sep 11, 2020

This is just a first draft. Feedback is very welcomed to improve it.

@dai-shi dai-shi pinned this issue Sep 11, 2020
@NorweskiDrwal
Copy link

NorweskiDrwal commented Sep 11, 2020

I would love to see an example of updating properties of a stored object.

const funkyPigAtom = atom({ funkyPig: { glasses: 'stylish', bling: 'golden' }})

Say I wanna update bling.

@dai-shi
Copy link
Member Author

dai-shi commented Sep 11, 2020

@NorweskiDrwal Hi, I made a new Tips issue. Please check: #32

@antipalindrome
Copy link

antipalindrome commented Sep 11, 2020

I would love to see clarification/documentation on how to use atoms across multiple files. So far I'm using one file that contains all the atoms and I import the individual atoms I need into my component files, but not sure if that's the best way.

@dai-shi
Copy link
Member Author

dai-shi commented Sep 11, 2020

@hsw107 You can define atoms anywhere in any files, export them, and import them where needed. If you are asking some best practices to organize atoms in files, it's a good question. We'd like to collect various use cases and find a good pattern.

@gauravkumar37
Copy link

gauravkumar37 commented Sep 12, 2020

OTOH if one wants an atom to be completely resident within the component including the atom config, sort of like the React's useState, a custom hook like this can be useful:

const useCreateAtom = <Value extends any>(initialValue: NonFunction<NonPromise<Value>>) => {
  const [dataAtom] = useState(() => atom(initialValue));
  return useAtom(dataAtom);
};
function TodoItem({idx}: { idx: number }) {
  const [data, setData] = useCreateAtom({id: idx, done: false});
}

Edit 1: @dai-shi The above code works fine but when I try to use the below code, it doesn't work. Please suggest how to fix it. setData is no longer callable because it is now a union of types of data, setData and dataAtom.

const useCreateAtom = <Value extends any>(initialValue: NonFunction<NonPromise<Value>>) => {
  const [dataAtom] = useState(() => atom(initialValue));
  return [...useAtom(dataAtom), dataAtom];
};

function TodoItem({idx}: { idx: number }) {
  const [data, setData, dataAtom] = useCreateAtom({id: idx, done: false});
}

Edit 2: Manually specifying return types work, not sure why TS was not able to infer the return type

const useCreateAtom = <Value extends any>(initialValue: NonFunction<NonPromise<Value>>): [NonPromise<Value>, (update: SetStateAction<Value>) => void, PrimitiveAtom<Value>] => {
  const [dataAtom] = useState(() => atom(initialValue));
  return [...useAtom(dataAtom), dataAtom];
};

@dai-shi
Copy link
Member Author

dai-shi commented Sep 12, 2020

@gauravkumar37

  return [...useAtom(dataAtom), dataAtom] as const;

would also work.

@mwood23
Copy link

mwood23 commented Sep 17, 2020

Could you clarify this part of the docs?

The useAtom hook can be seen as a special useContext hook. It returns an atom value stored in the Provider and an updating function as a tuple, just like useState.

The way it reads it makes it seem like the registration of an atom to the Provider is done somewhere else. Based on the code it seems like it would be more fair to say something like this...

atom is a function to create an atom config. It's an object and the object identity is important. You can create it from everywhere, and must register it to a Jotai Provider after creation. To do so, you must use one of the built in hooks to register it to a Jotai Provider.

Amazing work on the library! I needed this exact thing for a rich text editor to manage the state of each created block. Since the identity of the atoms are important would this be an acceptable pattern for registering an atom per component? I want it to live in render so that each time I invoke <Component /> in the code it has its own atom state. Could be a good thing to capture based on #27 (comment).

const Component = () => {
  const atomRef = React.useRef(atom(0))
  const [count, setCount] = useAtom(atomRef.current)

  return <div>something</div>
}

@dai-shi
Copy link
Member Author

dai-shi commented Sep 17, 2020

Thanks for your feedback!

can be seen as a special useContext hook

I wanted to make an analogy to typical Context+useState usage. Maybe I should add some code.

it seem like the registration of an atom to the Provider is done somewhere else.

I see. We need to add some notes then.

and must register it to a Jotai Provider after creation. To do so,

The registration is rather implicit, so it's not a user who registers explicitly, I'd say. Let me try to incorporate.

Amazing work on the library! I needed this exact thing for a rich text editor to manage the state of each created block.

Would love to see what would build (even a small example).

const atomRef = React.useRef(atom(0))

This is valid, but this is equivalent to React.useState(0) because refs are gone on unmount.
Is it what you want?

@mmiller42
Copy link

Can you clarify how the get function subscribes to updates for a derived atom? For example, I assume something like this wouldn't work right?

atom(get => get(condition ? atom1 : atom2))

though I'm not sure (I figured it tracks the dependencies the first time it's evaluated)

@dai-shi
Copy link
Member Author

dai-shi commented Sep 25, 2020

@mmiller42
It should work. (More precisely, it's fixed since v0.7.0. Prior to that version, dependencies are accumulated in each invocation of get.)
Note condition should come from some other atoms for proper tracking. If it's just a global variable, the behavior would become just random..

Please also follow the discussion in #66. I need your feedbacks.

I figured it tracks the dependencies the first time it's evaluated

It will track them every time it's evaluated.

@dai-shi dai-shi mentioned this issue Oct 4, 2020
@dai-shi dai-shi unpinned this issue Oct 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants