Skip to content

Commit

Permalink
feat: jotai/redux (pmndrs#421)
Browse files Browse the repository at this point in the history
* feat: jotai/redux

* add build script

* add example

* Update docs/api/redux.md

Co-authored-by: Mathis Møller <[email protected]>

Co-authored-by: Mathis Møller <[email protected]>
  • Loading branch information
dai-shi and Mathis Møller authored Apr 16, 2021
1 parent c84d3ef commit 44ffdbe
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 283 deletions.
88 changes: 58 additions & 30 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
{
"devtools.js": {
"bundled": 1989,
"minified": 1051,
"gzipped": 594,
"redux.js": {
"bundled": 364,
"minified": 193,
"gzipped": 154,
"treeshaked": {
"rollup": {
"code": 28,
"import_statements": 28
"code": 14,
"import_statements": 14
},
"webpack": {
"code": 1045
"code": 998
}
}
},
"optics.js": {
"bundled": 1645,
"minified": 805,
"gzipped": 422,
"valtio.js": {
"bundled": 1111,
"minified": 555,
"gzipped": 320,
"treeshaked": {
"rollup": {
"code": 32,
"import_statements": 32
"code": 37,
"import_statements": 37
},
"webpack": {
"code": 1061
"code": 1054
}
}
},
"zustand.js": {
"bundled": 455,
"minified": 235,
"gzipped": 176,
"treeshaked": {
"rollup": {
"code": 14,
"import_statements": 14
},
"webpack": {
"code": 998
}
}
},
"devtools.js": {
"bundled": 1989,
"minified": 1051,
"gzipped": 594,
"treeshaked": {
"rollup": {
"code": 28,
"import_statements": 28
},
"webpack": {
"code": 1045
}
}
},
Expand All @@ -41,17 +69,17 @@
}
}
},
"valtio.js": {
"bundled": 1111,
"minified": 555,
"gzipped": 320,
"optics.js": {
"bundled": 1613,
"minified": 811,
"gzipped": 426,
"treeshaked": {
"rollup": {
"code": 37,
"import_statements": 37
"code": 32,
"import_statements": 32
},
"webpack": {
"code": 1054
"code": 1061
}
}
},
Expand All @@ -70,9 +98,9 @@
}
},
"xstate.js": {
"bundled": 2799,
"minified": 1268,
"gzipped": 629,
"bundled": 2812,
"minified": 1279,
"gzipped": 634,
"treeshaked": {
"rollup": {
"code": 29,
Expand All @@ -84,9 +112,9 @@
}
},
"utils.js": {
"bundled": 10423,
"minified": 5036,
"gzipped": 1949,
"bundled": 10432,
"minified": 5045,
"gzipped": 1953,
"treeshaked": {
"rollup": {
"code": 28,
Expand All @@ -98,9 +126,9 @@
}
},
"index.js": {
"bundled": 19924,
"minified": 9413,
"gzipped": 3031,
"bundled": 19939,
"minified": 9428,
"gzipped": 3032,
"treeshaked": {
"rollup": {
"code": 14,
Expand Down
52 changes: 52 additions & 0 deletions docs/api/redux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
This doc describes `jotai/redux` bundle.

Jotai's state resides in React, but sometimes it would be nice
to intract with the world outside React.
Redux provides a store interface that can be used to store some values
and sync with atoms in jotai.

## Install

You have to install `redux` to access this bundle and its functions.

```
npm install redux
# or
yarn add redux
```

## atomWithStore

`atomWithStore` creates a new atom with redux store.
It's two-way binding and you can change the value from both ends.

```js
import { useAtom } from 'jotai'
import { atomWithStore } from 'jotai/redux'
import { createStore } from 'redux'

const initialState = { count: 0 }
const reducer = (state = initialState, action: { type: 'INC' }) => {
if (action.type === 'INC') {
return { ...state, count: state.count + 1 }
}
return state
}
const store = createStore(reducer)
const storeAtom = atomWithStore(store)

const Counter: React.FC = () => {
const [state, dispatch] = useAtom(storeAtom)

return (
<>
count: {state.count}
<button onClick={() => dispatch({ type: 'INC' })}>button</button>
</>
)
}
```

### Examples

https://codesandbox.io/s/react-typescript-forked-cmlu5
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
"types": "./zustand.d.ts",
"module": "./esm/zustand.js",
"default": "./zustand.js"
},
"./redux": {
"types": "./redux.d.ts",
"module": "./esm/redux.js",
"default": "./redux.js"
}
},
"files": [
Expand All @@ -80,6 +85,7 @@
"build:xstate": "rollup -c --config-xstate",
"build:valtio": "rollup -c --config-valtio",
"build:zustand": "rollup -c --config-zustand",
"build:redux": "rollup -c --config-redux",
"postbuild": "yarn copy",
"eslint": "eslint --fix '{src,tests}/**/*.{js,ts,jsx,tsx}'",
"eslint:ci": "eslint '{src,tests}/**/*.{js,ts,jsx,tsx}'",
Expand Down Expand Up @@ -187,6 +193,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-query": "^3.13.2",
"redux": "^4.0.5",
"rollup": "^2.44.0",
"rollup-plugin-esbuild": "^3.0.2",
"rollup-plugin-size-snapshot": "^0.12.0",
Expand Down
1 change: 1 addition & 0 deletions src/redux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { atomWithStore } from './redux/atomWithStore'
20 changes: 20 additions & 0 deletions src/redux/atomWithStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Store, Action, AnyAction } from 'redux'
import { atom } from 'jotai'
import type { NonFunction } from '../core/types'

export function atomWithStore<State, A extends Action = AnyAction>(
store: Store<State, A>
) {
const baseAtom = atom(store.getState() as NonFunction<State>)
baseAtom.onMount = (setValue) =>
store.subscribe(() => {
setValue(store.getState())
})
const derivedAtom = atom(
(get) => get(baseAtom),
(_get, _set, action: A) => {
store.dispatch(action)
}
)
return derivedAtom
}
45 changes: 45 additions & 0 deletions tests/redux/atomWithStore.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react'
import { fireEvent, render, act } from '@testing-library/react'
import { createStore } from 'redux'
import { Provider, useAtom } from '../../src/index'
import { atomWithStore } from '../../src/redux'

it('count state', async () => {
const initialState = { count: 0 }
const reducer = (state = initialState, action: { type: 'INC' }) => {
if (action.type === 'INC') {
return { ...state, count: state.count + 1 }
}
return state
}
const store = createStore(reducer)
const storeAtom = atomWithStore(store)
const Counter: React.FC = () => {
const [state, dispatch] = useAtom(storeAtom)

return (
<>
count: {state.count}
<button onClick={() => dispatch({ type: 'INC' })}>button</button>
</>
)
}

const { findByText, getByText } = render(
<Provider>
<Counter />
</Provider>
)

await findByText('count: 0')

fireEvent.click(getByText('button'))
await findByText('count: 1')
expect(store.getState().count).toBe(1)

act(() => {
store.dispatch({ type: 'INC' })
})
await findByText('count: 2')
expect(store.getState().count).toBe(2)
})
Loading

0 comments on commit 44ffdbe

Please sign in to comment.