From 020686c7e1c3c58352f7ba4a621538c98f59145a Mon Sep 17 00:00:00 2001 From: alvinhui Date: Mon, 24 Feb 2020 16:34:31 +0800 Subject: [PATCH 01/14] chore: version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e361005b..92a80f10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ice/store", - "version": "1.0.1", + "version": "1.1.0", "description": "Lightweight React state management library based on react hooks", "main": "lib/index.js", "types": "lib/index.d.ts", From 96db032f20d55ec11600fd01074b7bb00346325d Mon Sep 17 00:00:00 2001 From: alvinhui Date: Wed, 26 Feb 2020 15:05:30 +0800 Subject: [PATCH 02/14] chore: typo --- docs/api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api.md b/docs/api.md index 61a15e7d..b3ed346f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -184,10 +184,10 @@ const counter = { }, }; -const { userModel } = createStore({ counter }); +const { useModel } = createStore({ counter }); function FunctionComponent() { - const [ state, actions ] = userModel('name'); + const [ state, actions ] = useModel('counter'); state.value; // 0 @@ -203,7 +203,7 @@ A hook granting your components access to the model actions. ```js function FunctionComponent() { - const actions = useModelActions('name'); + const actions = useModelActions('counter'); actions.add(1); } ``` @@ -216,8 +216,8 @@ A hook granting your components access to the action state of the model. ```js function FunctionComponent() { - const actions = useModelActions('foo'); - const actionsState = useModelActionsState('foo'); + const actions = useModelActions('counter'); + const actionsState = useModelActionsState('counter'); useEffect(() => { actions.fetch(); From ccbb35ee8fb428c96681437b81916702ed1887a4 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Tue, 3 Mar 2020 08:36:28 +0800 Subject: [PATCH 03/14] docs: more comparison --- docs/recipes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/recipes.md b/docs/recipes.md index 54e04438..be34775e 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -189,18 +189,20 @@ If the project is relatively large, or more likely to follow the page maintenanc | | constate | zustand | react-tracked | rematch | icestore | | --------| -------- | -------- | -------- | -------- | -------- | +| Framework | React | React | React | Any,None | React | | Simplicity | ★★★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ | | Readability | ★★★ | ★★★ | ★★★ | ★★★ | ★★★★ | | Configurable | ★★★ | ★★★ | ★★★ | ★★★★★ | ★★★ | | Less boilerplate | ★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ | | Async Action | + | O | O | O | O | | Class Component | + | + | + | O | O | -| Hooks Component | O | O | O | O | O | +| Function Component | O | O | O | O | O | | Async Status | X | X | X | O | O | | Centralization | X | X | X | O | O | | Model interaction | + | + | + | O | O | | SSR | O | X | O | O | O | | Lazy load models | + | + | + | O | O | +| Persist | X | X | X | O | X | | Middleware or Plug-in | X | O | X | O | X | | Devtools | X | O | X | O | X | \ No newline at end of file From c61698048dbabb4106e7e6bb6a8a85c81e0816a5 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Wed, 4 Mar 2020 15:50:04 +0800 Subject: [PATCH 04/14] docs: comparison --- docs/recipes.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index be34775e..a04227d3 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -191,18 +191,17 @@ If the project is relatively large, or more likely to follow the page maintenanc | --------| -------- | -------- | -------- | -------- | -------- | | Framework | React | React | React | Any,None | React | | Simplicity | ★★★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ | -| Readability | ★★★ | ★★★ | ★★★ | ★★★ | ★★★★ | -| Configurable | ★★★ | ★★★ | ★★★ | ★★★★★ | ★★★ | | Less boilerplate | ★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ | -| Async Action | + | O | O | O | O | +| Configurable | ★★★ | ★★★ | ★★★ | ★★★★★ | ★★★ | +| Shareable State | O | O | O | O | O | +| Reusable State | O | O | O | O | O | +| Interactive State | + | + | + | O | O | | Class Component | + | + | + | O | O | | Function Component | O | O | O | O | O | | Async Status | X | X | X | O | O | -| Centralization | X | X | X | O | O | -| Model interaction | + | + | + | O | O | | SSR | O | X | O | O | O | -| Lazy load models | + | + | + | O | O | | Persist | X | X | X | O | X | +| Lazy load models | + | + | + | O | O | +| Centralization | X | X | X | O | O | | Middleware or Plug-in | X | O | X | O | X | | Devtools | X | O | X | O | X | - \ No newline at end of file From d0b7a42f2e6425bbd971c5dcedd624459685d909 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Wed, 4 Mar 2020 18:43:26 +0800 Subject: [PATCH 05/14] feat: effects && reducers --- examples/counter/src/index.tsx | 15 +++++++++------ src/createModel.tsx | 33 +++++++++++++++++++++++++-------- src/types.ts | 2 ++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx index d97cf8d2..f0a77a2d 100644 --- a/examples/counter/src/index.tsx +++ b/examples/counter/src/index.tsx @@ -7,13 +7,16 @@ const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), tim // 1️⃣ Use a model to define your store const counter = { state: 0, - actions: { + reducers: { increment:(prevState) => prevState + 1, - async decrement(prevState) { + decrement:(prevState) => prevState - 1, + }, + effects: { + async decrementAsync(state, playload, actions) { await delay(1000); - return prevState - 1; + actions.decrement(); }, - }, + } }; const models = { @@ -27,12 +30,12 @@ const store = createStore(models); const { useModel } = store; function Counter() { const [ count, actions ] = useModel('counter'); - const { increment, decrement } = actions; + const { increment, decrementAsync } = actions as any; return (
{count} - +
); } diff --git a/src/createModel.tsx b/src/createModel.tsx index fe791db8..99a12b58 100644 --- a/src/createModel.tsx +++ b/src/createModel.tsx @@ -31,7 +31,13 @@ export function createModel(config: C, namespace?: type SetModelFunctionsState = SetFunctionsState; type IModelValue = ModelValue; - const { state: defineState = {}, actions: defineActions = [] } = config; + const { + state: defineState = {}, + actions: defineActions = {}, + effects: defineEffects = {}, + reducers = {}, + } = config; + const effects = { ...defineActions, ...defineEffects }; let actions; function useFunctionsState(functions: IModelConfigActionsKey[]): @@ -61,9 +67,9 @@ export function createModel(config: C, namespace?: function useActions(state: IModelState, setState: ReactSetState): [ ActionsPayload, (name: IModelConfigActionsKey, payload: any) => void, IModelActionsState ] { - const [ actionsState, , setActionsState ] = useFunctionsState(Object.keys(defineActions)); + const [ actionsState, , setActionsState ] = useFunctionsState(Object.keys(effects)); const [ actionsInitialPayload, actionsInitialIdentifier ]: [ActionsPayload, ActionsIdentifier] = useMemo( - () => transform(defineActions, (result, action, name) => { + () => transform(effects, (result, action, name) => { const state = { payload: null, identifier: 0, @@ -98,14 +104,17 @@ export function createModel(config: C, namespace?: [name]: identifier, }; (async () => { - const nextState = defineActions[name](state, payload, actions, modelsActions); + const nextState = effects[name](state, payload, actions, modelsActions); if (isPromise(nextState)) { setActionsState(name, { isLoading: true, error: null, }); try { - setState(await nextState); + const result = await nextState; + if (defineActions[name]) { + setState(result); + } setActionsState(name, { isLoading: false, error: null, @@ -132,9 +141,17 @@ export function createModel(config: C, namespace?: const [ state, setState ] = useState(preloadedState); const [ , executeAction, actionsState ] = useActions(state, setState); - actions = useMemo(() => transform(defineActions, (result, fn, name) => { - result[name] = (payload) => executeAction(name, payload); - }), [defineActions]); + actions = useMemo(() => { + const setEffects = transform(effects, (result, fn, name) => { + result[name] = (payload) => executeAction(name, payload); + }); + + const setReducers = transform(reducers, (result, fn, name) => { + result[name] = (payload) => setState((prevState) => fn(prevState, payload)); + }); + + return { ...setEffects, ...setReducers }; + }, [effects, reducers]); if (namespace && modelsActions) { modelsActions[namespace] = actions; diff --git a/src/types.ts b/src/types.ts index f097b631..d4e22b4d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -68,6 +68,8 @@ export interface ConfigActions { export interface Config { state: S; actions?: ConfigActions; + effects?: any; + reducers?: any; }; export interface Configs { From ea023d96c040c1673912c68d968c12f48dd20215 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Wed, 4 Mar 2020 22:31:21 +0800 Subject: [PATCH 06/14] refactor: types && demo --- examples/counter/src/index.tsx | 2 +- examples/todos/src/components/TodoList.tsx | 10 +- .../todos/src/components/TodoListClass.tsx | 16 ++-- examples/todos/src/components/Todos.tsx | 6 +- examples/todos/src/models/todos.ts | 58 ++++++----- examples/todos/src/models/user.ts | 23 +++-- package.json | 3 +- src/createModel.tsx | 96 ++++++++++--------- src/createStore.tsx | 22 ++--- src/types.ts | 45 ++++++--- 10 files changed, 157 insertions(+), 124 deletions(-) diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx index f0a77a2d..c76e1b6a 100644 --- a/examples/counter/src/index.tsx +++ b/examples/counter/src/index.tsx @@ -30,7 +30,7 @@ const store = createStore(models); const { useModel } = store; function Counter() { const [ count, actions ] = useModel('counter'); - const { increment, decrementAsync } = actions as any; + const { increment, decrementAsync } = actions; return (
{count} diff --git a/examples/todos/src/components/TodoList.tsx b/examples/todos/src/components/TodoList.tsx index 8b2f9afd..58d2dbd5 100644 --- a/examples/todos/src/components/TodoList.tsx +++ b/examples/todos/src/components/TodoList.tsx @@ -1,9 +1,9 @@ import React from 'react'; import store from '../store'; -const { useModel, useModelActionsState } = store; +const { useModel, useModelEffectsState } = store; -export function TodoList({ state, actions, actionsState }) { +export function TodoList({ state, actions, effectsState }) { const { title, subTitle, dataSource } = state; const { toggle, remove } = actions; @@ -25,7 +25,7 @@ export function TodoList({ state, actions, actionsState }) { {done ? {name} : {name}} { - actionsState.remove.isLoading ? + effectsState.remove.isLoading ? '...deleting...' : } @@ -38,12 +38,12 @@ export function TodoList({ state, actions, actionsState }) { export default function({ title }) { const [ state, actions ] = useModel('todos'); - const actionsState = useModelActionsState('todos'); + const effectsState = useModelEffectsState('todos'); return TodoList( { state: { ...state, title, subTitle: 'Function Component' }, actions, - actionsState, + effectsState, }, ); } diff --git a/examples/todos/src/components/TodoListClass.tsx b/examples/todos/src/components/TodoListClass.tsx index ff023d2a..6839f547 100644 --- a/examples/todos/src/components/TodoListClass.tsx +++ b/examples/todos/src/components/TodoListClass.tsx @@ -1,26 +1,26 @@ import { Component } from 'react'; import { Assign } from 'utility-types'; -import { UseModelValue, ModelActionsState } from '@ice/store'; +import { UseModelValue, ModelEffectsState } from '@ice/store'; // import compose from 'lodash/fp/compose'; import store from '../store'; import { TodoList as TodoListFn } from './TodoList'; import todosModel from '../models/todos'; -const { withModel, withModelActionsState } = store; +const { withModel, withModelEffectsState } = store; interface MapModelToProp { todos: UseModelValue; } -interface MapModelActionsStateToProp { - todosActionsState: ModelActionsState; +interface MapModelEffectsStateToProp { + todosActionsState: ModelEffectsState; } interface CustomProp { title: string; } -type PropsWithModel = Assign; +type PropsWithModel = Assign; type Props = Assign; class TodoList extends Component { @@ -41,14 +41,14 @@ class TodoList extends Component { return TodoListFn({ state: { title, dataSource, subTitle: 'Class Component' }, actions: { toggle: this.onToggle, remove: this.onRemove }, - actionsState: todosActionsState, + effectsState: todosActionsState, }); } } -export default withModelActionsState('todos')( +export default withModelEffectsState('todos')( withModel('todos')(TodoList), ); // functional flavor: -// export default compose(withModelActionsState('todos'), withModel('todos'))(TodoList); +// export default compose(withModelEffectsState('todos'), withModel('todos'))(TodoList); diff --git a/examples/todos/src/components/Todos.tsx b/examples/todos/src/components/Todos.tsx index 6ab786a6..3c250270 100644 --- a/examples/todos/src/components/Todos.tsx +++ b/examples/todos/src/components/Todos.tsx @@ -3,12 +3,12 @@ import store from '../store'; // import TodoList from './TodoListClass'; import TodoList from './TodoList'; -const { useModel, useModelActionsState } = store; +const { useModel, useModelEffectsState } = store; export default function Todos() { const todos = useModel('todos'); const [ state, actions ] = todos; - const actionsState = useModelActionsState('todos'); + const effectsState = useModelEffectsState('todos'); const { dataSource } = state; const { refresh } = actions; @@ -21,5 +21,5 @@ export default function Todos() { const taskView = dataSource.length ? : noTaskView; console.debug('Todos rending... '); - return actionsState.refresh.isLoading? loadingView : taskView; + return effectsState.refresh.isLoading? loadingView : taskView; } diff --git a/examples/todos/src/models/todos.ts b/examples/todos/src/models/todos.ts index 1c2018a0..bdb2fd63 100644 --- a/examples/todos/src/models/todos.ts +++ b/examples/todos/src/models/todos.ts @@ -18,9 +18,32 @@ const todos = { }, ], }, - - actions: { - async refresh(prevState: TodosState, args, actions, globalActions) { + reducers: { + toggle(prevState: TodosState, index: number) { + const dataSource = [].concat(prevState.dataSource); + dataSource[index].done = !prevState.dataSource[index].done; + return { + ...prevState, + dataSource, + }; + }, + setState(prevState: TodosState, payload) { + return { + ...prevState, + ...payload, + }; + } + }, + effects: { + add(state: TodosState, todo: Todo, actions, globalActions) { + const dataSource = [].concat(state.dataSource); + dataSource.push(todo); + globalActions.user.setTodos(dataSource.length); + actions.setState({ + dataSource + }); + }, + async refresh(state: TodosState, args, actions, globalActions) { await delay(2000); const dataSource: any[] = [ @@ -36,32 +59,17 @@ const todos = { }, ]; globalActions.user.setTodos(dataSource.length); - return { - ...prevState, + actions.setState({ dataSource, - }; - }, - toggle(prevState: TodosState, index: number) { - prevState.dataSource[index].done = !prevState.dataSource[index].done; - return { - ...prevState, - }; - }, - add(prevState: TodosState, todo: Todo, actions, globalActions) { - prevState.dataSource.push(todo); - globalActions.user.setTodos(prevState.dataSource.length); - return { - ...prevState, - }; + }); }, - async remove(prevState: TodosState, index: number, actions, globalActions) { + async remove(state: TodosState, index: number, actions, globalActions) { await delay(1000); + const dataSource = [].concat(state.dataSource); + dataSource.splice(index, 1); - prevState.dataSource.splice(index, 1); - globalActions.user.setTodos(prevState.dataSource.length); - return { - ...prevState, - }; + globalActions.user.setTodos(dataSource.length); + actions.setState(state); }, }, }; diff --git a/examples/todos/src/models/user.ts b/examples/todos/src/models/user.ts index a5abe374..ccce3755 100644 --- a/examples/todos/src/models/user.ts +++ b/examples/todos/src/models/user.ts @@ -8,22 +8,29 @@ const user = { todos: 0, auth: false, }, - actions: { - async login(prevState) { + reducers: { + setTodos(prevState, todos: number) { + return { ...prevState, todos }; + }, + setState(prevState, payload) { + return { + ...prevState, + ...payload, + } + }, + }, + effects: { + async login(prevState, payload, actions) { await delay(1000); const dataSource = { name: 'Alvin', }; const auth = true; - return { - ...prevState, + actions.setState({ dataSource, auth, - }; - }, - setTodos(prevState, todos: number) { - return { ...prevState, todos }; + }); }, }, }; diff --git a/package.json b/package.json index 92a80f10..3dd17039 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "react": "^16.8.0", "react-dom": "^16.8.0", "ts-jest": "^24.0.2", - "typescript": "^3.7.4" + "typescript": "^3.7.4", + "utility-types": "^3.10.0" }, "peerDependencies": { "react": "^16.8.0" diff --git a/src/createModel.tsx b/src/createModel.tsx index 99a12b58..9ab36d0b 100644 --- a/src/createModel.tsx +++ b/src/createModel.tsx @@ -7,15 +7,15 @@ import { ReactSetState, ModelProps, Config, - ActionsPayload, - SetActionsPayload, - ActionsIdentifier, + EffectsPayload, + SetEffectsPayload, + EffectsIdentifier, FunctionState, Model, ConfigPropTypeState, - ConfigPropTypeActions, - ModelActions, - ModelActionsState, + ConfigMergedEffects, + ModelEffects, + ModelEffectsState, SetFunctionsState, ModelValue, } from './types'; @@ -24,25 +24,26 @@ const isDev = process.env.NODE_ENV !== 'production'; export function createModel(config: C, namespace?: K, modelsActions?): Model { type IModelState = ConfigPropTypeState; - type IModelConfigActions = ConfigPropTypeActions; - type IModelConfigActionsKey = keyof IModelConfigActions; - type IModelActions = ModelActions; - type IModelActionsState = ModelActionsState; - type SetModelFunctionsState = SetFunctionsState; + type IModelConfigMergedEffects = ConfigMergedEffects; + type IModelConfigMergedEffectsKey = keyof IModelConfigMergedEffects; + type IModelEffects = ModelEffects; + type IModelEffectsState = ModelEffectsState; + type SetModelFunctionsState = SetFunctionsState; type IModelValue = ModelValue; + type IModelConfigMergedEffectsKeys = IModelConfigMergedEffectsKey[]; const { state: defineState = {}, actions: defineActions = {}, - effects: defineEffects = {}, + effects = {}, reducers = {}, } = config; - const effects = { ...defineActions, ...defineEffects }; + const mergedEffects = { ...defineActions, ...effects } as IModelConfigMergedEffects; let actions; - function useFunctionsState(functions: IModelConfigActionsKey[]): - [ IModelActionsState, SetModelFunctionsState, (name: IModelConfigActionsKey, args: FunctionState) => void ] { - const functionsInitialState = useMemo( + function useFunctionsState(functions: IModelConfigMergedEffectsKeys): + [ IModelEffectsState, SetModelFunctionsState, (name: IModelConfigMergedEffectsKey, args: FunctionState) => void ] { + const functionsInitialState = useMemo( () => transform(functions, (result, name) => { result[name] = { isLoading: false, @@ -51,9 +52,9 @@ export function createModel(config: C, namespace?: }, {}), [functions], ); - const [ functionsState, setFunctionsState ] = useState(() => functionsInitialState); + const [ functionsState, setFunctionsState ] = useState(() => functionsInitialState); const setFunctionState = useCallback( - (name: IModelConfigActionsKey, args: FunctionState) => setFunctionsState(prevState => ({ + (name: IModelConfigMergedEffectsKey, args: FunctionState) => setFunctionsState(prevState => ({ ...prevState, [name]: { ...prevState[name], @@ -65,11 +66,11 @@ export function createModel(config: C, namespace?: return [ functionsState, setFunctionsState, setFunctionState ]; } - function useActions(state: IModelState, setState: ReactSetState): - [ ActionsPayload, (name: IModelConfigActionsKey, payload: any) => void, IModelActionsState ] { - const [ actionsState, , setActionsState ] = useFunctionsState(Object.keys(effects)); - const [ actionsInitialPayload, actionsInitialIdentifier ]: [ActionsPayload, ActionsIdentifier] = useMemo( - () => transform(effects, (result, action, name) => { + function useEffects(state: IModelState, setState: ReactSetState): + [ EffectsPayload, (name: IModelConfigMergedEffectsKey, payload: any) => void, IModelEffectsState ] { + const [ effectsState, , setEffectsState ] = useFunctionsState(Object.keys(mergedEffects) as IModelConfigMergedEffectsKeys); + const [ effectsInitialPayload, effectsInitialIdentifier ]: [EffectsPayload, EffectsIdentifier] = useMemo( + () => transform(mergedEffects, (result, effect, name) => { const state = { payload: null, identifier: 0, @@ -79,9 +80,9 @@ export function createModel(config: C, namespace?: }, [ {}, {} ]), [], ); - const [ actionsPayload, setActionsPayload ]: [ ActionsPayload, SetActionsPayload ] = useState(() => actionsInitialPayload); - const setActionPayload = useCallback( - (name, payload) => setActionsPayload(prevState => ({ + const [ effectsPayload, setEffectsPayload ]: [ EffectsPayload, SetEffectsPayload ] = useState(() => effectsInitialPayload); + const setEffectPayload = useCallback( + (name, payload) => setEffectsPayload(prevState => ({ ...prevState, [name]: { ...prevState[name], @@ -92,71 +93,72 @@ export function createModel(config: C, namespace?: [], ); - const actionsIdentifier = useRef(actionsInitialIdentifier); - const actionsPayloadIdentifier = Object.keys(actionsPayload).map((name) => actionsPayload[name].identifier); + const effectsIdentifier = useRef(effectsInitialIdentifier); + const effectsPayloadIdentifier = Object.keys(effectsPayload).map((name) => effectsPayload[name].identifier); useEffect(() => { - Object.keys(actionsPayload).forEach((name) => { - const { identifier, payload } = actionsPayload[name]; - if (identifier && identifier !== actionsIdentifier.current[name]) { - actionsIdentifier.current = { - ...actionsIdentifier.current, + (Object.keys(effectsPayload) as IModelConfigMergedEffectsKeys).forEach((name) => { + const { identifier, payload } = effectsPayload[name]; + if (identifier && identifier !== effectsIdentifier.current[name]) { + effectsIdentifier.current = { + ...effectsIdentifier.current, [name]: identifier, }; (async () => { - const nextState = effects[name](state, payload, actions, modelsActions); + const nextState = mergedEffects[name](state, payload, actions, modelsActions); + const isAction = defineActions[name as string]; if (isPromise(nextState)) { - setActionsState(name, { + setEffectsState(name, { isLoading: true, error: null, }); try { const result = await nextState; - if (defineActions[name]) { + if (isAction) { setState(result); } - setActionsState(name, { + setEffectsState(name, { isLoading: false, error: null, }); } catch (error) { - setActionsState(name, { + setEffectsState(name, { isLoading: false, error, }); } - } else { + } else if (isAction) { setState(nextState); } })(); } }); - }, [ actionsPayloadIdentifier ]); + }, [ effectsPayloadIdentifier ]); - return [ actionsPayload, setActionPayload, actionsState ]; + return [ effectsPayload, setEffectPayload, effectsState ]; } function useValue({ initialState }: ModelProps): IModelValue { const preloadedState = initialState || (defineState as IModelState); const [ state, setState ] = useState(preloadedState); - const [ , executeAction, actionsState ] = useActions(state, setState); + const [ , executeEffect, effectsState ] = useEffects(state, setState); actions = useMemo(() => { - const setEffects = transform(effects, (result, fn, name) => { - result[name] = (payload) => executeAction(name, payload); + const setEffects = transform(mergedEffects, (result, fn, name) => { + result[name] = (payload) => executeEffect(name, payload); }); const setReducers = transform(reducers, (result, fn, name) => { result[name] = (payload) => setState((prevState) => fn(prevState, payload)); }); - return { ...setEffects, ...setReducers }; - }, [effects, reducers]); + return { ...setReducers, ...setEffects, }; + }, [mergedEffects, reducers]); if (namespace && modelsActions) { modelsActions[namespace] = actions; } - return [ state, actions, actionsState ]; + return [ state, actions, effectsState ]; } if (isDev && namespace) { diff --git a/src/createStore.tsx b/src/createStore.tsx index b3d7ab89..ae6d0e82 100644 --- a/src/createStore.tsx +++ b/src/createStore.tsx @@ -7,7 +7,7 @@ import { Model, ConfigPropTypeState, ModelActions, - ModelActionsState, + ModelEffectsState, UseModelValue, } from './types'; @@ -40,9 +40,9 @@ export function createStore(configs: C) { return useModelActions(); } - function useModelActionsState(namespace: K): ModelActionsState { - const [, , , useModelActionsState ] = getModel(namespace); - return useModelActionsState(); + function useModelEffectsState(namespace: K): ModelEffectsState { + const [, , , useModelEffectsState ] = getModel(namespace); + return useModelEffectsState(); } function useModel(namespace: K): UseModelValue { @@ -81,12 +81,12 @@ export function createStore(configs: C) { }; } - function withModelActionsState) => Record>(namespace?: K, mapModelActionsStateToProps?: M) { - mapModelActionsStateToProps = (mapModelActionsStateToProps || ((actionsState) => ({ [`${namespace}ActionsState`]: actionsState }))) as M; - return , P extends R>(Component: React.ComponentType

) => { + function withModelEffectsState) => Record>(namespace?: K, mapModelEffectsStateToProps?: M) { + mapModelEffectsStateToProps = (mapModelEffectsStateToProps || ((effectsState) => ({ [`${namespace}EffectsState`]: effectsState }))) as M; + return , P extends R>(Component: React.ComponentType

) => { return (props: Optionalize): React.ReactElement => { - const value = useModelActionsState(namespace); - const withProps = mapModelActionsStateToProps(value); + const value = useModelEffectsState(namespace); + const withProps = mapModelEffectsStateToProps(value); return ( (configs: C) { Provider, useModel, useModelActions, - useModelActionsState, + useModelEffectsState, withModel, withModelActions, - withModelActionsState, + withModelEffectsState, }; } diff --git a/src/types.ts b/src/types.ts index d4e22b4d..e9458b5d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Assign } from 'utility-types'; export type Optionalize = Omit; @@ -59,17 +60,27 @@ export type ContextHookReturn< ? ContextHookTuple : ContextHookMultipleTuple); -export type ConfigAction = (prevState: S, payload?: any, actions?: any, globalActions?: any) => S | Promise; + export type ConfigAction = (prevState: S, payload?: any, actions?: any, globalActions?: any) => S | Promise; + export type ConfigEffect = (state: S, payload?: any, actions?: any, globalActions?: any) => void | Promise; + export type ConfigReducer = (state: S, payload?: any,) => S; export interface ConfigActions { [name: string]: ConfigAction; } +export interface ConfigEffects { + [name: string]: ConfigEffect; +} + +export interface ConfigReducers { + [name: string]: ConfigReducer; +} + export interface Config { state: S; actions?: ConfigActions; - effects?: any; - reducers?: any; + effects?: ConfigEffects; + reducers?: ConfigReducers; }; export interface Configs { @@ -91,22 +102,22 @@ export type FunctionsState = { export type SetFunctionsState = ReactSetState>; -export type ActionIdentifier = number; +export type EffectIdentifier = number; -export type ActionsIdentifier = { - [K in keyof ConfigActions]: ActionIdentifier; +export type EffectsIdentifier = { + [K in keyof ConfigActions]: EffectIdentifier; } -export interface ActionPayload { +export interface EffectPayload { payload: any; - identifier: ActionIdentifier; + identifier: EffectIdentifier; } -export type ActionsPayload = { - [K in keyof A]: ActionPayload; +export type EffectsPayload = { + [K in keyof A]: EffectPayload; } -export type SetActionsPayload = ReactSetState>; +export type SetEffectsPayload = ReactSetState>; export type Actions = { [K in keyof A]?: (payload?: Parameters[1]) => void; @@ -114,9 +125,13 @@ export type Actions = { export type ConfigPropTypeState = PropType; export type ConfigPropTypeActions = PropType; -export type ModelActions = Actions>; -export type ModelActionsState = FunctionsState>; -export type ModelValue = [ ConfigPropTypeState, ModelActions, ModelActionsState ]; +export type ConfigPropTypeEffects = PropType; +export type ConfigPropTypeReducers = PropType; +export type ConfigMergedEffects = Assign, ConfigPropTypeEffects>; +export type ModelEffects = Actions>; +export type ModelActions = Actions, ConfigMergedEffects>>; +export type ModelEffectsState = FunctionsState>; +export type ModelValue = [ ConfigPropTypeState, ModelActions, ModelEffectsState ]; export type Model = ContextHookReturn< ConfigPropTypeState, @@ -124,7 +139,7 @@ export type Model = [ (model: ModelValue) => ConfigPropTypeState, (model: ModelValue) => ModelActions, - (model: ModelValue) => ModelActionsState + (model: ModelValue) => ModelEffectsState ] >; export type UseModelValue = [ ConfigPropTypeState, ModelActions ]; From 256bc4cb43e7fa8bedb069046e9b370360419a90 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 01:50:18 +0800 Subject: [PATCH 07/14] docs: actions to reducers && effects --- README.md | 17 ++++++----- docs/api.md | 73 +++++++++++++++++++++++++-------------------- docs/recipes.md | 40 +++++++++++++++---------- src/createModel.tsx | 6 +++- src/createStore.tsx | 40 ++++++++++++++++--------- src/types.ts | 4 +++ 6 files changed, 109 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 270f5011..91ca0cfe 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,16 @@ const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), tim // 1️⃣ Use a model to define your store const counter = { state: 0, - actions: { + reducers: { increment:(prevState) => prevState + 1, - async decrement(prevState) { + decrement:(prevState) => prevState - 1, + }, + effects: { + async decrementAsync(state, playload, actions) { await delay(1000); - return prevState - 1; + actions.decrement(); }, - }, + } }; const models = { @@ -66,12 +69,12 @@ const store = createStore(models); const { useModel } = store; function Counter() { const [ count, actions ] = useModel('counter'); - const { increment, decrement } = actions; + const { increment, decrementAsync } = actions; return (

); } diff --git a/docs/api.md b/docs/api.md index b3ed346f..98ccf1a7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -55,31 +55,44 @@ const model = { }; ``` -#### actions +#### reducers -`actions: { [string]: (prevState, payload, actions, globalActions) => any }` +`reducers: { [string]: (prevState, payload) => any }` -An object of functions that change the model's state. These functions take the model's previous state and a payload, and return the model's next state. +An object of functions that change the model's state. These functions take the model's previous state and a payload, and return the model's next state. These should be pure functions relying only on the state and payload args to compute the next state. For code that relies on the "outside world" (impure functions like api calls, etc.), use effects. ```js const counter = { state: 0, - actions: { - add: (prevState, payload) => prevState + payload, - }, + reducers: { + add: (state, payload) => state + payload, + } }; ``` -Actions provide a simple way of handling async actions when used with async/await: +#### effects + +`effects: { [string]: (prevState, payload, actions, globalActions) => void }` + +An object of functions that can handle the world outside of the model. Effects provide a simple way of handling async actions when used with async/await. ```js const counter = { - actions: { - async addAsync(prevState, payload) => { - await delay(1000); - return prevState + payload; + state: 0, + effects: { + async add(prevState, payload, actions) { + // wait for data to load + const response = await fetch('http://example.com/data'); + const data = await response.json(); + // pass the result to a local reducer + actions.update(data); }, }, + reducers: { + update(prev, data) { + return {...prev, ...data}; + } + }, }; ``` @@ -90,25 +103,19 @@ const user = { state: { foo: [], }, - actions: { + effects: { like(prevState, payload, actions, globalActions) => { actions.foo(payload); // call user's actions globalActions.user.foo(payload); // call actions of another model - - // do something... - - return { - ...prevState, - }; }, - foo(prevState, id) { - // do something... - + }, + reducres: { + foo(prevState, payload) { return { ...prevState, }; }, - }, + } }; ``` @@ -179,7 +186,7 @@ const counter = { state: { value: 0, }, - actions: { + reducers: { add: (prevState, payload) => ({...prevState, value: prevState.value + payload}), }, }; @@ -217,14 +224,14 @@ A hook granting your components access to the action state of the model. ```js function FunctionComponent() { const actions = useModelActions('counter'); - const actionsState = useModelActionsState('counter'); + const effectsState = useModelEffectsState('counter'); useEffect(() => { actions.fetch(); }, []); - actionsState.fetch.isLoading; - actionsState.fetch.error; + effectsState.fetch.isLoading; + effectsState.fetch.error; } ``` @@ -321,7 +328,7 @@ You can use `mapModelActionsToProps` to set the property as the same way like `m ### withModelActionsState -`withModelActionsState(name: string, mapModelActionsStateToProps?: (actionsState) => Object = (actionsState) => ({ [name]: actionsState }) ): (React.Component) => React.Component` +`withModelActionsState(name: string, mapModelActionsStateToProps?: (effectsState) => Object = (effectsState) => ({ [name]: effectsState }) ): (React.Component) => React.Component` ```tsx import { ModelActionsState, ModelActions } from '@ice/store'; @@ -361,7 +368,7 @@ const [ Provider, useState, useActions, - useActionsState, + useEffectsState, ] = createModel(model); ``` @@ -439,22 +446,22 @@ function FunctionComponent() { } ``` -### useActionsState +### useEffectsState -`useActionsState(): { [actionName: string]: { isLoading: boolean, error: Error } } ` +`useEffectsState(): { [actionName: string]: { isLoading: boolean, error: Error } } ` A hook granting your components access to the action state of the model. ```js function FunctionComponent() { const actions = useActions(); - const actionsState = useActionsState(); + const effectsState = useEffectsState(); useEffect(() => { actions.fetch(); }, []); - actionsState.fetch.isLoading; - actionsState.fetch.error; + effectsState.fetch.isLoading; + effectsState.fetch.error; } ``` diff --git a/docs/recipes.md b/docs/recipes.md index a04227d3..7348414c 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -18,21 +18,26 @@ export default { name: '', tasks: 0, }, - actions: { - async refresh() { - return await fetch('/user'); + effects: { + async refresh(state, playload, actions) { + const data = await fetch('/user'); + actions.setState(data); + }, + }, + reducers: { + setState(prevState, payload) { + return { ...prevState, ...payload }; }, }, }; // src/models/tasks -import { user } from './user'; - export default { state: [], - actions: { - async refresh() { - return await fetch('/tasks'); + effects: { + async refresh(state, playload, actions) { + const data = await fetch('/tasks'); + actions.setState(data); }, async add(prevState, task, actions, globalActions) { await fetch('/tasks/add', task); @@ -42,10 +47,13 @@ export default { // Retrieve todos after adding tasks await actions.refresh(); - - return { ...prevState }; }, - } + }, + reducers: { + setState(prevState, payload) { + return { ...prevState, ...payload }; + }, + }, }; // src/store @@ -67,9 +75,9 @@ For example, the action A in Model A calls the action B in Model B and the actio Be careful the possibility of endless loop problem will arise when methods from different models call each other. -## Async actions' executing status +## effects' executing status -`icestore` has built-in support to access the executing status of async actions. This enables users to have access to the isLoading and error executing status of async actions without defining extra state, making the code more consise and clean. +`icestore` has built-in support to access the executing status of effects. This enables users to have access to the isLoading and error executing status of effects without defining extra state, making the code more consise and clean. ### Example @@ -78,14 +86,14 @@ import { useModelActions } from './store'; function FunctionComponent() { const actions = useModelActions('name'); - const actionsState = useModelActionsState('name'); + const effectsState = useModelEffectsState('name'); useEffect(() => { actions.fetch(); }, []); - actionsState.fetch.isLoading; - actionsState.fetch.error; + effectsState.fetch.isLoading; + effectsState.fetch.error; } ``` diff --git a/src/createModel.tsx b/src/createModel.tsx index 9ab36d0b..5d6e057c 100644 --- a/src/createModel.tsx +++ b/src/createModel.tsx @@ -28,9 +28,9 @@ export function createModel(config: C, namespace?: type IModelConfigMergedEffectsKey = keyof IModelConfigMergedEffects; type IModelEffects = ModelEffects; type IModelEffectsState = ModelEffectsState; - type SetModelFunctionsState = SetFunctionsState; type IModelValue = ModelValue; type IModelConfigMergedEffectsKeys = IModelConfigMergedEffectsKey[]; + type SetModelFunctionsState = SetFunctionsState; const { state: defineState = {}, @@ -41,6 +41,10 @@ export function createModel(config: C, namespace?: const mergedEffects = { ...defineActions, ...effects } as IModelConfigMergedEffects; let actions; + if (Object.keys(defineActions).length > 0) { + console.error('The actions field is no longer recommended for the following reasons: https://github.com/ice-lab/icestore/issues/66'); + } + function useFunctionsState(functions: IModelConfigMergedEffectsKeys): [ IModelEffectsState, SetModelFunctionsState, (name: IModelConfigMergedEffectsKey, args: FunctionState) => void ] { const functionsInitialState = useMemo( diff --git a/src/createStore.tsx b/src/createStore.tsx index ae6d0e82..b6811836 100644 --- a/src/createStore.tsx +++ b/src/createStore.tsx @@ -81,20 +81,22 @@ export function createStore(configs: C) { }; } - function withModelEffectsState) => Record>(namespace?: K, mapModelEffectsStateToProps?: M) { - mapModelEffectsStateToProps = (mapModelEffectsStateToProps || ((effectsState) => ({ [`${namespace}EffectsState`]: effectsState }))) as M; - return , P extends R>(Component: React.ComponentType

) => { - return (props: Optionalize): React.ReactElement => { - const value = useModelEffectsState(namespace); - const withProps = mapModelEffectsStateToProps(value); - return ( - - ); + function createWithModelEffectsState(fieldSuffix: string = 'EffectsState') { + return function withModelEffectsState) => Record>(namespace?: K, mapModelEffectsStateToProps?: M) { + mapModelEffectsStateToProps = (mapModelEffectsStateToProps || ((effectsState) => ({ [`${namespace}${fieldSuffix}`]: effectsState }))) as M; + return , P extends R>(Component: React.ComponentType

) => { + return (props: Optionalize): React.ReactElement => { + const value = useModelEffectsState(namespace); + const withProps = mapModelEffectsStateToProps(value); + return ( + + ); + }; }; - }; + } } const modelsActions = {}; @@ -109,6 +111,16 @@ export function createStore(configs: C) { useModelEffectsState, withModel, withModelActions, - withModelEffectsState, + withModelEffectsState: createWithModelEffectsState(), + + /** + * @deprecated + */ + useModelActionsState: useModelEffectsState, + + /** + * @deprecated + */ + withModelActionsState: createWithModelEffectsState('ActionsState'), }; } diff --git a/src/types.ts b/src/types.ts index e9458b5d..cc9e4f47 100644 --- a/src/types.ts +++ b/src/types.ts @@ -78,6 +78,10 @@ export interface ConfigReducers { export interface Config { state: S; + + /** + * @deprecated + */ actions?: ConfigActions; effects?: ConfigEffects; reducers?: ConfigReducers; From 30a0f29ae51dca4da9ac8d24f21e3357d4d18fcc Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 01:55:08 +0800 Subject: [PATCH 08/14] chore: lint --- docs/recipes.md | 2 +- examples/counter/src/index.tsx | 2 +- examples/todos/src/models/todos.ts | 4 ++-- examples/todos/src/models/user.ts | 2 +- src/createModel.tsx | 4 ++-- src/createStore.tsx | 2 +- src/types.ts | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index 7348414c..6abd4c53 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -193,7 +193,7 @@ If the project is relatively large, or more likely to follow the page maintenanc - O: Yes - X: No -- +: Tips +- +: Extra | | constate | zustand | react-tracked | rematch | icestore | | --------| -------- | -------- | -------- | -------- | -------- | diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx index c76e1b6a..3425aa44 100644 --- a/examples/counter/src/index.tsx +++ b/examples/counter/src/index.tsx @@ -16,7 +16,7 @@ const counter = { await delay(1000); actions.decrement(); }, - } + }, }; const models = { diff --git a/examples/todos/src/models/todos.ts b/examples/todos/src/models/todos.ts index bdb2fd63..c9aea752 100644 --- a/examples/todos/src/models/todos.ts +++ b/examples/todos/src/models/todos.ts @@ -32,7 +32,7 @@ const todos = { ...prevState, ...payload, }; - } + }, }, effects: { add(state: TodosState, todo: Todo, actions, globalActions) { @@ -40,7 +40,7 @@ const todos = { dataSource.push(todo); globalActions.user.setTodos(dataSource.length); actions.setState({ - dataSource + dataSource, }); }, async refresh(state: TodosState, args, actions, globalActions) { diff --git a/examples/todos/src/models/user.ts b/examples/todos/src/models/user.ts index ccce3755..abdb15ce 100644 --- a/examples/todos/src/models/user.ts +++ b/examples/todos/src/models/user.ts @@ -16,7 +16,7 @@ const user = { return { ...prevState, ...payload, - } + }; }, }, effects: { diff --git a/src/createModel.tsx b/src/createModel.tsx index 5d6e057c..a4e6db42 100644 --- a/src/createModel.tsx +++ b/src/createModel.tsx @@ -38,7 +38,7 @@ export function createModel(config: C, namespace?: effects = {}, reducers = {}, } = config; - const mergedEffects = { ...defineActions, ...effects } as IModelConfigMergedEffects; + const mergedEffects = { ...defineActions, ...effects }; let actions; if (Object.keys(defineActions).length > 0) { @@ -109,7 +109,7 @@ export function createModel(config: C, namespace?: [name]: identifier, }; (async () => { - const nextState = mergedEffects[name](state, payload, actions, modelsActions); + const nextState = (mergedEffects as IModelConfigMergedEffects)[name](state, payload, actions, modelsActions); const isAction = defineActions[name as string]; if (isPromise(nextState)) { setEffectsState(name, { diff --git a/src/createStore.tsx b/src/createStore.tsx index b6811836..3f5c5699 100644 --- a/src/createStore.tsx +++ b/src/createStore.tsx @@ -96,7 +96,7 @@ export function createStore(configs: C) { ); }; }; - } + }; } const modelsActions = {}; diff --git a/src/types.ts b/src/types.ts index cc9e4f47..48d5941e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,9 +60,9 @@ export type ContextHookReturn< ? ContextHookTuple : ContextHookMultipleTuple); - export type ConfigAction = (prevState: S, payload?: any, actions?: any, globalActions?: any) => S | Promise; - export type ConfigEffect = (state: S, payload?: any, actions?: any, globalActions?: any) => void | Promise; - export type ConfigReducer = (state: S, payload?: any,) => S; +export type ConfigAction = (prevState: S, payload?: any, actions?: any, globalActions?: any) => S | Promise; +export type ConfigEffect = (state: S, payload?: any, actions?: any, globalActions?: any) => void | Promise; +export type ConfigReducer = (state: S, payload?: any,) => S; export interface ConfigActions { [name: string]: ConfigAction; From 926b833314db271e88063da48e98a8ea372b3527 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 02:09:00 +0800 Subject: [PATCH 09/14] docs: upgrade guidelines --- docs/recipes.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/docs/recipes.md b/docs/recipes.md index 6abd4c53..8dddfbf6 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -189,6 +189,146 @@ For most small and medium-sized projects, it is recommended to centrally manage If the project is relatively large, or more likely to follow the page maintenance of the store,then you can declare a store instance in each page directory. However, in this case, cross page store calls should be avoided as much as possible. +## Upgrade Guidelines + +### Define Model + +#### 0.x + +```ts +import user from './user'; + +const store = { + dataSource: [], + async refresh() { + await delay(2000); + + this.dataSource = [ + { + name: 'react', + }, + { + name: 'vue', + done: true, + }, + { + name: 'angular', + }, + ], + user.setTodos(this.dataSource.length); + }, + add(todo) { + this.dataSource.push(todo); + user.setTodos(this.dataSource.length); + }, +}; +``` + +#### 1.x + +```ts +const todos = { + state: { + dataSource: [], + }, + reducers: { + setState(prevState, payload) { + return { + ...prevState, + ...payload, + }; + }, + }, + effects: { + add(state, todo, actions, globalActions) { + const dataSource = [].concat(state.dataSource); + dataSource.push(todo); + globalActions.user.setTodos(dataSource.length); + actions.setState({ + dataSource, + }); + }, + async refresh(state, args, actions, globalActions) { + await delay(2000); + + const dataSource = [ + { + name: 'react', + }, + { + name: 'vue', + done: true, + }, + { + name: 'angular', + }, + ]; + globalActions.user.setTodos(dataSource.length); + actions.setState({ + dataSource, + }); + }, + }, +}; +``` + +### Create store + +#### 0.x + +```js +import Icestore from '@ice/store'; +import * as stores from './stores'; + +const icestore = new Icestore(); +export default icestore.registerStores(stores); +``` + +#### 1.x + +```js +import { createStore } from '@ice/store'; +import * as models from './models'; + +export default createStore(models); +``` + +### Consume model + +#### 0.x + +```js +function App() { + const todos = stores.useStore('todos'); + const { dataSource, add } = todos; +} +``` + +#### 1.x + +```js +function App() { + const [ state, actions ] = store.useModel('todos'); + const { dataSource } = state; + const { add } = actions; +} +``` + +### Bind View + +#### 0.x + +No need. + +#### 1.x + +```js +const { Provider } = store; +ReactDOM.render( + +, document.getElementById('root')); +``` + ## Comparison - O: Yes From b0416ba9d43073cdf50d92c3b2f3c884fa1ebf8b Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 02:12:45 +0800 Subject: [PATCH 10/14] chore: lint --- src/createModel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/createModel.tsx b/src/createModel.tsx index a4e6db42..9f710702 100644 --- a/src/createModel.tsx +++ b/src/createModel.tsx @@ -156,7 +156,7 @@ export function createModel(config: C, namespace?: result[name] = (payload) => setState((prevState) => fn(prevState, payload)); }); - return { ...setReducers, ...setEffects, }; + return { ...setReducers, ...setEffects }; }, [mergedEffects, reducers]); if (namespace && modelsActions) { From 9dea0efd66d21ed123d4ca03a0d72df3c0cdc58d Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 11:54:19 +0800 Subject: [PATCH 11/14] chore: typo --- README.md | 2 +- docs/recipes.md | 18 +++++++++--------- examples/counter/src/index.tsx | 2 +- examples/todos/src/models/todos.ts | 8 ++++---- examples/todos/src/models/user.ts | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 91ca0cfe..a45ce5d9 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ const counter = { decrement:(prevState) => prevState - 1, }, effects: { - async decrementAsync(state, playload, actions) { + async decrementAsync(state, payload, actions) { await delay(1000); actions.decrement(); }, diff --git a/docs/recipes.md b/docs/recipes.md index 8dddfbf6..b8a98760 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -19,13 +19,13 @@ export default { tasks: 0, }, effects: { - async refresh(state, playload, actions) { + async refresh(state, payload, actions) { const data = await fetch('/user'); - actions.setState(data); + actions.update(data); }, }, reducers: { - setState(prevState, payload) { + update(prevState, payload) { return { ...prevState, ...payload }; }, }, @@ -35,9 +35,9 @@ export default { export default { state: [], effects: { - async refresh(state, playload, actions) { + async refresh(state, payload, actions) { const data = await fetch('/tasks'); - actions.setState(data); + actions.update(data); }, async add(prevState, task, actions, globalActions) { await fetch('/tasks/add', task); @@ -50,7 +50,7 @@ export default { }, }, reducers: { - setState(prevState, payload) { + update(prevState, payload) { return { ...prevState, ...payload }; }, }, @@ -232,7 +232,7 @@ const todos = { dataSource: [], }, reducers: { - setState(prevState, payload) { + update(prevState, payload) { return { ...prevState, ...payload, @@ -244,7 +244,7 @@ const todos = { const dataSource = [].concat(state.dataSource); dataSource.push(todo); globalActions.user.setTodos(dataSource.length); - actions.setState({ + actions.update({ dataSource, }); }, @@ -264,7 +264,7 @@ const todos = { }, ]; globalActions.user.setTodos(dataSource.length); - actions.setState({ + actions.update({ dataSource, }); }, diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx index 3425aa44..bd9f956d 100644 --- a/examples/counter/src/index.tsx +++ b/examples/counter/src/index.tsx @@ -12,7 +12,7 @@ const counter = { decrement:(prevState) => prevState - 1, }, effects: { - async decrementAsync(state, playload, actions) { + async decrementAsync(state, payload, actions) { await delay(1000); actions.decrement(); }, diff --git a/examples/todos/src/models/todos.ts b/examples/todos/src/models/todos.ts index c9aea752..d0abc551 100644 --- a/examples/todos/src/models/todos.ts +++ b/examples/todos/src/models/todos.ts @@ -27,7 +27,7 @@ const todos = { dataSource, }; }, - setState(prevState: TodosState, payload) { + update(prevState: TodosState, payload) { return { ...prevState, ...payload, @@ -39,7 +39,7 @@ const todos = { const dataSource = [].concat(state.dataSource); dataSource.push(todo); globalActions.user.setTodos(dataSource.length); - actions.setState({ + actions.update({ dataSource, }); }, @@ -59,7 +59,7 @@ const todos = { }, ]; globalActions.user.setTodos(dataSource.length); - actions.setState({ + actions.update({ dataSource, }); }, @@ -69,7 +69,7 @@ const todos = { dataSource.splice(index, 1); globalActions.user.setTodos(dataSource.length); - actions.setState(state); + actions.update(state); }, }, }; diff --git a/examples/todos/src/models/user.ts b/examples/todos/src/models/user.ts index abdb15ce..71bd1737 100644 --- a/examples/todos/src/models/user.ts +++ b/examples/todos/src/models/user.ts @@ -12,7 +12,7 @@ const user = { setTodos(prevState, todos: number) { return { ...prevState, todos }; }, - setState(prevState, payload) { + update(prevState, payload) { return { ...prevState, ...payload, @@ -27,7 +27,7 @@ const user = { }; const auth = true; - actions.setState({ + actions.update({ dataSource, auth, }); From 945be69f167aa4180c7c7b1f5204b238350f64f3 Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 11:55:40 +0800 Subject: [PATCH 12/14] chore: args => payload --- docs/recipes.md | 2 +- examples/todos/src/models/todos.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index b8a98760..4f6b326b 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -248,7 +248,7 @@ const todos = { dataSource, }); }, - async refresh(state, args, actions, globalActions) { + async refresh(state, payload, actions, globalActions) { await delay(2000); const dataSource = [ diff --git a/examples/todos/src/models/todos.ts b/examples/todos/src/models/todos.ts index d0abc551..236f4d70 100644 --- a/examples/todos/src/models/todos.ts +++ b/examples/todos/src/models/todos.ts @@ -43,7 +43,7 @@ const todos = { dataSource, }); }, - async refresh(state: TodosState, args, actions, globalActions) { + async refresh(state: TodosState, payload, actions, globalActions) { await delay(2000); const dataSource: any[] = [ From 84b0b78897e967c1e75a93bf5d0866c82d7159cf Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 11:56:08 +0800 Subject: [PATCH 13/14] chore: typo --- docs/recipes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.md b/docs/recipes.md index 4f6b326b..c52e873c 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -314,7 +314,7 @@ function App() { } ``` -### Bind View +### Binding View #### 0.x From c7870a45220733b1618902b3757b326dd509dbea Mon Sep 17 00:00:00 2001 From: alvinhui Date: Thu, 5 Mar 2020 11:58:28 +0800 Subject: [PATCH 14/14] chore: useModelEffectsState && withModelEffectsState --- docs/api.md | 16 ++++++++-------- docs/recipes.md | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/api.md b/docs/api.md index 98ccf1a7..a25df991 100644 --- a/docs/api.md +++ b/docs/api.md @@ -18,10 +18,10 @@ const { Provider, useModel, useModelActions, - useModelActionsState, + useModelEffectsState, withModel, withModelActions, - withModelActionsState, + withModelEffectsState, } = createStore(models); ``` @@ -215,9 +215,9 @@ function FunctionComponent() { } ``` -### useModelActionsState +### useModelEffectsState -`useModelActionsState(name: string): { [actionName: string]: { isLoading: boolean, error: Error } } ` +`useModelEffectsState(name: string): { [actionName: string]: { isLoading: boolean, error: Error } } ` A hook granting your components access to the action state of the model. @@ -326,16 +326,16 @@ export default withModelActions('todos')(TodoList); You can use `mapModelActionsToProps` to set the property as the same way like `mapModelToProps`. -### withModelActionsState +### withModelEffectsState -`withModelActionsState(name: string, mapModelActionsStateToProps?: (effectsState) => Object = (effectsState) => ({ [name]: effectsState }) ): (React.Component) => React.Component` +`withModelEffectsState(name: string, mapModelActionsStateToProps?: (effectsState) => Object = (effectsState) => ({ [name]: effectsState }) ): (React.Component) => React.Component` ```tsx import { ModelActionsState, ModelActions } from '@ice/store'; import todosModel from '@/models/todos'; import store from '@/store'; -const { withModelActionsState } = store; +const { withModelEffectsState } = store; interface Props { todosActionsState: ModelActionsState; // `todosActionsState` automatically adds `${modelName}ActionsState` as the property @@ -349,7 +349,7 @@ class TodoList extends Component { } } -export default withModelActionsState('todos')(TodoList); +export default withModelEffectsState('todos')(TodoList); ``` You can use `mapModelActionsStateToProps` to set the property as the same way like `mapModelToProps`. diff --git a/docs/recipes.md b/docs/recipes.md index c52e873c..8cd42502 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -163,9 +163,9 @@ import compose from 'lodash/fp/compose'; export default compose(withModel('user'), withModel('todos'))(TodoList); ``` -### withModelActions & withModelActionsState +### withModelActions & withModelEffectsState -You can use `withModelActions` to call only model actions without listening for model changes, also for `withModelActionsState`. +You can use `withModelActions` to call only model actions without listening for model changes, also for `withModelEffectsState`. See [docs/api](./api.md) for more details.