Skip to content

Commit

Permalink
Merge pull request #17 from yf-yang/main
Browse files Browse the repository at this point in the history
feat: add read and write and subscribe
  • Loading branch information
zbeyens authored Jan 15, 2025
2 parents c1878a3 + dd37b66 commit 820af7a
Show file tree
Hide file tree
Showing 7 changed files with 1,143 additions and 138 deletions.
28 changes: 28 additions & 0 deletions .changeset/thick-dolphins-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'jotai-x': major
---

1. Rename `get` to `useValue`, `set` to `useSet`, `use` to `useState`.
2. `use<Name>Store().store()` -> `use<Name>Store().store`.
3. `use<Name>Store().get.value(option)` and `use<Name>Store().set.value(option)`'s `option` parameters are no longer supported. Pass the option to `use<Name>Store()` instead.
4. Rename APIs:
- `use<Name>Store().get.key()` -> `use<Name>Store().useKeyValue()`
- `use<Name>Store().get.key()` -> `use<Name>Store().useValue('key')`
- `use<Name>Store().set.key()` -> `use<Name>Store().useSetKey()`
- `use<Name>Store().set.key()` -> `use<Name>Store().useSet('key')`
- `use<Name>Store().use.key()` -> `use<Name>Store().useKeyState()`
- `use<Name>Store().use.key()` -> `use<Name>Store().useState('key')`
- `use<Name>Store().get.atom(atomConfig)` -> `use<Name>Store().useAtomValue(atomConfig)`
- `use<Name>Store().set.atom(atomConfig)` -> `use<Name>Store().useSetAtom(atomConfig)`
- `use<Name>Store().use.atom(atomConfig)` -> `use<Name>Store().useAtomState(atomConfig)`
5. More APIs to directly get/set/subscribe atom states:
- `use<Name>Store().getKey()`
- `use<Name>Store().get('key')`
- `use<Name>Store().setKey(...args)`
- `use<Name>Store().set('key', ...args)`
- `use<Name>Store().subscribeKey(...args)`
- `use<Name>Store().subscribe('key', ...args)`
- `use<Name>Store().getAtom(atomConfig)`
- `use<Name>Store().setAtom(atomConfig, ...args)`
- `use<Name>Store().subscribeAtom(atomConfig, ...args)`

142 changes: 129 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# JotaiX

[Migrating from v1 to v2](#migrate-from-v1-to-v2)

JotaiX is a custom extension of [Jotai](https://github.com/pmndrs/jotai), a primitive and flexible state management library for React. Jotai offers a
minimalistic API to manage global, derived, or async states in React, solving common issues such as unnecessary
re-renders or complex context management. JotaiX builds upon this foundation, providing enhanced utilities and patterns
Expand Down Expand Up @@ -63,12 +65,55 @@ The **`options`** object can include several properties to customize the behavio
The **`createAtomStore`** function returns an object (**`AtomStoreApi`**) containing the following properties and methods for interacting with the store:
- **`use<Name>Store`**:
- A function that returns the following objects: **`get`**, **`set`**, **`use`** and **`store`**, where values are hooks for each state defined in the store.
- **`get`**: Hooks for accessing a state within a component, ensuring re-rendering when the state changes. See [useAtomValue](https://jotai.org/docs/core/use-atom#useatomvalue).
- **`set`**: Hooks for setting a state within a component. See [useSetAtom](https://jotai.org/docs/core/use-atom#usesetatom).
- **`use`**: Hooks for accessing and setting a state within a component, ensuring re-rendering when the state changes. See [useAtom](https://jotai.org/docs/core/use-atom).
- **`store`**: A hook to access the [JotaiStore](https://jotai.org/docs/core/store) for the current context.
- Example: `const [element, setElement] = useElementStore().use.element()`
- A function that returns the following objects: **`useValue`**, **`useSet`**, **`useState`**, where values are hooks for each state defined in the store, and **`get`**, **`set`**, **`subscribe`**, **`store`**, where values are direct get/set accessors to modify each state.
- **`useValue`**: Hooks for accessing a state within a component, ensuring re-rendering when the state changes. See [useAtomValue](https://jotai.org/docs/core/use-atom#useatomvalue).
``` js
const store = useElementStore();
const element = store.useElementValue();
// alternative
const element = useElementStore().useValue('element');
```
- **`useSet`**: Hooks for setting a state within a component. See [useSetAtom](https://jotai.org/docs/core/use-atom#usesetatom).
``` js
const store = useElementStore();
const element = store.useSetElement();
// alternative
const element = useElementStore().useSet('element');
```
- **`useState`**: Hooks for accessing and setting a state within a component, ensuring re-rendering when the state changes. See [useAtom](https://jotai.org/docs/core/use-atom).
``` js
const store = useElementStore();
const element = store.useElementState();
// alternative
const element = useElementStore().useState('element');
```
- **`get`**: Directly get the state. Not a hook so it could be used in event handlers or other hooks, and the component won't re-render if the state changes. See [createStore](https://jotai.org/docs/core/store#createstore)
``` js
const store = useElementStore();
useEffect(() => { console.log(store.getElement()) }, []);
// alternative
useEffect(() => { console.log(store.get('element')) }, []);
```
- **`set`**: Directly set the state. Not a hook so it could be used in event handlers or other hooks. See [createStore](https://jotai.org/docs/core/store#createstore)
``` js
const store = useElementStore();
useEffect(() => { store.setElement('div') }, []);
// alternative
useEffect(() => { store.set('element', 'div') }, []);
```
- **`subscribe`**: Subscribe to the state change. . See [createStore](https://jotai.org/docs/core/store#createstore)
- NOTE: The subscribed callback will fire whenever the atom state or dependent atom states change. There is no equality check.
``` js
const store = useElementStore();
useEffect(() => store.subscribeElement((newElement) => console.log(newElement)), []);
// alternative
useEffect(() => store.subscribe('element', (newElement) => console.log(newElement)), []);
```
- **`store`**: The [JotaiStore](https://jotai.org/docs/core/store) for the current context.
``` js
const store = useElementStore();
const jotaiStore = store.store;
```
- **`<Name>Provider`**:
- The API includes dynamically generated provider components for each defined store. This allows scoped state management within your application. More information in the next section.
- **`<name>Store`**:
Expand Down Expand Up @@ -103,20 +148,24 @@ const { useUserStore } = createAtomStore({
}),
});
const intro = useAppStore().get.intro();
const userStore = useUserStore();
const intro = userStore.useIntroValue();
```

#### Externally Defined Derived Atoms

Derived atoms can also be defined externally by accessing the store's atoms through the `<name>Store` API. Externally defined atoms can be accessed through the store using the special `use<Name>Store().{get,set,use}.atom` hooks.
Derived atoms can also be defined externally by accessing the store's atoms through the `<name>Store` API. Externally defined atoms can be accessed through the store using special hooks:
`useAtomValue`, `useSetAtom`, `useAtomState`, `getAtom`, `setAtom`, `subscribeAtom`.

```ts
const { userStore, useUserStore } = createAtomStore({
username: 'Alice',
}, { name: 'user' });
const introAtom = atom((get) => `My name is ${get(userStore.atom.username)}`);
const intro = useUserStore().get.atom(introAtom);
const userStore = useUserStore();
const intro = userStore.useAtomValue(introAtom);
```

### Example Usage
Expand Down Expand Up @@ -162,8 +211,20 @@ const App = () => {
};
const Component = () => {
const [name, setName] = useAppStore().use.name();
const onUpdateName = useAppStore().get.onUpdateName();
const appStore = useAppStore();
const [name, setName] = store.useNameState();
const onUpdateName = store.useOnUpdateNameValue();
useEffect(() => store.subscribe.name((newName) => {
console.log(`Name updated to: ${newName}`);
// An alternative to `appStore.useNameState()`, won't rerender when the state changes
assert.ok(newName === appStore.getName());
if (newName.includes('#')) {
// Equivalent to `appStore.useSetName()`'s return
appStore.setName('invalid');
onUpdateName('invalid');
}
}), [appStore])
return (
<div>
Expand Down Expand Up @@ -211,9 +272,11 @@ const App = () => {
// Accessing state from the specified scope.
const Component = () => {
// Here, we get the state from the parent scope
const [name, setName] = useAppStore('parent').use.name();
const parentAppStore = useAppStore('parent');
const [name, setName] = parentScope.useNameState();
// Here, we get the state from the closest scope (default)
const onUpdateName = useAppStore().get.onUpdateName();
const appStore = useAppStore();
const onUpdateName = appStore.useOnUpdateNameValue();
return (
<div>
Expand All @@ -224,6 +287,59 @@ const Component = () => {
};
```

## Migrate from v1 to v2

1. Return of `use<Name>Store`: `get` is renamed to `use<Key>Value`, `set` is renamed to `useSet<Key>`, `use` is renamed to `useState`.
``` diff
- const name = useAppStore().get.name();
- const setName = useAppStore().set.name();
- const [name, setName] = useAppStore().use.name();
+ const appStore = useAppStore();
+ const name = appStore.useNameValue();
+ const setName = appStore.useSetName();
+ const [name, setName] = appStore.useNameState();
+ // alternative
+ const name = appStore.useValue('name');
+ const setName = appStore.useSet('name');
+ const [name, setName] = appStore.useState('name');
```

2. Rename `.atom()` APIs:
``` diff
- const atomValue = useAppStore().get.atom(atomConfig);
- const setAtomValue = useAppStore().set.atom(atomConfig);
- const [atomValue, setAtomValue] = useAppStore().use.atom(atomConfig);
+ const appStore = useAppStore();
+ const atomValue = appStore.useAtomValue(atomConfig);
+ const setAtomValue = appStore.useSetAtom(atomConfig);
+ const [atomValue, setAtomValue] = appStore.useAtomState(atomConfig);
```
NOTE: Try to avoid using the key "atom" as the store state key because
1. `useValue('atom')` and `useSet('atom')` and `useState('atom')` are not supported. They are only valid if the key "atom" is presented in the store.
2. On the other hand, `useAtomValue()`, `useSetAtom()`, and `useAtomState()` cannot access the state if the key "atom" is presented in the store.

3. Return of `use<Name>Store`: `store` is no longer a function. Now it is a direct property.
``` diff
- const store = useAppStore().store();

+ const appStore = useAppStore();
+ const jotaiStore = appStore.store;
```
4. Return of `use<Name>Store`: `option` is no longer a valid parameter of `useValue` and `useSet`. To control the behavior, directly pass the options to `createAtomStore` or `use<Name>Store`.
``` diff
- const scope1Name = useAppStore().useValue.name(scope1Options);
- const scope2Name = useAppStore().useValue.name(scope2Options);

+ const scope1AppStore = useAppStore(scope1Options);
+ const scope1Name = scope1AppStore.useNameValue();
+ const scope2AppStore = useAppStore(scope2Options);
+ const scope2Name = scope2AppStore.useNameValue();
```
## Contributing
### Ideas and discussions
Expand Down
Loading

0 comments on commit 820af7a

Please sign in to comment.