From 1088b8ea8051e45c068e0b874c6acd106b401ebc Mon Sep 17 00:00:00 2001 From: s Date: Fri, 9 Apr 2021 22:10:38 +0530 Subject: [PATCH 1/3] :rocket: Add node_redux for state container --- lua_modules/node_redux/README.md | 66 +++++++ lua_modules/node_redux/actionType_utils.lua | 31 ++++ lua_modules/node_redux/combineReducers.lua | 172 ++++++++++++++++++ .../node_redux/createStore_example.lua | 76 ++++++++ .../node_redux/isPlainObject_utils.lua | 27 +++ lua_modules/node_redux/node_redux.lua | 154 ++++++++++++++++ 6 files changed, 526 insertions(+) create mode 100644 lua_modules/node_redux/README.md create mode 100644 lua_modules/node_redux/actionType_utils.lua create mode 100644 lua_modules/node_redux/combineReducers.lua create mode 100644 lua_modules/node_redux/createStore_example.lua create mode 100644 lua_modules/node_redux/isPlainObject_utils.lua create mode 100644 lua_modules/node_redux/node_redux.lua diff --git a/lua_modules/node_redux/README.md b/lua_modules/node_redux/README.md new file mode 100644 index 0000000000..24ff791069 --- /dev/null +++ b/lua_modules/node_redux/README.md @@ -0,0 +1,66 @@ +# NodeRedux +Redux is a predictable state container for lua apps and NodeMCU. + +## Influences + +NodeRedux evolves the ideas of [Redux](https://redux.js.org/), +which help to maintain state lua base app basically NodeMCU devices. + + +## Basic Example + +The whole global state of your app is stored in an object tree inside a single _store_. +The only way to change the state tree is to create an _action_, +an object describing what happened, and _dispatch_ it to the store. +To specify how state gets updated in response to an action, you write pure _reducer_ +functions that calculate a new state based on the old state and the action. + + +```lua +local redux = require('redux') -- optional line for nodemcu, only need for lua app + +-- This is a reducer - a function that takes a current state value and an +-- action object describing "what happened", and returns a new state value. +-- A reducer's function signature is: (state, action) => newState +-- +-- The NodeRedux state should contain only plain Lua table. +-- The root state value is usually an table. It's important that you should +-- not mutate the state table, but return a new table if the state changes. +-- +-- You can use any conditional logic you want in a reducer. In this example, +-- we use a if statement, but it's not required. + +local function counterReducer(state, action) + state = state or { value = 0 } + if action.type == 'counter/incremented' then + return { value = state.value + 2 } + elseif action.type == 'counter/decremented' then + return { value = state.value - 1 } + else + return state + end +end + +redux.createStore(counterReducer) + +local function console(pState, cState) + print('Previous State: '.. pState.value, 'Current State: '.. cState.value) +end + +redux.store.subscribe(console) + +redux.store.dispatch({type = 'counter/incremented'}) +-- {value = 1} +redux.store.dispatch({type = 'counter/incremented'}) +-- {value = 2} +redux.store.dispatch({type = 'counter/decremented'}) +-- {value = 1} +``` + +[Here](./createStore_example.lua) the example code + +## License + +[MIT](LICENSE) + + diff --git a/lua_modules/node_redux/actionType_utils.lua b/lua_modules/node_redux/actionType_utils.lua new file mode 100644 index 0000000000..266ec74ac5 --- /dev/null +++ b/lua_modules/node_redux/actionType_utils.lua @@ -0,0 +1,31 @@ +local ActionType +do + local upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + local lowerCase = "abcdefghijklmnopqrstuvwxyz" + local numbers = "0123456789" + + local characterSet = upperCase .. lowerCase .. numbers + + local function randomeSting(length) + local output = "" + for _ = 1, length do + output = math.random(#characterSet) .. output + end + return output + end + + local intString = randomeSting(36) + local INIT = "@redux/INIT" .. intString + + local function PROBE_UNKNOWN_ACTION() + return '@@redux/PROBE_UNKNOWN_ACTION' .. randomeSting(36) + end + + + ActionType = { + INIT = INIT, + PROBE_UNKNOWN_ACTION = PROBE_UNKNOWN_ACTION + } +end + +return ActionType diff --git a/lua_modules/node_redux/combineReducers.lua b/lua_modules/node_redux/combineReducers.lua new file mode 100644 index 0000000000..cf3c9bfdea --- /dev/null +++ b/lua_modules/node_redux/combineReducers.lua @@ -0,0 +1,172 @@ +local require, type, error, pairs, table, tostring = require, type, error, pairs, table, tostring + +local ActionTypes = require('actionType_utils') +local isPlainObject = require('isPlainObject_utils') + + +local function _getKeys(object) + local keyset={} + local n = 0 + for k, _ in pairs(object) do + n = n + 1 + keyset[n] = k + end + return keyset +end + +local function _getKeysString(object) + local keys = '' + for k, _ in pairs(object) do + keys = tostring(k) .. ', ' .. keys + end + return string.sub(keys, 1, -3) +end + +local function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) + local reducerKeys = _getKeys(reducers) + local argumentName = 'previous state received by the reducer' + if(action ~= nil and action.type == ActionTypes.INIT) then + argumentName = 'preloadedState argument passed to createStore' + end + + if(#reducerKeys == 0) then + return 'Store does not have a valid reducer. Make sure the argument passed ' .. + 'to combineReducers is an object whose values are reducers.' + end + + if (not isPlainObject(inputState)) then + local keys = _getKeysString(reducers) + return 'The' .. argumentName .. 'has unexpected type. ' .. + 'Expected argument to be an object with the following ' .. + 'keys "' .. keys .. '"' + end + + local unexpectedKeys = {} + for k, _ in pairs(inputState) do + if(reducers[k] == nil and not unexpectedKeyCache[k]) then + table.insert(unexpectedKeys, k) + end + end + + for _, v in pairs(unexpectedKeys) do + unexpectedKeyCache[v] = true + end + + if (action ~= nil and action.type == ActionTypes.REPLACE) then + return + end + + if (#unexpectedKeys > 0) then + return "Unexpected " .. #unexpectedKeys > 1 and "'keys'" or "'key'" .. + '"'.. _getKeysString(unexpectedKeys) ..'" found in ' .. argumentName .. '.' .. + 'Expected to find one of the known reducer keys instead: "' .. + _getKeysString(unexpectedKeys) ..'". Unexpected keys will be ignored.' + end +end + +local function assertReducerShape(reducers) + for k, v in pairs(reducers) do + local reducer = v + local initialState = reducer(nil, { type = ActionTypes.INIT }) + + if (type(initialState) == 'nil') then + error( + 'The slice reducer for key "' .. k .. '" returned undefined during initialization. ' .. + 'If the state passed to the reducer is undefined, you must '.. + 'explicitly return the initial state. The initial state may '.. + "not be undefined. If you don't want to set a value for this reducer, ".. + "you can use null instead of undefined.", + 2 + ) + end + + initialState = reducer(nil, { type = ActionTypes.PROBE_UNKNOWN_ACTION() }) + + if (type(initialState) == 'nil') then + error( + 'The slice reducer for key "' .. k .. '" returned undefined when probed with a random type. ' .. + "Don't try to handle '" .. ActionTypes.INIT .. "' or other actions in \"redux/*\" " .. + 'namespace. They are considered private. Instead, you must return the ' .. + 'current state for any unknown actions, unless it is undefined, ' .. + 'in which case you must return the initial state, regardless of the ' .. + 'action type. The initial state may not be undefined, but can be null.', + 2 + ) + end + end +end + +local function combineReducers(reducers) + local reducerKeys = _getKeys(reducers) + local finalReducers = {} + + for _, v in pairs(reducerKeys) do + if (type(reducers[v]) == 'function' ) then + finalReducers[v] = reducers[v] + end + end + + local finalReducerKeys = _getKeys(finalReducers) + + + local unexpectedKeyCache = {} + local shapeAssertionError + local res, val = pcall(assertReducerShape, finalReducers) + + if(not res) then + shapeAssertionError = val + end + + local function combination(state, action) + if (shapeAssertionError) then + error (shapeAssertionError, 2) + end + + state = state or {} + + local warningMessage = getUnexpectedStateShapeWarningMessage( + state, + finalReducers, + action, + unexpectedKeyCache + ) + + if (warningMessage) then + print('\27[93mWARNING :: '..warningMessage..'\27[0m') + end + + local hasChanged = false + local nextState = {} + + + for _, v in pairs(finalReducerKeys) do + local key = v + local reducer = finalReducers[key] + local previousStateForKey = state[key] + local nextStateForKey = reducer(previousStateForKey, action) + + if(type(nextStateForKey) == 'nil') then + local actionType = action and action.type + error( + 'When called with an action of type "'.. actionType or '(unknown type)' .. '"' .. + 'the slice reducer for key "' .. key .. '" returned undefined. ' .. + 'To ignore an action, you must explicitly return the previous state. ' .. + 'If you want this reducer to hold no value, you can return null instead of undefined.', + 2 + ) + end + nextState[key] = nextStateForKey + hasChanged = hasChanged or nextStateForKey ~= previousStateForKey + end + hasChanged = hasChanged or #finalReducerKeys ~= #_getKeys(state) + + if (hasChanged) then + return nextState + else + return state + end + end + return combination +end + +return combineReducers diff --git a/lua_modules/node_redux/createStore_example.lua b/lua_modules/node_redux/createStore_example.lua new file mode 100644 index 0000000000..e16c60c7f1 --- /dev/null +++ b/lua_modules/node_redux/createStore_example.lua @@ -0,0 +1,76 @@ +-- +-- Created by IntelliJ IDEA. +-- User: shubhams +-- Date: 06/04/21 +-- Time: 1:53 PM +-- To change this template use File | Settings | File Templates. +-- + +local redux = require('node_redux') + +local function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + s = s .. ' ' .. k .. ' = ' .. dump(v) .. ',' + end + s = string.sub(s, 1, -2) + return s .. ' }' + else + return tostring(o) + end +end + +local function reducer1(state, action) + state = state or { + value = {0} + } + if action.type == 'counter/incremented' then + return { + value = {state.value[1] + 1} + } + elseif action.type == 'counter/decremented' then + return { + value = {state.value[1] - 2} + } + else + return state + end +end + +local function reducer2(state, action) + state = state or { + value = 0 + } + if action.type == 'counter/incremented' then + return { + value = state.value + 2 + } + elseif action.type == 'counter/decremented' then + return { + value = state.value - 1 + } + else + return state + end +end + +local newReducer = redux.combineReducers({ + r1 = reducer1, + r2 = reducer2 +}) + + +redux.createStore(newReducer) + + +local function s(pState, cState) + print('Previous State: ' .. dump(pState), '\nCurrent State: ' .. dump(cState) .. '\n\n') +end + + +redux.store.subscribe(s) + +redux.store.dispatch({type = 'counter/incremented'}) +redux.store.dispatch({type = 'counter/decremented'}) +redux.store.dispatch({type = 'counter/incremented'}) diff --git a/lua_modules/node_redux/isPlainObject_utils.lua b/lua_modules/node_redux/isPlainObject_utils.lua new file mode 100644 index 0000000000..d2a61e7b58 --- /dev/null +++ b/lua_modules/node_redux/isPlainObject_utils.lua @@ -0,0 +1,27 @@ +local pairs, type = pairs, type + +local function _checkObect(obj) + local check = true + for _, v in pairs(obj) do + local obType = type(v) + if (obType == 'string' or obType == 'number' or obType == 'nil' or obType == 'boolean') then + check = check and true + elseif(obType == 'table') then + check = check and _checkObect(v) + else + check = false + break + end + end + return check +end + +local isPlainObject = function (obj) + if(type(obj) ~= 'table' or obj == nil) then + return false + end + return _checkObect(obj) +end + + +return isPlainObject diff --git a/lua_modules/node_redux/node_redux.lua b/lua_modules/node_redux/node_redux.lua new file mode 100644 index 0000000000..426c6667db --- /dev/null +++ b/lua_modules/node_redux/node_redux.lua @@ -0,0 +1,154 @@ +local require, type, error, ipairs, table, select = require, type, error, ipairs, table, select + +local isPlainObject = require('isPlainObject_utils') +local ActionTypes = require('actionType_utils') +local combineReducers = require('combineReducers') + +local redux, store +do + local function createStore(reducer, preloadedState, enhancer, ...) + if(type(reducer) ~= 'function') then + error("Expected the root reducer to be a function. Instead, received: "..type(reducer), 2) + end + + local arg_4 = select(1, ...) + + if((type(preloadedState) == 'function' and type(enhancer) == 'function') + or (type(enhancer) == 'function' and type(arg_4) == 'function') ) then + error('It looks like you are passing several store enhancers to ' .. + 'createStore(). This is not supported. Instead, compose them ' .. + 'together to a single function.', 2) + end + + if(type(preloadedState) == 'function' and type(enhancer) == 'nil') then + enhancer = preloadedState + preloadedState = nil + end + + if(type(enhancer) ~= 'nil') then + if(type(enhancer) ~= 'function') then + error('Expected the enhancer to be a function. Instead, received: ' .. type(enhancer), 2) + end + + return enhancer(createStore)(reducer, preloadedState) + end + + local currentReducer, currentState, currentListeners, nextListeners, isDispatching, countListeners + do + currentReducer = reducer + currentState = preloadedState + currentListeners = {} + nextListeners = currentListeners + isDispatching = false + countListeners = 0 + + local function copy(array) + if type(array) ~= 'table' then return array end + local res = {} + local index = 0 + for _, v in ipairs(array) do + res[index] = v + index = index + 1 + end + return res + end + + local function ensureCanMutateNextListeners() + if(nextListeners == currentListeners) then + nextListeners = copy(currentListeners) + end + end + + local function getState() + if(isDispatching) then + error('You may not call store.getState() while the reducer is executing. ' .. + 'The reducer has already received the state as an argument. ' .. + 'Pass it down from the top reducer instead of reading it from the store.', 2) + end + + return currentState + end + + local function subscribe(listener) + if(type(listener) ~= 'function') then + error('You may not call store.subscribe() while the reducer is executing. ' .. + 'If you would like to be notified after the store has been updated, subscribe from a ' .. + 'component and invoke store.getState() in the callback to access the latest state. ', 2) + end + + local isSubscribed = true + ensureCanMutateNextListeners() + table.insert(nextListeners, listener) + + countListeners = countListeners + 1 + + local function unsubscribe() + if (not isSubscribed) then + return + end + + if (isDispatching) then + error('You may not unsubscribe from a store listener while the reducer is executing.', 2) + end + isSubscribed = false + ensureCanMutateNextListeners() + table.remove(nextListeners, countListeners) + currentListeners = nil + end + return unsubscribe + end + + local function dispatch(action) + if (not isPlainObject(action)) then + error("Actions must be plain objects.", 2) + end + + if (type(action.type) == 'nil') then + error('Actions may not have an undefined "type" property. ' .. + 'You may have misspelled an action type string constant.', 2) + end + + if(isDispatching) then + error('Reducers may not dispatch actions.') + end + isDispatching = true + local previousState = currentState + currentState = currentReducer(currentState, action) + isDispatching = false + currentListeners = nextListeners + local listeners = currentListeners + for _, listener in ipairs(listeners) do + listener(previousState, currentState) + end + return action + end + + local function replaceReducer(nextReducer) + if(type(nextReducer) ~= 'function') then + error('Expected the nextReducer to be a function. Instead, received: '..type(nextReducer), 2) + end + + currentReducer = nextReducer + + dispatch({ type = ActionTypes.REPLACE }) + end + + dispatch({ type = ActionTypes.INIT }) + + store = { + dispatch = dispatch, + subscribe = subscribe, + getState = getState, + replaceReducer = replaceReducer, + } + redux.store = store + end + end + + redux = { + createStore = createStore, + combineReducers = combineReducers, + } +end + +return redux From 31f8b4c4388b2fab01ebb71eabd01dcbe16bbf7c Mon Sep 17 00:00:00 2001 From: s Date: Fri, 9 Apr 2021 23:04:49 +0530 Subject: [PATCH 2/3] :rocket: Add doc of APIs --- docs/lua-modules/node_redux.md | 255 +++++++++++++++++++++++++++++++ lua_modules/node_redux/README.md | 67 +------- 2 files changed, 257 insertions(+), 65 deletions(-) create mode 100644 docs/lua-modules/node_redux.md diff --git a/docs/lua-modules/node_redux.md b/docs/lua-modules/node_redux.md new file mode 100644 index 0000000000..b7cdb79cb9 --- /dev/null +++ b/docs/lua-modules/node_redux.md @@ -0,0 +1,255 @@ +# NodeRedux Module +Redux is a predictable state container for lua apps and NodeMCU. + +## Influences + +NodeRedux evolves the ideas of [Redux](https://redux.js.org/), +which help to maintain state lua base app basically NodeMCU devices. + +## Basic Example + +The whole global state of your app is stored in an object tree inside a single _store_. +The only way to change the state tree is to create an _action_, +an object describing what happened, and _dispatch_ it to the store. +To specify how state gets updated in response to an action, you write pure _reducer_ +functions that calculate a new state based on the old state and the action. + + +```lua +local redux = require('redux') -- optional line for nodemcu, only need for lua app + +-- This is a reducer - a function that takes a current state value and an +-- action object describing "what happened", and returns a new state value. +-- A reducer's function signature is: (state, action) => newState +-- +-- The NodeRedux state should contain only plain Lua table. +-- The root state value is usually an table. It's important that you should +-- not mutate the state table, but return a new table if the state changes. +-- +-- You can use any conditional logic you want in a reducer. In this example, +-- we use a if statement, but it's not required. + +local function counterReducer(state, action) + state = state or { value = 0 } + if action.type == 'counter/incremented' then + return { value = state.value + 2 } + elseif action.type == 'counter/decremented' then + return { value = state.value - 1 } + else + return state + end +end + +redux.createStore(counterReducer) + +local function console(pState, cState) + print('Previous State: '.. pState.value, 'Current State: '.. cState.value) +end + +redux.store.subscribe(console) + +redux.store.dispatch({type = 'counter/incremented'}) +-- {value = 1} +redux.store.dispatch({type = 'counter/incremented'}) +-- {value = 2} +redux.store.dispatch({type = 'counter/decremented'}) +-- {value = 1} +``` + +[Here](../../lua_modules/node_redux/createStore_example.lua) the example code + +### Require +```lua +local redux = require("node_redux") +``` + +## redux.createStore() +Creates a Redux store that holds the complete state tree of device. There should only be a single store in your device. + +#### Syntax +`redux.createStore(reducer)` + +#### Parameters +- `reducer`(Function): A reducing function that returns the next state tree, + given the current state tree, and an action to handle. +- `preloadedState`(any): The initial state. You may optionally specify it to + hydrate the state from the server in universal apps, or to restore a previously + serialized user session. If you produced reducer with combineReducers, this must + be a plain object with the same shape as the keys passed to it. Otherwise, + you are free to pass anything that your reducer can understand. +- `enhancer` (Function): The store enhancer. You may optionally specify it to + enhance the store with third-party capabilities such as middleware, time travel, + persistence, etc. The only store enhancer that ships with Redux is + applyMiddleware() ***TODO: Support added in near next PR*** + +#### Returns +- `nil` + +## redux.combineReducers() +As your app grows more complex, you'll want to split your reducing function into +separate functions, each managing independent parts of the state. +The combineReducers helper function turns an object whose values are different +reducing functions into a single reducing function you can pass to createStore. + +#### Syntax +```lua +redux.combineReducers({ + reducer_one = reducer_1, + reducer_two = reducer_2, + ... +}) +``` + +#### Parameters +- `table`(Table of Functions values): A reducing functions that returns the next + state tree, given the current state tree, and an action to handle. +#### Returns + +- `combined_reducer` (Function) A single combined reducer build by combining + all reducers + +#### Example + +```lua +local redux = require('redux') -- optional line for nodemcu, only need for lua app + +-- This is a reducer - a function that takes a current state value and an +-- action object describing "what happened", and returns a new state value. +-- A reducer's function signature is: (state, action) => newState +-- +-- The NodeRedux state should contain only plain Lua table. +-- The root state value is usually an table. It's important that you should +-- not mutate the state table, but return a new table if the state changes. +-- +-- You can use any conditional logic you want in a reducer. In this example, +-- we use a if statement, but it's not required. + +local function counterReducer(state, action) + state = state or { value = 0 } + if action.type == 'counter/incremented' then + return { value = state.value + 2 } + elseif action.type == 'counter/decremented' then + return { value = state.value - 1 } + else + return state + end +end + +local function inverseCounterReducer(state, action) + state = state or { value = 0 } + if action.type == 'counter/incremented' then + return { value = state.value - 1 } + elseif action.type == 'counter/decremented' then + return { value = state.value + 1 } + else + return state + end +end + +local reducer = redux.combineReducers({ + counterReducer = counterReducer, + inverseCounterReducer = inverseCounterReducer, +}) + +redux.createStore(reducer) + +local function console(pState, cState) + print('Previous State: counterReducer' .. + pState.counterReducer.value, + 'Current State: counterReducer' .. + cState.counterReducer.value + ) + print('Previous State: inverseCounterReducer' .. + pState.inverseCounterReducer.value, + 'Current State: inverseCounterReducer' .. + cState.inverseCounterReducer.value + ) +end + +redux.store.subscribe(console) + +redux.store.dispatch({type = 'counter/incremented'}) +-- {counterReducer = { value = 2 }, inverseCounterReducer = { value = -1 } } +redux.store.dispatch({type = 'counter/incremented'}) +-- {counterReducer = { value = 4 }, inverseCounterReducer = { value = -2 } } +redux.store.dispatch({type = 'counter/decremented'}) +-- {counterReducer = { value = 3 }, inverseCounterReducer = { value = -1 } } +``` + +## redux.store.getState() +Returns the current state tree of your application. It is equal to the last value +returned by the store's reducer. +#### Syntax +`redux.store.getState()` + +#### Parameters +None + +#### Returns +The current state tree of your application. + +## redux.store.dispatch() +Dispatches an action. This is the only way to trigger a state change. + +The store's reducing function will be called with the current getState() result, +and the given action synchronously. Its return value will be considered the next state. +It will be returned from getState() from now on, and the change listeners will +immediately be notified. +#### Syntax +`redux.store.dispatch(action)` + +#### Parameters +- `action` (_Table_): A plain object describing the change that makes sense for + your application. Actions must have a type field that indicates the `type` + of action being performed. Types can be defined as constants and imported + from another module. It's better to use strings for `type` than Symbols + because strings are serializable. + +#### Returns +(_Table_): The dispatched action + +## redux.store.subscribe() +Adds a change listener. It will be called any time an action is dispatched, and some +part of the state tree may potentially have changed. You may then use `1st arg` for +previous state and `2nd arg` for current state. + +- The listener should only call dispatch() either in response to user actions or + under specific conditions (e.g. dispatching an action when the store has + a specific field). Calling dispatch() without any conditions is technically + possible, however it leads to an infinite loop as every dispatch() call + usually triggers the listener again. + +#### Syntax +`redux.store.subscribe(listener)` + +#### Parameters +- `listener` (Function): The callback to be invoked any time an action has been + dispatched, and the state tree might have changed. You may then use `1st arg` for + previous state and `2nd arg` for current state + +#### Example - listener +```lua +local function listener(pState, cState) + print('Previous State: counterReducer' .. + pState.counterReducer.value, + 'Current State: counterReducer' .. + cState.counterReducer.value + ) + print('Previous State: inverseCounterReducer' .. + pState.inverseCounterReducer.value, + 'Current State: inverseCounterReducer' .. + cState.inverseCounterReducer.value + ) +end +``` + +## redux.store.replaceReducer() +It is an advanced API. You might need this if your app implements code +splitting, and you want to load some of the reducers dynamically. +You might also need this if you implement a hot reloading mechanism for Redux. + +#### Syntax +`redux.store.replaceReducer(nextReducer)` + +#### Parameters +- `nextReducer` (Function): The next reducer for the store to use. diff --git a/lua_modules/node_redux/README.md b/lua_modules/node_redux/README.md index 24ff791069..72c06b5335 100644 --- a/lua_modules/node_redux/README.md +++ b/lua_modules/node_redux/README.md @@ -1,66 +1,3 @@ -# NodeRedux -Redux is a predictable state container for lua apps and NodeMCU. - -## Influences - -NodeRedux evolves the ideas of [Redux](https://redux.js.org/), -which help to maintain state lua base app basically NodeMCU devices. - - -## Basic Example - -The whole global state of your app is stored in an object tree inside a single _store_. -The only way to change the state tree is to create an _action_, -an object describing what happened, and _dispatch_ it to the store. -To specify how state gets updated in response to an action, you write pure _reducer_ -functions that calculate a new state based on the old state and the action. - - -```lua -local redux = require('redux') -- optional line for nodemcu, only need for lua app - --- This is a reducer - a function that takes a current state value and an --- action object describing "what happened", and returns a new state value. --- A reducer's function signature is: (state, action) => newState --- --- The NodeRedux state should contain only plain Lua table. --- The root state value is usually an table. It's important that you should --- not mutate the state table, but return a new table if the state changes. --- --- You can use any conditional logic you want in a reducer. In this example, --- we use a if statement, but it's not required. - -local function counterReducer(state, action) - state = state or { value = 0 } - if action.type == 'counter/incremented' then - return { value = state.value + 2 } - elseif action.type == 'counter/decremented' then - return { value = state.value - 1 } - else - return state - end -end - -redux.createStore(counterReducer) - -local function console(pState, cState) - print('Previous State: '.. pState.value, 'Current State: '.. cState.value) -end - -redux.store.subscribe(console) - -redux.store.dispatch({type = 'counter/incremented'}) --- {value = 1} -redux.store.dispatch({type = 'counter/incremented'}) --- {value = 2} -redux.store.dispatch({type = 'counter/decremented'}) --- {value = 1} -``` - -[Here](./createStore_example.lua) the example code - -## License - -[MIT](LICENSE) - +# NodeRedux Module +Documentation for this Lua module is available in the [node_redux.md](../../docs/lua-modules/node_redux.md) file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section. From 1f315ff29b1fbba1f3a2be963a4832239e674a8b Mon Sep 17 00:00:00 2001 From: s Date: Fri, 9 Apr 2021 23:36:13 +0530 Subject: [PATCH 3/3] :hammer: Fix doc ci --- docs/lua-modules/node_redux.md | 35 +++++++++------------------------- mkdocs.yml | 1 + 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/docs/lua-modules/node_redux.md b/docs/lua-modules/node_redux.md index b7cdb79cb9..a0300fef2f 100644 --- a/docs/lua-modules/node_redux.md +++ b/docs/lua-modules/node_redux.md @@ -1,12 +1,17 @@ # NodeRedux Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2021-04-09 | [Shubham Srivastava](https://github.com/shubham-sri) | [Shubham Srivastava](https://github.com/shubham-sri) | [node_redux.lua](../../lua_modules/node_redux/node_redux.lua) | + +
Redux is a predictable state container for lua apps and NodeMCU. -## Influences +### Influences NodeRedux evolves the ideas of [Redux](https://redux.js.org/), which help to maintain state lua base app basically NodeMCU devices. -## Basic Example +### Basic Example The whole global state of your app is stored in an object tree inside a single _store_. The only way to change the state tree is to create an _action_, @@ -16,18 +21,7 @@ functions that calculate a new state based on the old state and the action. ```lua -local redux = require('redux') -- optional line for nodemcu, only need for lua app - --- This is a reducer - a function that takes a current state value and an --- action object describing "what happened", and returns a new state value. --- A reducer's function signature is: (state, action) => newState --- --- The NodeRedux state should contain only plain Lua table. --- The root state value is usually an table. It's important that you should --- not mutate the state table, but return a new table if the state changes. --- --- You can use any conditional logic you want in a reducer. In this example, --- we use a if statement, but it's not required. +local redux = require('redux') local function counterReducer(state, action) state = state or { value = 0 } @@ -111,18 +105,7 @@ redux.combineReducers({ #### Example ```lua -local redux = require('redux') -- optional line for nodemcu, only need for lua app - --- This is a reducer - a function that takes a current state value and an --- action object describing "what happened", and returns a new state value. --- A reducer's function signature is: (state, action) => newState --- --- The NodeRedux state should contain only plain Lua table. --- The root state value is usually an table. It's important that you should --- not mutate the state table, but return a new table if the state changes. --- --- You can use any conditional logic you want in a reducer. In this example, --- we use a if statement, but it's not required. +local redux = require('redux') local function counterReducer(state, action) state = state or { value = 0 } diff --git a/mkdocs.yml b/mkdocs.yml index d606d8eaa8..27ced969e7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ pages: - 'lm92': 'lua-modules/lm92.md' - 'mcp23008': 'lua-modules/mcp23008.md' - 'mcp23017': 'lua-modules/mcp23017.md' + - 'node_redux': 'lua-modules/node_redux.md' - 'redis': 'lua-modules/redis.md' - 'telnet': 'lua-modules/telnet.md' - 'yeelink': 'lua-modules/yeelink.md'