From 2b7a679156cf6d9b33691f7e313b483ccec64c46 Mon Sep 17 00:00:00 2001 From: jshrt Date: Thu, 25 Jul 2019 16:04:17 +0200 Subject: [PATCH] feat(wip): local state --- examples/todo/components/Form.jsx | 56 ++++++++ examples/todo/components/TodoList.jsx | 8 ++ src/index.js | 7 +- src/local-state.js | 195 ++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 examples/todo/components/Form.jsx create mode 100644 src/local-state.js diff --git a/examples/todo/components/Form.jsx b/examples/todo/components/Form.jsx new file mode 100644 index 0000000..f72740b --- /dev/null +++ b/examples/todo/components/Form.jsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import { View, LocalState } from '../../../src' + +const { + state, + setState +} = LocalState({ + schema: { + name: "", + age: 0, + }, + + setter: (mutation) => { + return { + name: mutation.name ? mutation.name.toLowerCase() : "", + age: mutation.age + ? mutation.age > 0 + ? mutation.age * 2 + : mutation.age * 10 + : mutation.age + 1, + } + }, + + getter: (state) => { + const [ + firstName, lastName + ] = state.name.split(" ") + + return { + firstName, + lastName, + } + } +}) + +export default View( + () => +
+
+      {
+        

+ name: { state.name } + age: { state.age } +

+ } +
+
  • + + setState("name")(value)}/> +
  • +
  • + + setState("age")(value)}/> +
  • +
    +) diff --git a/examples/todo/components/TodoList.jsx b/examples/todo/components/TodoList.jsx index 7d8aad4..f818e45 100644 --- a/examples/todo/components/TodoList.jsx +++ b/examples/todo/components/TodoList.jsx @@ -4,10 +4,18 @@ import { createTodo, login, logout } from '../actions.jsx' import { userState, todoState } from '../state.jsx' import ListItem from './ListItem.jsx' +import Form from "./Form" + +console.log({ + todoState, userState +}) export default View(() => { return (
    +
    +
    +
    Hi, we have {todoState.size} total todos. Pending:
      diff --git a/src/index.js b/src/index.js index 25b9cef..9b1005d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,6 @@ -import { configure } from 'mobx' - -configure({ enforceActions: 'strict', isolatedGlobalState: true }) - export * from 'mobx' export { Model, Propose } from './model' export { State } from './state' -export { observer as View } from 'mobx-react' \ No newline at end of file +export { observer as View } from 'mobx-react' +export { LocalState } from "./local-state" diff --git a/src/local-state.js b/src/local-state.js new file mode 100644 index 0000000..569165b --- /dev/null +++ b/src/local-state.js @@ -0,0 +1,195 @@ +/** + * + * @example: + * import { LocalState } from "samx" + * + * const { state, setState } = LocalState({ + * schema: { + * }, + * setter: (mutation) => { + * return mutation + * }, + * getter: (state) => { + * return state + * }, + * }) + * */ + +import { observable, set } from "mobx" + +const LocalStateError = (msg) => new Error(msg) + +const Errors = { + setter: { + InvalidMutation: LocalStateError("Invalid Mutation"), + notAllowed: LocalStateError("Operation not allowed"), + }, + setState: { + args: { + one: LocalStateError("Invalid arguments. First argument should be either a string prop name or a mutation object"), + two: LocalStateError("Invalid arguments. First argument must be a string, second must be an object/number/string"), + invalid: LocalStateError("Invalid argument list.") + } + }, + Constructor: { + InvalidArgs: { + first: { + schema: LocalStateError("Schema must be an object"), + } + } + }, + getter: { + prop: { + invalid: LocalStateError("Invalid property access. No such property is defined in the getter."), + } + }, +} + +/** + * Encapsulated Mobx.set for validation and interfacing purposes + * + * @param {Mobx.observable} observable + * @param {Object} mutation + */ +const Setter = (interceptor) => (observableState, mutation) => { + console.log(observableState, mutation) + Object.keys(mutation).forEach( + (key) => { + if (typeof mutation[key] === "function") { + throw Errors.setter.InvalidMutation + } + } + ) + + if (typeof interceptor === "function") { + const newMutation = interceptor(toJS(observableState), mutation) + set(observableState, newMutation) + } else { + set(observableState, mutation) + } + + return true +} + +/** + * + * @example: + * first: + * setState({ property: value }) + * + * second: + * setState("property")(value) + * + * third: + * setState("property", value) + */ +const SetState = (observableState, interceptor) => (...args) => { + const interceptedSetter = Setter(interceptor) + const argsLength = args.length + + switch (argsLength) { + case 1: + const arg = args[0] + if (typeof arg === "object") { + return interceptedSetter(observableState, arg) + } else if (typeof arg === "string") { + return (mutation) => interceptedSetter(observableState, mutation) + } else throw Errors.setState.args.one + + case 2 || argsLength > 2: + const [ first, second ] = args + + if ( + typeof first === "string" + && ( + typeof second === "object" + || typeof second === "string" + || typeof second === "number" + ) + ) { + return interceptedSetter(observableState, { + [first]: second, + }) + } else throw Errors.setState.args.two + + default: + throw Errors.setState.args.invalid + } +} + +/** + * + * @param {Object} opts + * @param {Object} opts.schema + * @optional {Function} opts.setter + * @optional {Function} opts.getter + * + */ +const LocalState = (opts) => { + const options = {} + + if (typeof opts.schema !== "object") { + throw Errors.Constructor.InvalidArgs.first.schema + } + + options.schema = opts.schema + + if (opts.setter && typeof opts.setter === "Function") { + options.setter = opts.setter + } + + if (opts.getter && typeof opts.getter === "Function") { + options.getter = opts.getter + } + + const observableState = observable(options.schema) + + const state = new Proxy({ + __data: observableState, + + getCurrentState() { + return toJS(observableState) + }, + + __getObservable() { + return observableState + }, + + }, { + get(target, prop) { + console.log({ target, prop }) + if (typeof target[prop] === "function") { + const r = Reflect.get(...arguments) + console.log({ target, prop, value: target[prop], r }) + return r + } + + if (options.getter) { + const currentValue = options.getter(target.__data) + + if (prop in Object.keys(currentValue)) { + return currentValue[prop] + } else { + throw Errors.getter.prop.invalid + } + } else { + return target.__data[prop] + } + }, + + set() { + throw Errors.setter.notAllowed + } + }) + + const setState = SetState(state.__getObservable(), options.setter || undefined) + + return { + state, + setState, + } +} + +export { + LocalState, +}