diff --git a/docs/canner.schema.js b/docs/canner.schema.js index 07f2590e..ea21bce1 100644 --- a/docs/canner.schema.js +++ b/docs/canner.schema.js @@ -80,7 +80,11 @@ export default - + - + diff --git a/docs/schema/realWorld/posts.schema.js b/docs/schema/realWorld/posts.schema.js index a6b88338..05d70198 100644 --- a/docs/schema/realWorld/posts.schema.js +++ b/docs/schema/realWorld/posts.schema.js @@ -26,6 +26,7 @@ const Posts = ({attributes}) => diff --git a/docs/schema/realWorld/users.schema.js b/docs/schema/realWorld/users.schema.js index bcb522b4..ad9bc732 100644 --- a/docs/schema/realWorld/users.schema.js +++ b/docs/schema/realWorld/users.schema.js @@ -45,16 +45,21 @@ const Users = ({attributes}) => ( }] }]} /> - - - + name === '123', + message: 'Should be 123' + } + }}/> + + - + - + { loading: Loading, }); } - component = this.wrapByHOC(component, ['title', 'onDeploy', 'deploy', 'request', 'relation', 'query', 'cache', 'route', 'id', 'context', 'errorCatch'] || []); + component = this.wrapByHOC(component, ['title', 'onDeploy', 'validation', 'deploy', 'request', 'relation', 'query', 'cache', 'route', 'id', 'context', 'errorCatch'] || []); } if (!component) { diff --git a/src/components/Provider.js b/src/components/Provider.js index 60077d8b..8ef949e0 100644 --- a/src/components/Provider.js +++ b/src/components/Provider.js @@ -146,7 +146,16 @@ export default class Provider extends React.PureComponent { actions = removeIdInCreateArray(actions); const mutation = objectToQueries(actionToMutation(actions[0]), false); const variables = actionsToVariables(actions, schema); - variables.payload = this.onDeploy(key, fromJS(variables.payload)).toJS(); + const queryVariables = this.query.getVairables(); + const query = gql`${this.query.toGQL(key)}`; + const cachedData = client.readQuery({query, variables: queryVariables}); + const mutatedData = cachedData[key]; + const {error} = this.onDeploy(key, fromJS(mutatedData)); + if (error) { + return Promise.reject(); + } + + // variables.payload = data.toJS(); return client.mutate({ mutation: gql`${mutation}`, variables diff --git a/src/hocs/cache.js b/src/hocs/cache.js index bf282326..11e34505 100644 --- a/src/hocs/cache.js +++ b/src/hocs/cache.js @@ -177,12 +177,22 @@ export default function withCache(Com: React.ComponentType<*>, options: { if (!this.actionManager) { return deploy(key, id); } + const originData = this.state[key]; let actions = this.actionManager.getActions(key, id); - actions = actions.map(action => { - const {key, value} = action.payload; - action.payload.value = this._executeOnDeployCallback(key, value); - return action; - }) + const mutatedData = actions.reduce((result, action) => { + return mutate(result, action); + }, originData); + const {error} = this._executeOnDeployCallback(key, mutatedData.get(key)); + if (error) { + return Promise.reject(); + } + // actions = actions.map(action => { + // const {key, value} = action.payload; + // hasError = hasError || error; + // action.payload.value = data; + // return action; + // }); + // $FlowFixMe this.actionManager.removeActions(key, id); request(actions); diff --git a/src/hocs/index.js b/src/hocs/index.js index fba0a8d0..0a9d1cc6 100644 --- a/src/hocs/index.js +++ b/src/hocs/index.js @@ -10,6 +10,7 @@ import containerRouter from './containerRouter'; import onDeploy from './onDeploy'; import context from './connectContext'; import errorCatch from './errorCatch'; +import validation from './validation'; export default { errorCatch, @@ -24,5 +25,6 @@ export default { title, containerRouter, onDeploy, - context + context, + validation }; \ No newline at end of file diff --git a/src/hocs/onDeploy.js b/src/hocs/onDeploy.js index 8cdb98ef..a9ea043c 100644 --- a/src/hocs/onDeploy.js +++ b/src/hocs/onDeploy.js @@ -46,16 +46,19 @@ export default function withOndeploy(Com: React.ComponentType<*>) { return onDeploy(arg1, callback); } else { // first arguments is a function - return onDeploy(this.key, v => { + return onDeploy(this.key, result => { let restPathArr = refId.getPathArr(); - if (this.id) { - restPathArr = restPathArr.slice(2); - } else { + // if (this.id) { + // restPathArr = restPathArr.slice(2); + // } else { restPathArr = restPathArr.slice(1); + // } + const {paths, value} = getValueAndPaths(result.data, restPathArr); + return { + ...result, + // $FlowFixMe + data: result.data.setIn(paths, arg1(value)) } - const {paths, value} = getValueAndPaths(v, restPathArr); - // $FlowFixMe - return v.setIn(paths, arg1(value)); }); } } diff --git a/src/hocs/route.js b/src/hocs/route.js index 39b62c25..df78b2bd 100644 --- a/src/hocs/route.js +++ b/src/hocs/route.js @@ -52,6 +52,11 @@ export default function withRoute(Com: React.ComponentType<*>) { deploying: false }); goTo(routes[0]); + }) + .catch(() => { + this.setState({ + deploying: false + }); }); } diff --git a/src/hocs/validation.js b/src/hocs/validation.js new file mode 100644 index 00000000..7c453e37 --- /dev/null +++ b/src/hocs/validation.js @@ -0,0 +1,128 @@ +// @flow + +import * as React from 'react'; +import RefId from 'canner-ref-id'; +import {Map, List} from 'immutable'; +import Ajv from 'ajv'; +import {isEmpty} from 'lodash'; + +type Props = { + refId: RefId, + keyName: string, + routes: Array, + pattern: string, + onDeploy: (key: string, callback: any => any) => string, + removeOnDeploy: (key: string, id: ?string) => void, + rootValue: any, + validation: Object, +}; + +type State = { + error: boolean, + errorInfo: Array +} + +export default function withValidation(Com: React.ComponentType<*>) { + return class ComponentWithValition extends React.Component { + key: string; + id: ?string; + state = { + error: false, + errorInfo: [] + } + + componentDidMount() { + const {refId, validation = {}, onDeploy, required = false} = this.props; + const key = refId.getPathArr()[0]; + const ajv = new Ajv(); + const validate = ajv.compile(validation); + if (isEmpty(validation) && !required) { + // no validation + return; + } + let paths = refId.getPathArr(); + const {validator = {validate: () => true, message: ''}} = validation; + paths = paths.slice(1); + onDeploy(key, result => { + const {value} = getValueAndPaths(result.data, paths); + const isRequiredValid = required ? Boolean(value) : true; + const customValid = validator.validate(value); + if (customValid && isRequiredValid && validate((value && value.toJS) ? value.toJS() : value)) { + this.setState({ + error: false, + errorInfo: [] + }); + return result; + } + const errorInfo = [].concat(isRequiredValid ? []:{message: 'Required Field!'}) + .concat(validate.errors || []) + .concat(customValid ? []:{message: validator.message}); + this.setState({ + error: true, + errorInfo: errorInfo + }); + return { + ...result, + error: true, + errorInfo: errorInfo + } + }); + } + + render() { + const {error, errorInfo} = this.state; + return + + { + error && {errorInfo[0].message} + } + + } + }; +} + +export function splitRefId({ + refId, + rootValue, + pattern +}: { + refId: RefId, + rootValue: any, + pattern: string +}) { + const [key, index] = refId.getPathArr(); + let id; + if (pattern.startsWith('array')) { + id = rootValue.getIn([key, index, 'id']); + } + return { + key, + id + } +} + +export function getValueAndPaths(value: Map, idPathArr: Array) { + return idPathArr.reduce((result: any, key: string) => { + let v = result.value; + let paths = result.paths; + if (Map.isMap(v)) { + if (v.has('edges') && v.has('pageInfo')) { + v = v.getIn(['edges', key, 'node']); + paths = paths.concat(['edges', key, 'node']); + } else { + v = v.get(key); + paths = paths.concat(key); + } + } else if (List.isList(v)) { + v = v.get(key); + paths = paths.concat(key); + } + return { + value: v, + paths + } + }, { + value, + paths: [] + }); +} \ No newline at end of file diff --git a/src/onDeployManager/index.js b/src/onDeployManager/index.js index 9af55db1..d2709924 100644 --- a/src/onDeployManager/index.js +++ b/src/onDeployManager/index.js @@ -17,8 +17,7 @@ export class OnDeployManager { value: any }): any => { const callbacks = this.findCallback(key); - // console.log(value, callbacks, callbacks.reduce((result, callback) => callback(result), value)); - return callbacks.reduce((result, callback) => callback(result), value); + return callbacks.reduce((result, callback) => callback(result), {data: value}); } findCallback = (key: string): Array => { diff --git a/yarn.lock b/yarn.lock index b42fe1ff..24bad9a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1559,7 +1559,7 @@ ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0: +ajv@^6.1.0, ajv@^6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360" dependencies: