diff --git a/README.md b/README.md index 80f8b9e..37d99e0 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ const Usage = () => { [Code Sandbox Example](https://codesandbox.io/s/usereduxstate-gdl7g) ### React Native Snack Example -[Snack Example](https://snack.expo.io/embed.js) +[Snack Example](https://snack.expo.io/@myckhel/use-redux-state-hook) ## API diff --git a/package.json b/package.json index 89f96e9..bec9a3d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "author": "myckhel", "license": "MIT", "repository": "myckhel/use-redux-state-hook", + "homepage": "https://myckhel.github.io/use-redux-state-hook", "main": "dist/index.js", "module": "dist/index.modern.js", "source": "src/index.js", diff --git a/src/hooks.js b/src/hooks.js index ead9f7b..6750291 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -88,7 +88,7 @@ export const useReduxState = (config, initState) => { const _selector = useCallback( (state) => { - const storeState = selector(state, name, getInit) + const storeState = selector(state, name) return storeState !== undefined ? storeState : getInit() }, [name] @@ -180,5 +180,4 @@ export const action = (name, payload, reducer) => ({ reducer }) -export const selector = (state, name, getInit = () => undefined) => - get(state?.[STATE_NAME], name) +export const selector = (state, name) => get(state?.[STATE_NAME], name) diff --git a/website/docs/apis/action.md b/website/docs/apis/action.md index 38db677..72e7977 100644 --- a/website/docs/apis/action.md +++ b/website/docs/apis/action.md @@ -1,36 +1,45 @@ --- id: action -title: action +title: action() --- -This creates redux action for a given state. +Action creator for dispatching setState action for a given state name. + +```ts +action(stateName, payload, setter?: (state, payload) => newState) ``` + +## `Returns` + +setState redux action + +```ts action(stateName, payload, setter) +// { type: SET_REDUX_STATE, payload, name, reducer } ``` -## Returns -Redux action - -## Example +## `Example` ```jsx -import {useEffect} from 'react' -import useReduxState, {action} from 'use-redux-state-hook' +import { useEffect } from 'react' +import useReduxState, { action } from 'use-redux-state-hook' const Component = () => { useReduxState({ state: { state1: 'a', - state2: 'b', + state2: 'b' }, name: 'component_state' }) useEffect(() => { - store.dispatch(action( - 'component_state', - {state2: 'c'}, - (component_state, payload) => ({...component_state, ...payload}) - )) + store.dispatch( + action( + 'component_state', + { state2: 'c' }, + (component_state, payload) => ({ ...component_state, ...payload }) + ) + ) // component_state {state1: 'a', state2: 'c'} }, []) } diff --git a/website/docs/apis/cleanup.md b/website/docs/apis/cleanup.md index 293352f..77d269c 100644 --- a/website/docs/apis/cleanup.md +++ b/website/docs/apis/cleanup.md @@ -1,4 +1,35 @@ --- id: cleanup -title: cleanup +title: cleanup() --- + +function that dispatches an action to delete the state from redux store. +useful when you dont want to persist the state when component unmounted. + +*function is provided by `use-redux-state-hook`* + +```js +cleanup() +``` + +## `Example` + +```jsx +import { useEffect } from 'react' +import useReduxState from 'use-redux-state-hook' +const Component = () => { + + const {cleanup} = useReduxState({ + state: { + state1: 'a', + state2: 'b' + }, + name: 'component_state' + }) + + useEffect(() => { + return () => cleanup() + /* component_state: undefined */ + }, []) +} +``` diff --git a/website/docs/apis/get-state.md b/website/docs/apis/get-state.md index c8addcd..d14e893 100644 --- a/website/docs/apis/get-state.md +++ b/website/docs/apis/get-state.md @@ -1,4 +1,44 @@ --- id: get-state -title: getState +title: getState() --- + +function to get the state for a given state name. + +## `Arguments` + +### **stateName** + +name of the nest-able state to be selected + +```js +getState(store, stateName, getter?: (state) => selectedState) +``` + +## `Returns` + +### **stateValue** + +```js +getState(store, 'todos.completed', (state) => state.todos) // any value +``` + +## `Example` + +```jsx +import { useEffect } from 'react' +import useReduxState, { getState } from 'use-redux-state-hook' +const Component = () => { + useReduxState({ + state: { + state1: [], + }, + name: 'todos.completed' + }) + + useEffect(() => { + console.log(getState(store, 'todos.completed', (state) => state.todos)) + // {state1: []} + }, []) +} +``` diff --git a/website/docs/apis/hooks/use-get-state.md b/website/docs/apis/hooks/use-get-state.md new file mode 100644 index 0000000..dcb40fb --- /dev/null +++ b/website/docs/apis/hooks/use-get-state.md @@ -0,0 +1,42 @@ +--- +id: use-get-state +title: useGetState() +--- + +This hook gives you a getState function for a given state + +## Arguments + +### `stateName` + +name of the nestable state to be selected + +## Returns + +### `getState()` + +```js +getState(stateName) +``` + +## Example + +```jsx +import { useEffect } from 'react' +import useReduxState, { useGetState } from 'use-redux-state-hook' +const Component = () => { + useReduxState({ + state: { + state1: 'a', + state2: 'b' + }, + name: 'component_state' + }) + const getState = useGetState('component_state') + + useEffect(() => { + console.log(getState((component_state) => component_state?.state2)) + // 'c' + }, []) +} +``` diff --git a/website/docs/apis/hooks/use-memo-selector.md b/website/docs/apis/hooks/use-memo-selector.md index 1a39aa8..e416583 100644 --- a/website/docs/apis/hooks/use-memo-selector.md +++ b/website/docs/apis/hooks/use-memo-selector.md @@ -1,45 +1,59 @@ --- id: use-memo-selector -title: useMemoSelector +title: useMemoSelector() --- This hook allows to select redux state efficiently and in a memoized way. it uses [createSelector](https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc) to make sure selector is not recomputed unless one of its arguments changes. -it also uses [react-fast-compare](https://gg.com) to prevent unnecessary updates. +it also uses [react-fast-compare](https://github.com/FormidableLabs/react-fast-compare) to prevent unnecessary updates. ## Arguments -### selector()||stateName'' -#### selector() + +### `selector()||stateName''` + +#### `selector()` + function that selects from the state. receives 1 argument + ``` (storeState) => selectedState ``` -#### stateName + +#### `stateName` + name of the nestable state to be selected -### result() +### `result()` + function that returns the result of the selected state + ``` (selectedState) => selectedState ``` -### equality() -function that compare prevState against newState to determine if selected state has changed. defaults to [react-fast-compare](https://gg.com) + +### `equality()` + +function that compare prevState against newState to determine if selected state has changed. defaults to [react-fast-compare](https://github.com/FormidableLabs/react-fast-compare) ## Example + ```jsx -import useReduxState, {useMemoSelector} from 'use-redux-state-hook' +import useReduxState, { useMemoSelector } from 'use-redux-state-hook' const Component = () => { - const {selector} = useReduxState({ + const { selector } = useReduxState({ state: { state1: 'a', - state2: 'b', + state2: 'b' }, name: 'component_state' }) - const state1 = useMemoSelector(selector, (component_state) => component_state?.state1) + const state1 = useMemoSelector( + selector, + (component_state) => component_state?.state1 + ) const state2 = useMemoSelector('component_state.state2') - console.log({state1, state2}) // {state1: 'a', state2: 'b'} + console.log({ state1, state2 }) // {state1: 'a', state2: 'b'} } ``` diff --git a/website/docs/apis/hooks/use-redux-state.md b/website/docs/apis/hooks/use-redux-state.md index c1aba61..11bfc22 100644 --- a/website/docs/apis/hooks/use-redux-state.md +++ b/website/docs/apis/hooks/use-redux-state.md @@ -1,90 +1,122 @@ --- id: use-redux-state -title: useReduxState +title: useReduxState() --- + This hook allows to create redux state at runtime. ```ts useReduxState(stateName?: string, state?: any) ``` + or -```js + +```ts useReduxState(config?: { - name?: 'string', // nested state name + name?: string, // nested state name state?: any, // initial state reducer?: (storeState, initialState) => mergedState }) ``` -#### `returns` -```js -{ - selector, setState, getState, action, cleanup, useMemoSelector -} -``` +### `returns{}` + +- [selector](../selector.md) +- [setState](../set-state.md) +- [getState](../get-state.md) +- [action](../action.md) +- [cleanup](../cleanup.md) +- [useMemoSelector](use-memo-selector) ## `config` -### name'' +### **name''** + +**type:** string +**default:** Date().getTime()
nestable key name of the redux state + ```js -useReduxState({name: 'state.name', state: 'Mike'}); +useReduxState({ name: 'state.name', state: 'Mike' }) // creates nested state in the store {state: {name: 'Mike'}} ``` -### state? +### **state?** + +**type:** any
initial state + ```js -useReduxState({name: 'state.name', state: 'Mike'}); +useReduxState({ name: 'state.name', state: 'Mike' }) // creates nested state in the store {state: {name: 'Mike'}} ``` -### reducer()? +### **reducer()?** + +**type:** function
function that takes the current `state` for the given name, `payload` and returns computed new state. this function runs once during the initialization of the state use the function when you want to manually handle how the state should be created/updated. ```js -useReduxState({name: 'state.name', state: {is: 'Mike'}}); +useReduxState({ name: 'state.name', state: { is: 'Mike' } }) // {state: {name: {is: 'Mike'}}} useReduxState({ name: 'state.name', - state: {and: 'Redux'}, - reducer: (currentState, payload) => ({...currentState, ...payload}) -}); + state: { and: 'Redux' }, + reducer: (currentState, payload) => ({ ...currentState, ...payload }) +}) ``` + in the above example reducer had prevented from over-writing the store state when there is an existing value for the state. instead of: + ```js -{state: {name: {and: 'Redux'}}} +{ + state: { + name: { + and: 'Redux' + } + } +} ``` + we got: + ```js {state: {name: {is: 'Mike', and: 'Redux'}}} ``` -### unmount?. +### **unmount?** + **type:** boolean **default:** false determines whether redux state should mount + ```js -useReduxState({unmount: true, name: 'state.name', state: 'Mike'}); +useReduxState({ unmount: true, name: 'state.name', state: 'Mike' }) // store undefined -useReduxState({unmount: false, name: 'state.name', state: 'Mike'}); +useReduxState({ unmount: false, name: 'state.name', state: 'Mike' }) // {state: {name: 'Mike'}} ``` -### cleanup?. +### **cleanup?** + **type:** boolean + determines whether redux state should cleanup the state when component unmounted from view. ```js -const {getState} = useReduxState({cleanup: true, name: 'state.name', state: 'Mike'}); +const { getState } = useReduxState({ + cleanup: true, + name: 'state.name', + state: 'Mike' +}) useEffect(() => { console.log(getState()) // {state: {name: 'Mike'}} @@ -93,12 +125,47 @@ useEffect(() => { ``` ## `Example` -```js -const {selector, setState} = useReduxState({ - name: 'state.name', - state: { - count: 1, - locale: 'en_US' - } -}); + +```jsx +import React, { useEffect } from 'react' +import { View, StyleSheet, Button, Alert } from 'react-native' +import useReduxState from 'use-redux-state-hook' + +const App = () => { + const { + selector, + useMemoSelector, + getState, + cleanup, + setState + } = useReduxState({ + /* nestable state name */ + name: 'state.name', + /* initial component state */ + state: { + count: 1, + locale: 'en_US' + }, + /* if state !exists overwrite with payload */ + reducer: (state, payload) => state || payload, + /* whether state should be mounted */ + unmount: false + }) + + const { count, locale } = useMemoSelector(selector, (state) => state) + + useEffect(() => { + /* delete the state when component unmount from tree */ + return () => cleanup() + }, []) + + return ( + + ) +} +export default App ``` diff --git a/website/docs/apis/hooks/use-set-state.md b/website/docs/apis/hooks/use-set-state.md index cb31257..8f85cf4 100644 --- a/website/docs/apis/hooks/use-set-state.md +++ b/website/docs/apis/hooks/use-set-state.md @@ -1,37 +1,51 @@ --- id: use-set-state -title: useSetState +title: useSetState() --- This hook gives you a setState function for a given state ## Arguments -### stateName + +### `stateName` + name of the nestable state to be selected -## Returns -### setState() ```js -setState(payload, setter) +useSetState('todos.completed') +``` + +## `Returns()` + +### **setState()** + +```ts +setState(payload, setter?: (state, payload) => newState) ``` ## Example + ```jsx -import {useEffect} from 'react' -import useReduxState, {useSetState} from 'use-redux-state-hook' +import { useEffect } from 'react' +import useReduxState, { useSetState } from 'use-redux-state-hook' + const Component = () => { - const {selector} = useReduxState({ + useReduxState({ state: { state1: 'a', - state2: 'b', + state2: 'b' }, name: 'component_state' }) + const setState = useSetState('component_state') useEffect(() => { - setState({state2: 'c'}, (component_state, payload) => ({...component_state, ...payload})) - // component_state {state1: 'a', state2: 'c'} + setState({ state2: 'c' }, (component_state, payload) => ({ + ...component_state, + ...payload + })) + // component_state {state1: 'a', state2: 'c'} }, []) } ``` diff --git a/website/docs/apis/selector.md b/website/docs/apis/selector.md index 9424268..c208cb5 100644 --- a/website/docs/apis/selector.md +++ b/website/docs/apis/selector.md @@ -1,4 +1,36 @@ --- id: selector -title: selector +title: selector() --- + +function to select state for a given state name. +```js +selector(storeRootState, stateName) +``` + +**Returns** Redux state for the given name + +## `Example` + +```jsx +import {useEffect} from 'react' +import useReduxState, {selector} from 'use-redux-state-hook' + +const Component = () => { + useReduxState({ + state: { + state1: 'a', + state2: 'b', + }, + name: 'component_state' + }) + + useEffect(() => { + selector( + store.getState(), + 'component_state', + ) + // component_state {state1: 'a', state2: 'b'} + }, []) +} +``` diff --git a/website/docs/apis/set-config.md b/website/docs/apis/set-config.md index e097342..dba2361 100644 --- a/website/docs/apis/set-config.md +++ b/website/docs/apis/set-config.md @@ -1,8 +1,36 @@ --- id: set-config -title: setConfig +title: setConfig() --- -## `cleanup` +function to configure the `use-redux-state-hook`. -## `setter` +## `config objects` + +### **cleanup.** + +boolean value to determine if the state should be deleted when all component that subscribed to a state name have been unmounted from react tree. +**_this is experimental_** + +### **setter()?** + +function to replace the initial `use-redux-state-hook` setter function. + +## `Example` + +```js +import { setConfig } from 'use-redux-state-hook' +setConfig({ + cleanup: false, + setter: (stateValue, payload) => { + switch (stateValue?.constructor) { + case Object: + return { ...stateValue, ...payload } + case Array: + return [...stateValue, ...payload] + default: + return payload + } + } +}) +``` diff --git a/website/docs/apis/set-state.md b/website/docs/apis/set-state.md index fe3a389..eb39007 100644 --- a/website/docs/apis/set-state.md +++ b/website/docs/apis/set-state.md @@ -1,4 +1,53 @@ --- id: set-state -title: setState +title: setState() --- + +function to dispatch set state action. + +## `Arguments` + +### **storeDispatcher()** + +redux store's dispatch function. + +### **action()** + +a state's set state action creator. + +### **payload** + +value to set in the state. + +### **setter()?** + +function to determine how the state should be set. + +```js +setState(store.dispatch, action, payload, setter?: (state, payload) => newState) +``` + +## `Example` + +```jsx +import { useEffect } from 'react' +import useReduxState, { setState } from 'use-redux-state-hook' +const Component = () => { + const { action } = useReduxState({ + state: { + state1: [] + }, + name: 'todos.completed' + }) + + useEffect(() => { + console.log( + setState(store.dispatch, action, [1], (state, payload) => [ + ...state, + payload + ]) + ) + // {state1: [1]} + }, []) +} +``` diff --git a/website/docs/guides/state.md b/website/docs/guides/state.md deleted file mode 100644 index 04592bb..0000000 --- a/website/docs/guides/state.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -id: state -title: state ---- diff --git a/website/docs/guides/using-redux-state.md b/website/docs/guides/using-redux-state.md new file mode 100644 index 0000000..c966f96 --- /dev/null +++ b/website/docs/guides/using-redux-state.md @@ -0,0 +1,4 @@ +--- +id: using-redux-state +title: state +--- diff --git a/website/docs/install.md b/website/docs/install.md index 41baec7..2d9a2f6 100644 --- a/website/docs/install.md +++ b/website/docs/install.md @@ -8,7 +8,7 @@ slug: /install import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -## Install +## `Install` ```bash - yarn add use-redux-state-hook redux react-redux @reduxjs/toolkit + yarn add use-redux-state-hook@beta ``` @@ -25,7 +25,30 @@ import TabItem from '@theme/TabItem'; ```bash - npm install --save use-redux-state-hook redux react-redux @reduxjs/toolkit + npm install --save use-redux-state-hook@beta + ``` + + + + +## `Install with peerDependencies` + + + + + ```bash + yarn add use-redux-state-hook@beta redux react-redux @reduxjs/toolkit + ``` + + + + + + ```bash + npm install --save use-redux-state-hook@beta redux react-redux @reduxjs/toolkit ``` diff --git a/website/docs/intro.md b/website/docs/intro.md index 28cf63a..8db0765 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -5,8 +5,60 @@ sidebar_label: Introduction slug: / --- -## Overview +## `Overview` `use-redux-state-hook` allows you to create redux states at runtime for your react components without creating static actions and reducers. -It was also designed to solve react's unnecessary re-render issue by using `useMemoSelector` api. +It was also designed to solve react's unnecessary re-render issue by using `useMemoSelector` api and to improve your app's performance. -`use-redux-state-hook` was also created to improve your app's performance. +## `Problems solved` +### **handles unnecessary selectors re-rendering** +`use-redux-state-hook` makes sure selectors doesn't re-render component when state has no changes. +### **reduces redux code boilerplate** +Say bye to redux action/reducers boilerplates!
+with redux, before you create states you have to at least define the store in advance in a similar way to example below. + +## `Example` +```js +// reducer.js +const INIT_STATE = { + state1: 'one', + state2: 'two', +}; + +const reducer = (state = INIT_STATE, {type, payload}) => { + switch (type) { + case 'addState1': + return {...state, state1: 'ones'} + default: + case 'addState2': + return {...state, state2: 'twos'} + default: + return state; + } +}; +export default reducer; +``` +Sometimes you would need to move your small react components states to redux store, so for every components state you would have to define them in advance.
+This can be redundant and unnecessary.
+But `use-redux-state-hook` found a way to create redux state much easier and dynamically without writing redundant codes.
+`use-redux-state-hook` can make you have similar states above with few lines of codes. +```jsx +const Component = () => { + const {setState, getState} = useReduxState({ + name: 'component_state', + state: { + state1: 'one', + state2: 'two', + } + }); + + useEffect(() => { + console.log(getState()) // {state1: 'one', state2: 'two'} + + setState({ + state1: 'ones', + state2: 'twos', + }); + console.log(getState()) // {state1: 'ones', state2: 'twos'} + }, []) +} +``` diff --git a/website/docs/usage.md b/website/docs/usage.md index 28536df..bf121a3 100644 --- a/website/docs/usage.md +++ b/website/docs/usage.md @@ -5,7 +5,7 @@ sidebar_label: Usage slug: /usage --- -## Setup +## `Setup` ```js import { configureStore } from '@reduxjs/toolkit' @@ -32,7 +32,7 @@ const store = configureStore({ setConfig({cleanup: false}) ``` -## Basic Usage +## `Basic Usage` ```jsx import React, { Component } from 'react' @@ -52,9 +52,7 @@ const Usage = () => {
Current Count: {count}
- setState((prevState) => ({ ...prevState, locale })) - } + onChange={({ target: { value: locale } }) => setState({locale})} value={locale} />