diff --git a/CHANGELOG.md b/CHANGELOG.md index a67f11d..db7aff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# 0.1.5 +Change selector payload type of data, now it's *Immutable JSON or unndefined* please see [test-source](https://github.com/edtoken/redux-tide/blob/master/test/selector.spec.js#L48) +Add query builder result to action store and selector response `args` +Implement `action.remove` +Implement `action.recall` +Improve documentation +Improve examples, add new example +Add code sandbox examples +Add new badges + +# 0.1.4 + # 0.1.3 Fix critical bug in method `Action.empty` small refactoring action.js, selector.js files diff --git a/README.md b/README.md index 8de1e2e..0a72e9d 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,16 @@ ActionCreator + ActionSelector, reducers are created automatically [coverage-report](https://edtoken.github.io/redux-tide/coverage/lcov-report/index.html) -[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![npm version](https://badge.fury.io/js/redux-tide.svg)](https://badge.fury.io/js/redux-tide) [![Build Status](https://api.travis-ci.org/edtoken/redux-tide.svg?branch=master)](https://travis-ci.org/edtoken/redux-tide) [![Maintainability](https://api.codeclimate.com/v1/badges/5952e9edfa038e49658f/maintainability)](https://codeclimate.com/github/edtoken/redux-tide/maintainability) [![npm downloads](https://img.shields.io/npm/dm/redux-tide.svg?style=flat-square)](https://www.npmjs.com/package/redux-tide) [![Coverage Status](https://coveralls.io/repos/github/edtoken/redux-tide/badge.svg?branch=master)](https://coveralls.io/github/edtoken/redux-tide?branch=master) -[![Inline docs](https://inch-ci.org/github/edtoken/redux-tide.svg?branch=master)](https://inch-ci.org/github/edtoken/redux-tide) +[![Inline docs](https://inch-ci.org/github/edtoken/redux-tide.svg?branch=master)](https://inch-ci.org/github/edtoken/redux-tide) +[![dependencies Status](https://david-dm.org/edtoken/redux-tide/status.svg)](https://david-dm.org/edtoken/redux-tide) +[![devDependencies Status](https://david-dm.org/edtoken/redux-tide/dev-status.svg)](https://david-dm.org/edtoken/redux-tide?type=dev) [![HitCount](http://hits.dwyl.com/edtoken/redux-tide.svg)](http://hits.dwyl.com/edtoken/redux-tide) [![NPM](https://nodei.co/npm/redux-tide.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/redux-tide/) @@ -96,7 +98,7 @@ npm install redux-thunk --save ------ ### Discussion You can connect to [Gitter chat room](https://gitter.im/practice-feature/redux-tide) -[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/practice-feature/redux-tide](https://badges.gitter.im/practice-feature/redux-tide.svg)](https://gitter.im/practice-feature/redux-tide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ### Usage 1. You might install library @@ -227,12 +229,22 @@ export const createNewPost = createAction( ] ) +export const getPostById = createAction( + postsSchema, + get, + (postId) => { + return (dispatch, getState)=>{ + // return Promise (axios call or other) + } + } +) + // basic redux action can be use export const openEditPost = (postId) => { return { type:OPEN_EDIT, postId - } + } } ``` @@ -324,6 +336,7 @@ import {getActionData} from 'redux-tide'; ``` {String} actionId - your action id {*} sourceResult - your source response from server (not mapped response) +{*} args - your args from query builder action method {String} status - pending|success|error {Number} time - timestamp of action {Boolean} hasError - has error or not diff --git a/lib/action.js b/lib/action.js index 726f6b9..06f0a33 100644 --- a/lib/action.js +++ b/lib/action.js @@ -69,9 +69,10 @@ var makeActionHandler = function makeActionHandler(Action, actionId, parentActio * * @param {String} error - action error text * @param {Object|Array} payloadSource - response from action result + * @param {Object|Array} args - arguments from queryBuilder * @returns {Object} - action dispatch body */ - return function (error, payloadSource, sourceResult) { + return function (error, payloadSource, sourceResult, args) { if (status === 'success' && payloadSource === undefined) { error = 'Empty payload'; status = 'error'; @@ -117,10 +118,11 @@ var makeActionHandler = function makeActionHandler(Action, actionId, parentActio * payloadSource: Object|Array * }} */ - return Object.freeze({ + return { time: new Date().getTime(), type: '' + actionId, prefix: _config.ACTION_TYPE_PREFIX, + args: args, actionId: actionId, parentActionId: parentActionId, status: status, @@ -134,22 +136,22 @@ var makeActionHandler = function makeActionHandler(Action, actionId, parentActio sourceResult: sourceResult, payload: payload, payloadSource: payloadSource - }); + }; }; }; var makeResultCallback = function makeResultCallback(responseMapper, success, error) { - return function (dispatch, getState, err, result) { + return function (dispatch, getState, err, result, args) { try { var errorMessage = (0, _helper.parseError)(err); if (errorMessage) { - return dispatch(error(errorMessage, undefined)); + return dispatch(error(errorMessage, undefined, undefined, args)); } - dispatch(success(undefined, responseMapper(result), result)); + dispatch(success(undefined, responseMapper(result), result, args)); } catch (e) { - dispatch(error(String('' + (e.message || e)), undefined)); + dispatch(error(String('' + (e.message || e)), undefined, undefined, args)); throw e; } }; @@ -183,9 +185,9 @@ var makeCallActionMethod = function makeCallActionMethod(resultCallBack) { if (actionResult instanceof Promise) { return actionResult.then(function (resp) { - return resultCallBack(dispatch, getState, false, resp); + return resultCallBack(dispatch, getState, false, resp, args); }).catch(function (err) { - return resultCallBack(dispatch, getState, err, undefined); + return resultCallBack(dispatch, getState, err, undefined, args); }); } @@ -193,19 +195,19 @@ var makeCallActionMethod = function makeCallActionMethod(resultCallBack) { actionResult = actionResult.call(undefined, dispatch, getState); if (!actionResult) { - return resultCallBack(dispatch, getState, undefined, undefined); + return resultCallBack(dispatch, getState, undefined, undefined, args); } if (actionResult instanceof Promise) { return actionResult.then(function (resp) { - return resultCallBack(dispatch, getState, false, resp); + return resultCallBack(dispatch, getState, false, resp, args); }).catch(function (err) { - return resultCallBack(dispatch, getState, err, undefined); + return resultCallBack(dispatch, getState, err, undefined, args); }); } } - return resultCallBack(dispatch, getState, false, actionResult); + return resultCallBack(dispatch, getState, false, actionResult, args); }; }; @@ -377,7 +379,7 @@ var makeAction = function makeAction(actionId, parentActionId, actionSchema, act * * @returns {Undefined} - returns None, only clear action data */ - this.action.empty = function () { + this.action.reset = function () { return function (dispatch, getState) { dispatch({ time: new Date().getTime(), @@ -389,28 +391,28 @@ var makeAction = function makeAction(actionId, parentActionId, actionSchema, act }; }; - // /** - // * Clean entity from entity reducer - // * - // * @memberOf action.makeAction.Action - // * @type {Function} - // * - // * @example - // * store.dispatch(userLoginAction.clean()) - // * - // * @returns {Undefined} - returns None, only clear entity data - // */ - // this.action.clean = () => { - // return (dispatch, getState) => { - // dispatch({ - // time: new Date().getTime(), - // type: ACTION_CLEAN_TYPE_NAME, - // prefix: ACTION_TYPE_PREFIX, - // actionId: this.actionId, - // actionSchema: this.schema - // }) - // } - // } + /** + * Remove action entity id or ids from entities reducer + * + * @memberOf action.makeAction.Action + * @type {Function} + * + * @example + * store.dispatch(userLoginAction.remove()) + * + * @returns {Undefined} - returns None, only remove entity items + */ + this.action.remove = function () { + return function (dispatch, getState) { + dispatch({ + time: new Date().getTime(), + type: _config.ACTION_REMOVE_TYPE_NAME, + prefix: _config.ACTION_TYPE_PREFIX, + actionId: _this.actionId, + actionSchema: _this.schema + }); + }; + }; return this.action; }; diff --git a/lib/config.js b/lib/config.js index c15190a..6193587 100644 --- a/lib/config.js +++ b/lib/config.js @@ -77,13 +77,13 @@ var ENTITIES_REDUCER_NAME = exports.ENTITIES_REDUCER_NAME = ACTION_TYPE_PREFIX + var ACTION_EMPTY_TYPE_NAME = exports.ACTION_EMPTY_TYPE_NAME = ACTION_TYPE_PREFIX + '-empty'; /** - * Action type name for clear entity data from entity reducer + * Action type name for delete entities from state * * @memberOf config * @const * @type {String} */ -var ACTION_CLEAN_TYPE_NAME = exports.ACTION_CLEAN_TYPE_NAME = ACTION_TYPE_PREFIX + '-clean'; +var ACTION_REMOVE_TYPE_NAME = exports.ACTION_REMOVE_TYPE_NAME = ACTION_TYPE_PREFIX + '-remove'; /** * replaced default response mapper to callback diff --git a/lib/reducer.js b/lib/reducer.js index 7f13aac..b42df68 100644 --- a/lib/reducer.js +++ b/lib/reducer.js @@ -37,7 +37,8 @@ var makeActionsReducer = function makeActionsReducer(defaultActionsState) { return state; } - var status = action.status, + var args = action.args, + status = action.status, time = action.time, actionId = action.actionId, payload = action.payload, @@ -52,8 +53,8 @@ var makeActionsReducer = function makeActionsReducer(defaultActionsState) { var entityKey = actionSchema.key; - // action.clear - if (action.type === _config.ACTION_EMPTY_TYPE_NAME) { + // action.clear || action.remove + if (action.type === _config.ACTION_EMPTY_TYPE_NAME || action.type === _config.ACTION_REMOVE_TYPE_NAME) { return state.set(actionId, (0, _immutable.fromJS)(Object.assign({ entityKey: entityKey }, actionDefaultData, { time: time }))); } @@ -68,6 +69,7 @@ var makeActionsReducer = function makeActionsReducer(defaultActionsState) { } actionState = actionState.merge({ + args: args, status: status, time: time, hasError: hasError, diff --git a/lib/selector.js b/lib/selector.js index 9e7d53c..e24cbc3 100644 --- a/lib/selector.js +++ b/lib/selector.js @@ -44,21 +44,21 @@ var getPayloadIds = function getPayloadIds(dataKey, isArray, actionState, stateK }; var makeActionDenormalizedPayload = function makeActionDenormalizedPayload(isArray, payloadIds, schema, entities) { + // return empty immutable object if (!payloadIds) { return undefined; } var result = (0, _helper.denormalize)(payloadIds, [schema], entities).filter(function (v) { return v; - }).map(function (item) { - return item.toJS(); }); + return isArray ? result : result[0]; }; var makeActionDenormalizedPayloads = function makeActionDenormalizedPayloads(isFetching, actionSchema, entities, payloadIsArray, actionDataKey, entityState, actionState) { if (!actionDataKey) { - return {}; + return undefined; } if (!entityState) { @@ -95,6 +95,7 @@ var _makeGetActionData = function _makeGetActionData(action, actionId, entityNam return Object.assign(makeDefaultActionData(), { actionId: actionId, + args: actionState.get('args'), sourceResult: actionState.get('sourceResult'), status: actionState.get('status'), time: actionState.get('time'), @@ -133,7 +134,10 @@ var getMergedActionsData = exports.getMergedActionsData = function getMergedActi }); return sortedByUpate.reduce(function (memo, item) { - return Object.assign(memo, item); + return Object.assign(memo, item, { + payload: memo.payload ? memo.payload.merge(item.payload) : item.payload, + prevPayload: memo.prevPayload ? memo.prevPayload.merge(item.prevPayload) : item.prevPayload + }); }); }); }; diff --git a/package.json b/package.json index a794cc4..63b182d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "redux-tide", "description": "tiny factory for redux crud normalize", - "version": "0.1.4", + "version": "0.1.5", "main": "./lib/index.js", "files": [ "lib", @@ -82,7 +82,7 @@ "jsdoc": "^3.5.5", "json-server": "^0.12.1", "minami": "^1.2.3", - "mocha": "^4.1.0", + "mocha": "^5.0.0", "prettier": "^1.9.2", "raw-loader": "^0.5.1", "react": "^16.2.0", diff --git a/src/action.js b/src/action.js index 2fd9a61..74730c6 100644 --- a/src/action.js +++ b/src/action.js @@ -3,8 +3,8 @@ */ import { + ACTION_REMOVE_TYPE_NAME, ACTION_EMPTY_TYPE_NAME, - ACTION_CLEAN_TYPE_NAME, ACTION_ID_KEY, ACTION_IDS_KEY, ACTION_TYPE_PREFIX, @@ -81,9 +81,10 @@ const makeActionHandler = ( * * @param {String} error - action error text * @param {Object|Array} payloadSource - response from action result + * @param {Object|Array} args - arguments from queryBuilder * @returns {Object} - action dispatch body */ - return function(error, payloadSource, sourceResult) { + return function(error, payloadSource, sourceResult, args) { if (status === 'success' && payloadSource === undefined) { error = 'Empty payload' status = 'error' @@ -139,10 +140,11 @@ const makeActionHandler = ( * payloadSource: Object|Array * }} */ - return Object.freeze({ + return { time: new Date().getTime(), type: `${actionId}`, prefix: ACTION_TYPE_PREFIX, + args, actionId, parentActionId, status, @@ -156,22 +158,22 @@ const makeActionHandler = ( sourceResult, payload, payloadSource - }) + } } } const makeResultCallback = (responseMapper, success, error) => { - return function(dispatch, getState, err, result) { + return function(dispatch, getState, err, result, args) { try { const errorMessage = parseError(err) if (errorMessage) { - return dispatch(error(errorMessage, undefined)) + return dispatch(error(errorMessage, undefined, undefined, args)) } - dispatch(success(undefined, responseMapper(result), result)) + dispatch(success(undefined, responseMapper(result), result, args)) } catch (e) { - dispatch(error(String(`${e.message || e}`), undefined)) + dispatch(error(String(`${e.message || e}`), undefined, undefined, args)) throw e } } @@ -208,25 +210,27 @@ const makeCallActionMethod = resultCallBack => { if (actionResult instanceof Promise) { return actionResult - .then(resp => resultCallBack(dispatch, getState, false, resp)) - .catch(err => resultCallBack(dispatch, getState, err, undefined)) + .then(resp => resultCallBack(dispatch, getState, false, resp, args)) + .catch(err => resultCallBack(dispatch, getState, err, undefined, args)) } if (typeof actionResult === 'function') { actionResult = actionResult.call(this, dispatch, getState) if (!actionResult) { - return resultCallBack(dispatch, getState, undefined, undefined) + return resultCallBack(dispatch, getState, undefined, undefined, args) } if (actionResult instanceof Promise) { return actionResult - .then(resp => resultCallBack(dispatch, getState, false, resp)) - .catch(err => resultCallBack(dispatch, getState, err, undefined)) + .then(resp => resultCallBack(dispatch, getState, false, resp, args)) + .catch(err => + resultCallBack(dispatch, getState, err, undefined, args) + ) } } - return resultCallBack(dispatch, getState, false, actionResult) + return resultCallBack(dispatch, getState, false, actionResult, args) } } @@ -435,7 +439,7 @@ const makeAction = function( * * @returns {Undefined} - returns None, only clear action data */ - this.action.empty = () => { + this.action.reset = () => { return (dispatch, getState) => { dispatch({ time: new Date().getTime(), @@ -447,28 +451,28 @@ const makeAction = function( } } - // /** - // * Clean entity from entity reducer - // * - // * @memberOf action.makeAction.Action - // * @type {Function} - // * - // * @example - // * store.dispatch(userLoginAction.clean()) - // * - // * @returns {Undefined} - returns None, only clear entity data - // */ - // this.action.clean = () => { - // return (dispatch, getState) => { - // dispatch({ - // time: new Date().getTime(), - // type: ACTION_CLEAN_TYPE_NAME, - // prefix: ACTION_TYPE_PREFIX, - // actionId: this.actionId, - // actionSchema: this.schema - // }) - // } - // } + /** + * Remove action entity id or ids from entities reducer + * + * @memberOf action.makeAction.Action + * @type {Function} + * + * @example + * store.dispatch(userLoginAction.remove()) + * + * @returns {Undefined} - returns None, only remove entity items + */ + this.action.remove = () => { + return (dispatch, getState) => { + dispatch({ + time: new Date().getTime(), + type: ACTION_REMOVE_TYPE_NAME, + prefix: ACTION_TYPE_PREFIX, + actionId: this.actionId, + actionSchema: this.schema + }) + } + } return this.action } diff --git a/src/config.js b/src/config.js index a92a4b0..f7b1681 100644 --- a/src/config.js +++ b/src/config.js @@ -72,13 +72,13 @@ export const ENTITIES_REDUCER_NAME = `${ACTION_TYPE_PREFIX}-entities` export const ACTION_EMPTY_TYPE_NAME = `${ACTION_TYPE_PREFIX}-empty` /** - * Action type name for clear entity data from entity reducer + * Action type name for delete entities from state * * @memberOf config * @const * @type {String} */ -export const ACTION_CLEAN_TYPE_NAME = `${ACTION_TYPE_PREFIX}-clean` +export const ACTION_REMOVE_TYPE_NAME = `${ACTION_TYPE_PREFIX}-remove` /** * replaced default response mapper to callback diff --git a/src/index.js b/src/index.js index 12fc961..41f13e9 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,11 @@ import { createReducers } from './reducer' import { denomalizeEntityItemById, getActionData, - getMergedActionsData, getEntityItemsByAction, getEntityItemsByEntityName, getEntityItemsBySchema, - getEntityReducer + getEntityReducer, + getMergedActionsData } from './selector' import { diff --git a/src/reducer.js b/src/reducer.js index 2780fb0..43b11d7 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -7,6 +7,7 @@ import { normalize } from 'normalizr' import { ACTION_EMPTY_TYPE_NAME, + ACTION_REMOVE_TYPE_NAME, ACTION_TYPE_PREFIX, ACTIONS_REDUCER_NAME, ENTITIES_REDUCER_NAME @@ -32,6 +33,7 @@ const makeActionsReducer = defaultActionsState => { } const { + args, status, time, actionId, @@ -47,8 +49,11 @@ const makeActionsReducer = defaultActionsState => { const entityKey = actionSchema.key - // action.clear - if (action.type === ACTION_EMPTY_TYPE_NAME) { + // action.clear || action.remove + if ( + action.type === ACTION_EMPTY_TYPE_NAME || + action.type === ACTION_REMOVE_TYPE_NAME + ) { return state.set( actionId, fromJS(Object.assign({ entityKey }, actionDefaultData, { time })) @@ -68,6 +73,7 @@ const makeActionsReducer = defaultActionsState => { } actionState = actionState.merge({ + args, status, time, hasError, diff --git a/src/selector.js b/src/selector.js index 1423b8d..85fc89a 100644 --- a/src/selector.js +++ b/src/selector.js @@ -46,13 +46,13 @@ const makeActionDenormalizedPayload = ( schema, entities ) => { + // return empty immutable object if (!payloadIds) { return undefined } - const result = denormalize(payloadIds, [schema], entities) - .filter(v => v) - .map(item => item.toJS()) + const result = denormalize(payloadIds, [schema], entities).filter(v => v) + return isArray ? result : result[0] } @@ -66,7 +66,7 @@ const makeActionDenormalizedPayloads = ( actionState ) => { if (!actionDataKey) { - return {} + return undefined } if (!entityState) { @@ -124,6 +124,7 @@ const _makeGetActionData = (action, actionId, entityName, actionSchema) => { makeDefaultActionData(), { actionId, + args: actionState.get('args'), sourceResult: actionState.get('sourceResult'), status: actionState.get('status'), time: actionState.get('time'), @@ -168,7 +169,14 @@ export const getMergedActionsData = (...actions) => { const sortedByUpate = actionsData.sort((a, b) => a.time - b.time) return sortedByUpate.reduce((memo, item) => { - return Object.assign(memo, item) + return Object.assign(memo, item, { + payload: memo.payload + ? memo.payload.merge(item.payload) + : item.payload, + prevPayload: memo.prevPayload + ? memo.prevPayload.merge(item.prevPayload) + : item.prevPayload + }) }) } ) diff --git a/test/action.spec.js b/test/action.spec.js index 7f75bad..678da6b 100644 --- a/test/action.spec.js +++ b/test/action.spec.js @@ -1,16 +1,14 @@ import 'should' import sinon from 'sinon' import { schema } from 'normalizr' - -require('should-sinon') - import { + createAction, makeActionHandler, - makeActionUniqId, - makeAction, - createAction + makeActionUniqId } from '../src/action' +require('should-sinon') + describe('action makeActionUniqId ', function() { it('makeActionUniqId returns uniquie ids', function() { const result = new Set([ diff --git a/test/config.spec.js b/test/config.spec.js index 6104f83..28f2406 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -1,24 +1,23 @@ import 'should' import sinon from 'sinon' import { schema } from 'normalizr' - -require('should-sinon') - import { - IS_TEST_ENVIRONMENT, - STATUSES, + ACTION_EMPTY_TYPE_NAME, ACTION_ID_KEY, ACTION_IDS_KEY, ACTION_TYPE_PREFIX, ACTIONS_REDUCER_NAME, ENTITIES_REDUCER_NAME, - ACTION_EMPTY_TYPE_NAME, + IS_TEST_ENVIRONMENT, setDefaultResponseMapper, - setDenormalize + setDenormalize, + STATUSES } from '../src/config' import { createAction } from '../src/action' +require('should-sinon') + describe('config is valid', function() { it('should define all required variables', function() { IS_TEST_ENVIRONMENT.should.not.be.undefined() diff --git a/test/helper.spec.js b/test/helper.spec.js index ed1bdcd..7d11602 100644 --- a/test/helper.spec.js +++ b/test/helper.spec.js @@ -1,4 +1,4 @@ -import { uniqPrefix, parseError } from '../src/helper' +import { parseError, uniqPrefix } from '../src/helper' describe('helper uniqPrefix', function() { it('created uniq prefixed', function() { diff --git a/test/selector.spec.js b/test/selector.spec.js index cf27dec..b03aadf 100644 --- a/test/selector.spec.js +++ b/test/selector.spec.js @@ -2,7 +2,7 @@ import 'should' import { schema } from 'normalizr' import { fromJS } from 'immutable' -import { ENTITIES_REDUCER_NAME, ACTIONS_REDUCER_NAME } from '../src/config' +import { ACTIONS_REDUCER_NAME, ENTITIES_REDUCER_NAME } from '../src/config' import { getActionData, getEntityItemsByAction, @@ -46,6 +46,7 @@ it('selector getActionData', function() { } const result = getActionData(action)(state) + result.payload = result.payload.toJS() result.should.be.deepEqual({ status: '', diff --git a/website/package.json b/website/package.json index 6f17121..978e5f6 100644 --- a/website/package.json +++ b/website/package.json @@ -3,32 +3,13 @@ "version": "0.1.0", "private": true, "dependencies": { - "autoprefixer": "7.1.6", - "babel-core": "6.26.0", - "babel-eslint": "7.2.3", - "babel-jest": "20.0.3", - "babel-loader": "7.1.2", - "babel-preset-react-app": "^3.1.0", - "babel-runtime": "6.26.0", + "axios": "^0.17.1", + "redux-tide": "^0.1.4", "case-sensitive-paths-webpack-plugin": "2.1.1", "chalk": "1.1.3", - "css-loader": "0.28.7", "dotenv": "4.0.0", - "eslint": "4.10.0", - "eslint-config-react-app": "^2.0.1", - "eslint-loader": "1.9.0", - "eslint-plugin-flowtype": "2.39.1", - "eslint-plugin-import": "2.8.0", - "eslint-plugin-jsx-a11y": "5.1.1", - "eslint-plugin-react": "7.4.0", - "extract-text-webpack-plugin": "3.0.2", - "file-loader": "1.1.5", "fs-extra": "3.0.1", "history": "^4.7.2", - "html-loader": "^0.5.4", - "html-webpack-plugin": "2.29.0", - "jest": "20.0.4", - "markdown-loader": "^2.0.2", "normalizr": "^3.2.4", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.2.0", @@ -46,12 +27,6 @@ "redux-devtools-dock-monitor": "^1.1.3", "redux-devtools-log-monitor": "^1.4.0", "redux-thunk": "^2.2.0", - "style-loader": "0.19.0", - "sw-precache-webpack-plugin": "0.11.4", - "url-loader": "0.6.2", - "webpack": "3.8.1", - "webpack-dev-server": "2.9.4", - "webpack-manifest-plugin": "1.3.2", "whatwg-fetch": "2.0.3" }, "scripts": { @@ -59,7 +34,36 @@ "build": "node scripts/build.js", "test": "node scripts/test.js --env=jsdom" }, - "devDependencies": {}, + "devDependencies": { + "codesandbox": "^1.1.14", + "autoprefixer": "7.1.6", + "babel-core": "6.26.0", + "babel-eslint": "7.2.3", + "babel-jest": "20.0.3", + "babel-loader": "7.1.2", + "babel-preset-react-app": "^3.1.0", + "babel-runtime": "6.26.0", + "eslint": "4.10.0", + "eslint-config-react-app": "^2.0.1", + "eslint-loader": "1.9.0", + "eslint-plugin-flowtype": "2.39.1", + "eslint-plugin-import": "2.8.0", + "eslint-plugin-jsx-a11y": "5.1.1", + "eslint-plugin-react": "7.4.0", + "style-loader": "0.19.0", + "sw-precache-webpack-plugin": "0.11.4", + "url-loader": "0.6.2", + "webpack": "3.8.1", + "webpack-dev-server": "2.9.4", + "webpack-manifest-plugin": "1.3.2", + "css-loader": "0.28.7", + "extract-text-webpack-plugin": "3.0.2", + "file-loader": "1.1.5", + "html-loader": "^0.5.4", + "html-webpack-plugin": "2.29.0", + "markdown-loader": "^2.0.2", + "jest": "20.0.4" + }, "jest": { "collectCoverageFrom": [ "src/**/*.{js,jsx,mjs}" diff --git a/website/public/index.html b/website/public/index.html index c016007..1900e5b 100644 --- a/website/public/index.html +++ b/website/public/index.html @@ -1,6 +1,17 @@ + + + + + diff --git a/website/src/App.css b/website/src/App.css index 8456cbf..9eaeb28 100644 --- a/website/src/App.css +++ b/website/src/App.css @@ -1,30 +1,34 @@ .App { - text-align: center; + text-align: center; } .App-logo { - animation: App-logo-spin infinite 20s linear; - height: 80px; + animation: App-logo-spin infinite 20s linear; + height: 80px; } .App-header { - background-color: #222; - height: 150px; - padding: 20px; - color: white; + background-color: #222; + height: 150px; + padding: 20px; + color: white; } .App-title { - font-size: 1.5em; + font-size: 1.5em; } .App-intro { - font-size: large; + font-size: large; } @keyframes App-logo-spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } .spinner { @@ -55,18 +59,23 @@ } @-webkit-keyframes sk-bouncedelay { - 0%, 80%, 100% { -webkit-transform: scale(0) } - 40% { -webkit-transform: scale(1.0) } + 0%, 80%, 100% { + -webkit-transform: scale(0) + } + 40% { + -webkit-transform: scale(1.0) + } } @keyframes sk-bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); - } 40% { - -webkit-transform: scale(1.0); - transform: scale(1.0); - } + } + 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } } .spinner { @@ -97,16 +106,21 @@ } @-webkit-keyframes sk-bouncedelay { - 0%, 80%, 100% { -webkit-transform: scale(0) } - 40% { -webkit-transform: scale(1.0) } + 0%, 80%, 100% { + -webkit-transform: scale(0) + } + 40% { + -webkit-transform: scale(1.0) + } } @keyframes sk-bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); - } 40% { - -webkit-transform: scale(1.0); - transform: scale(1.0); - } + } + 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } } \ No newline at end of file diff --git a/website/src/App.js b/website/src/App.js index 0a00b8f..ea0213c 100644 --- a/website/src/App.js +++ b/website/src/App.js @@ -1,23 +1,40 @@ -import React, {Component} from 'react'; -import logo from './logo.svg'; -import {Navbar, Nav, NavItem} from 'react-bootstrap' -import './App.css'; +import React, {Component} from 'react' +import logo from './logo.svg' +import {Nav, Navbar, NavItem} from 'react-bootstrap' +import './App.css' const README = require('../../README.md') const exampleName = document.location.search.replace('?ex=', '') -const PUBLIC_URL = process.env.PUBLIC_URL || '/' +const PUBLIC_URL = (document && document.location && document.location.host === 'localhost:3000') ? process.env.PUBLIC_URL || '/' : 'https://edtoken.github.io/redux-tide' -const exampleComponents = { - 'blog': require('./blog'), - 'different-entity-id': require('./different-entity-id'), - 'merged-actions-data': require('./merged-actions-data') -} +const EXAMPLES = [ + { + 'title': 'Blog example', + 'path': 'blog', + 'component': require('./blog') + }, + { + 'title': 'Different entity id', + 'path': 'different-entity-id', + 'component': require('./different-entity-id') + }, + { + 'title': 'Merged actions data', + 'path': 'merged-actions-data', + 'component': require('./merged-actions-data') + }, + { + 'title': 'Remove entity from state', + 'path': 'remove-entity-from-state', + 'component': require('./remove-entity-from-state') + } +] class App extends Component { render() { - const ExampleComponent = exampleName && exampleComponents[exampleName] ? exampleComponents[exampleName].default : undefined + const ExampleComponent = exampleName ? EXAMPLES.find(item => item.path === exampleName).component.default : undefined return (
@@ -30,11 +47,11 @@ class App extends Component { Star + aria-label="Star @edtoken/redux-tide on GitHub">Star
@@ -53,15 +70,12 @@ class App extends Component { Index - - Blog example - - - Different entity id - - - Merged actions data - + {EXAMPLES.map((item, num) => { + return + {item.title} + + })}
@@ -87,8 +101,8 @@ class App extends Component { } - ); + ) } } -export default App; +export default App diff --git a/website/src/App.test.js b/website/src/App.test.js index b84af98..e639a62 100644 --- a/website/src/App.test.js +++ b/website/src/App.test.js @@ -1,8 +1,8 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; +import React from 'react' +import ReactDOM from 'react-dom' +import App from './App' it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); -}); + const div = document.createElement('div') + ReactDOM.render(, div) +}) diff --git a/website/src/DevTools.js b/website/src/DevTools.js index 5459367..07457ca 100644 --- a/website/src/DevTools.js +++ b/website/src/DevTools.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react' +import React from 'react' import {createDevTools} from 'redux-devtools' import LogMonitor from 'redux-devtools-log-monitor' import DockMonitor from 'redux-devtools-dock-monitor' @@ -8,7 +8,7 @@ export default createDevTools( diff --git a/website/src/RESTApi.js b/website/src/RESTApi.js index df872e5..d4384eb 100644 --- a/website/src/RESTApi.js +++ b/website/src/RESTApi.js @@ -5,7 +5,7 @@ const apiInstance = axios.create({ }) const createRequest = method => { - return function(url, params = {}, data = {}) { + return function (url, params = {}, data = {}) { return apiInstance.request({ method, url: url, diff --git a/website/src/blog/actions.js b/website/src/blog/actions.js index 0809914..1b6ca75 100644 --- a/website/src/blog/actions.js +++ b/website/src/blog/actions.js @@ -1,13 +1,13 @@ -import {createAction} from "../../../src/action"; +import {createAction} from 'redux-tide' import * as api from '../RESTApi' -import {postsSchema} from "./schema"; +import {postsSchema} from "./schema" /** * Ajax axios call get all posts * * @type {Action} */ -export const getAllPost = createAction(postsSchema, api.get, query => [`posts`, query]) +export const getAllPost = createAction(postsSchema, api.get, query => [`posts?_embed=comments`, query]) /** @@ -17,10 +17,10 @@ export const getAllPost = createAction(postsSchema, api.get, query => [`posts`, * * @type {Action} */ -export const fetchPost = createAction(postsSchema, api.get, postId => `posts/${postId}`) +export const fetchPost = createAction(postsSchema, api.get, postId => `posts/${postId}?_embed=comments`) export const updatePost = createAction(postsSchema, api.put, (postId, data) => [ - `posts/${postId}`, + `posts/${postId}?_embed=comments`, undefined, data ]) diff --git a/website/src/blog/index.js b/website/src/blog/index.js index 93c4b0d..8d1be18 100644 --- a/website/src/blog/index.js +++ b/website/src/blog/index.js @@ -1,15 +1,15 @@ -import React, {Component} from 'react'; -import {Provider} from 'react-redux' -import {connect} from 'react-redux' +import React, {Component} from 'react' +import {connect, Provider} from 'react-redux' import {ConnectedRouter} from 'react-router-redux' -import {Table, Pager, Modal, Button, FormControl, ControlLabel, Alert} from 'react-bootstrap' -import {Spinner} from "../Spinner"; +import {Alert, Button, ControlLabel, FormControl, Modal, Pager, Table} from 'react-bootstrap' +import {Spinner} from "../Spinner" -import DevTools from '../DevTools' import store, {history} from './store' -import {getAllPost, fetchPost, updatePost} from "./actions"; -import {getActionData} from "../../../src"; + +import DevTools from '../DevTools' +import {fetchPost, getAllPost, updatePost} from "./actions" +import {getActionData} from 'redux-tide' class BlogPostFormComponent extends Component { @@ -39,7 +39,7 @@ class BlogPostFormComponent extends Component { // return // } - this.setState({form: this.props.payload || {}}) + this.setState({form: this.props.payload ? this.props.payload.toJS() : {}}) } componentWillMount() { @@ -62,11 +62,14 @@ class BlogPostFormComponent extends Component { } render() { - const {isFetching, hasError, errorText, payload} = this.props + const {isFetching, hasError, errorText, payload, args} = this.props const {saved, form} = this.state const disableEdit = isFetching const completed = saved && this.props.status === 'success' + // console.log('args', args) + // console.log('payload', payload) + return (
@@ -88,9 +91,6 @@ class BlogPostFormComponent extends Component { But you are calling only PUT post/postId -

fetchPost payload:

-
{JSON.stringify(payload, null, 2)}
- {isFetching &&
} @@ -109,6 +109,7 @@ class BlogPostFormComponent extends Component { (this._handleChange(e))} @@ -125,6 +126,12 @@ class BlogPostFormComponent extends Component { {!completed && 'Save changes'} + +
+

fetchPost payload:

+ {payload &&
{JSON.stringify({...payload.toJS(), comments: ['... Comments List ...']}, null, 2)}
} + +
) @@ -212,11 +219,11 @@ class BlogPostsTableComponent extends Component { {hasPayload && payload.map((item, num) => { return this.props.handleOpenPost(item.id)}> - {item.userId} - {item.id} - {item.title} + key={['table-post', item.get('id'), num].join('-')} + onClick={() => this.props.handleOpenPost(item.get('id'))}> + {item.get('userId')} + {item.get('id')} + {item.get('title')} @@ -283,15 +290,22 @@ class BlogExampleComponent extends Component { return (

Blog Example

-

Source code source

+

+ Preview in SandBox codesandbox.io +

+ +

Source code source

Demonstrate how to create list and single item requests, sync data between it, witout reducers
Please look into the DevTools panel and Network requests
-
You can hide DevTools, click Ctrl+H +
You can open DevTools, click Ctrl+H
`posts/${postId}` + postId => `posts/${postId}?_embed=comments` ) export const updatePost = createAction( postsSchema, api.put, (postId, data) => [ - `posts/${postId}`, + `posts/${postId}?_embed=comments`, undefined, data ] @@ -29,7 +29,7 @@ export const updatePost = createAction( export const deletePost = createAction( postsSchema, api.del, - postId => `posts/${postId}` + postId => `posts/${postId}?_embed=comments` ) diff --git a/website/src/different-entity-id/index.js b/website/src/different-entity-id/index.js index 61b64be..0624909 100644 --- a/website/src/different-entity-id/index.js +++ b/website/src/different-entity-id/index.js @@ -1,25 +1,23 @@ -import React, {Component} from 'react'; -import {Provider} from 'react-redux' -import {connect} from 'react-redux' +import React, {Component} from 'react' +import {connect, Provider} from 'react-redux' import {ConnectedRouter} from 'react-router-redux' -import {Table, Pager, ProgressBar, Modal, Button, FormControl, ControlLabel, Alert} from 'react-bootstrap' +import {Alert} from 'react-bootstrap' import DevTools from '../DevTools' import store, {history} from './store' -import {fetchPost, fetchSinglePost, updatePost, updateSinglePost} from "./actions"; -import {getActionData} from "../../../src"; +import {fetchPost, fetchSinglePost, updatePost, updateSinglePost} from "./actions" +import {getActionData} from 'redux-tide' class CommonPostComponent extends Component { componentWillMount() { - console.log('fetch', this.props.postId) this.props.fetch(this.props.postId) } componentWillReceiveProps(nextProps) { // it's need only for SinglePostCorrectComponent - const prevProps = this.props; + const prevProps = this.props this.props = nextProps if (this.props.postId && this.props.postId !== prevProps.postId) { @@ -53,7 +51,7 @@ class CommonPostComponent extends Component {

{hasError &&
{errorText}
} -
{JSON.stringify(payload, null, 2)}
+ {payload &&
{JSON.stringify({...payload.toJS(), comments: ['... Comments List ...']}, null, 2)}
}
) } } @@ -62,8 +60,8 @@ class CommonPostComponent extends Component { const PostCorrectComponent = connect( (state, props) => getActionData(fetchPost.withPrefix(props.postId)), dispatch => ({ - fetch: (postId) => dispatch(fetchPost.withPrefix(postId)(postId)), - update: (postId, data) => dispatch(updatePost.withPrefix(postId)(postId, data)) + fetch: postId => dispatch(fetchPost.withPrefix(postId)(postId)), + update: (postId, data) => dispatch(updatePost.withPrefix(postId)(postId, data)), }) )(CommonPostComponent) @@ -79,8 +77,8 @@ const PostIncorrectComponent = connect( const SinglePostCorrectComponent = connect( state => getActionData(fetchSinglePost), dispatch => ({ - fetch: (postId) => dispatch(fetchSinglePost(postId)), - update: (postId, data) => (dispatch(updateSinglePost(postId, data))) + fetch: postId => dispatch(fetchSinglePost(postId)), + update: (postId, data) => dispatch(updateSinglePost(postId, data)) }) )(CommonPostComponent) @@ -113,7 +111,14 @@ class DifferentEntityIdExampleComponent extends Component { return (

Different entity Id

-

Source code source

+

+ Preview in SandBox codesandbox.io +

+ +

Source code source

Demonstrate how to use `.withPrefix`, `.withName`, `.clone` methods
@@ -140,7 +145,8 @@ class DifferentEntityIdExampleComponent extends Component { If you are have 1 component to 1 action
you can use without withPrefix method, it's not required
- Please change postId from input to 2,
Then click update and look correct-post-2
+ Please change postId from input to 2,
Then click update + and look correct-post-2
, document.getElementById('root')); -// registerServiceWorker(); +ReactDOM.render(, document.getElementById('root')) diff --git a/website/src/logo.svg b/website/src/logo.svg index 6b60c10..4ec91df 100644 --- a/website/src/logo.svg +++ b/website/src/logo.svg @@ -1,6 +1,7 @@ - + diff --git a/website/src/merged-actions-data/actions.js b/website/src/merged-actions-data/actions.js index ff8702f..f7ec697 100644 --- a/website/src/merged-actions-data/actions.js +++ b/website/src/merged-actions-data/actions.js @@ -1,6 +1,6 @@ -import {createAction} from "../../../src/action"; +import {createAction} from 'redux-tide' import * as api from '../RESTApi' -import {postsSchema} from "./schema"; +import {postsSchema} from "./schema" /** @@ -13,14 +13,14 @@ import {postsSchema} from "./schema"; export const fetchPost = createAction( postsSchema, api.get, - postId => `posts/${postId}` + postId => `posts/${postId}?_embed=comments` ) export const updatePost = createAction( postsSchema, api.put, (postId, data) => [ - `posts/${postId}`, + `posts/${postId}?_embed=comments`, undefined, data ] diff --git a/website/src/merged-actions-data/index.js b/website/src/merged-actions-data/index.js index 168842b..af4d388 100644 --- a/website/src/merged-actions-data/index.js +++ b/website/src/merged-actions-data/index.js @@ -1,19 +1,18 @@ -import React, {Component} from 'react'; -import {Provider} from 'react-redux' -import {connect} from 'react-redux' +import React, {Component} from 'react' +import {connect, Provider} from 'react-redux' import {ConnectedRouter} from 'react-router-redux' -import {Table, Pager, ProgressBar, Modal, Button, FormControl, ControlLabel, Alert} from 'react-bootstrap' +import {Alert} from 'react-bootstrap' import DevTools from '../DevTools' import store, {history} from './store' -import {fetchPost, fetchSinglePost, updatePost, updateSinglePost} from "./actions"; -import {getActionData, getMergedActionsData} from "../../../src"; + +import {fetchPost, fetchSinglePost, updatePost, updateSinglePost} from './actions' +import {getActionData, getMergedActionsData} from 'redux-tide' class CommonPostComponent extends Component { componentWillMount() { - console.log('fetch', this.props.postId) this.props.fetch(this.props.postId) } @@ -42,7 +41,7 @@ class CommonPostComponent extends Component {

{hasError &&
{errorText}
} -
{JSON.stringify(payload, null, 2)}
+ {payload &&
{JSON.stringify({...payload.toJS(), comments: ['... Comments List ...']}, null, 2)}
}
) } } @@ -97,7 +96,16 @@ class DifferentEntityIdExampleComponent extends Component { return (

Merged Actions Data

-

Source code source

+ +

+ Preview in SandBox codesandbox.io +

+ +

Source code source

Demonstrate how to use `getMergedActionsData` selector
diff --git a/website/src/merged-actions-data/schema.js b/website/src/merged-actions-data/schema.js index a8130ed..4119a59 100644 --- a/website/src/merged-actions-data/schema.js +++ b/website/src/merged-actions-data/schema.js @@ -5,12 +5,12 @@ const commentsSchema = new schema.Entity('comments') const postsSchema = new schema.Entity('posts') postsSchema.define({ - // author: profileSchema, - // comments: [commentsSchema] + author: profileSchema, + comments: [commentsSchema] }) commentsSchema.define({ - // postId: postsSchema + postId: postsSchema }) export { diff --git a/website/src/merged-actions-data/store.js b/website/src/merged-actions-data/store.js index 806c7dc..c995c07 100644 --- a/website/src/merged-actions-data/store.js +++ b/website/src/merged-actions-data/store.js @@ -1,9 +1,9 @@ import {applyMiddleware, combineReducers, compose, createStore} from 'redux' import {routerMiddleware} from 'react-router-redux' -import {denormalize} from 'normalizr'; +import {denormalize} from 'normalizr' import thunk from 'redux-thunk' import createHistory from 'history/createBrowserHistory' -import {createReducers, setDefaultResponseMapper, setDenormalize} from '../../../src/index'; +import {createReducers, setDefaultResponseMapper, setDenormalize} from 'redux-tide' import DevTools from '../DevTools' import {customReducer} from './reducer' import {appSchema} from './schema' diff --git a/website/src/registerServiceWorker.js b/website/src/registerServiceWorker.js index 12542ba..9612a6c 100644 --- a/website/src/registerServiceWorker.js +++ b/website/src/registerServiceWorker.js @@ -10,36 +10,36 @@ const isLocalhost = Boolean( window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +) export default function register() { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + const publicUrl = new URL(process.env.PUBLIC_URL, window.location) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 - return; + return } window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Lets check if a service worker still exists or not. - checkValidServiceWorker(swUrl); + checkValidServiceWorker(swUrl) } else { // Is not local host. Just register service worker - registerValidSW(swUrl); + registerValidSW(swUrl) } - }); + }) } } @@ -48,7 +48,7 @@ function registerValidSW(swUrl) { .register(swUrl) .then(registration => { registration.onupdatefound = () => { - const installingWorker = registration.installing; + const installingWorker = registration.installing installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { @@ -56,20 +56,20 @@ function registerValidSW(swUrl) { // the fresh content will have been added to the cache. // It's the perfect time to display a "New content is // available; please refresh." message in your web app. - console.log('New content is available; please refresh.'); + console.log('New content is available; please refresh.') } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + console.log('Content is cached for offline use.') } } - }; - }; + } + } }) .catch(error => { - console.error('Error during service worker registration:', error); - }); + console.error('Error during service worker registration:', error) + }) } function checkValidServiceWorker(swUrl) { @@ -84,25 +84,25 @@ function checkValidServiceWorker(swUrl) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { registration.unregister().then(() => { - window.location.reload(); - }); - }); + window.location.reload() + }) + }) } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl); + registerValidSW(swUrl) } }) .catch(() => { console.log( 'No internet connection found. App is running in offline mode.' - ); - }); + ) + }) } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); + registration.unregister() + }) } } diff --git a/website/src/remove-entity-from-state/actions.js b/website/src/remove-entity-from-state/actions.js new file mode 100644 index 0000000..b13e3e8 --- /dev/null +++ b/website/src/remove-entity-from-state/actions.js @@ -0,0 +1,34 @@ +import {createAction} from 'redux-tide' +import * as api from '../RESTApi' +import {postsSchema} from "./schema" + + +/** + * Ajax axios call get post by Id + * + * @property {Number} postId - post id + * + * @type {Action} + */ +export const fetchPost = createAction( + postsSchema, + api.get, + postId => `posts/${postId}?_embed=comments` +) + +export const updatePost = createAction( + postsSchema, + api.put, + (postId, data) => [ + `posts/${postId}?_embed=comments`, + undefined, + data + ] +) + + +export const deletePost = createAction( + postsSchema, + api.del, + postId => `posts/${postId}` +) \ No newline at end of file diff --git a/website/src/remove-entity-from-state/index.js b/website/src/remove-entity-from-state/index.js new file mode 100644 index 0000000..c37c15b --- /dev/null +++ b/website/src/remove-entity-from-state/index.js @@ -0,0 +1,131 @@ +import React, {Component} from 'react' +import {connect, Provider} from 'react-redux' + +import {ConnectedRouter} from 'react-router-redux' +import {Alert} from 'react-bootstrap' + +import DevTools from '../DevTools' +import store, {history} from './store' +import {deletePost, fetchPost, updatePost} from "./actions" +import {getMergedActionsData} from 'redux-tide' + +class CommonPostComponent extends Component { + + componentWillMount() { + this.props.fetch(this.props.postId) + } + + componentWillReceiveProps(nextProps) { + const prevProps = this.props + this.props = nextProps + + if (this.props.postId && this.props.postId !== prevProps.postId) { + this.props.fetch(this.props.postId) + } + } + + render() { + const {postId, payload, isFetching, hasError, errorText} = this.props + + return (
+ POST ID (props) {postId} 
+ post id payload {payload ? payload.id : ''} +
+
+ + {isFetching &&
isFetching...
} + + + +   + +   + +
+
+ {hasError &&
{errorText}
} +
{JSON.stringify({
+        title: payload ? payload.get('title') : ''
+      }, null, 2)}
+
) + } +} + +const CommonPost = connect( + (state, props) => getMergedActionsData( + fetchPost.withPrefix(props.postId), + updatePost.withPrefix(props.postId), + deletePost.withPrefix(props.postId) + ), + (dispatch) => ({ + del: postId => dispatch(deletePost.withPrefix(postId)(postId)), + fetch: postId => dispatch(fetchPost.withPrefix(postId)(postId)), + update: (postId, data) => dispatch(updatePost.withPrefix(postId)(postId, data)) + }) +)(CommonPostComponent) + +class DeleteTntityFromStateExampleComponent extends Component { + + render() { + + return (
+

Remove Entity from state

+ +

+ Preview in SandBox codesandbox.io +

+ +

Source code source +

+ + + Demonstrate how to use `dispatch(action.remove())` method
+ When you do `DELETE users/:user_id`, you need to delete entity from normalized store
+ For this, you can use `dispatch(deleteUserAction.remove())` or `dispatch(deleteUserAction.withPrefix(userId).remove())` +
+ +
+
+

With delete It's correct

+ +
+
+

Without delete It's no correct

+ +
+
+
) + } +} + +export default class MergedActionsDataComponentWrapper extends Component { + + render() { + return (
+ + +
+ + +
+
+
+
) + } +} \ No newline at end of file diff --git a/website/src/remove-entity-from-state/reducer.js b/website/src/remove-entity-from-state/reducer.js new file mode 100644 index 0000000..b9abc22 --- /dev/null +++ b/website/src/remove-entity-from-state/reducer.js @@ -0,0 +1,13 @@ +/** + * + * Common reducer with your data + * + */ +const defaultState = { + NODE_ENV: process.env.NODE_ENV +} + +export const customReducer = (state = defaultState, action) => { + + return state +} \ No newline at end of file diff --git a/website/src/remove-entity-from-state/schema.js b/website/src/remove-entity-from-state/schema.js new file mode 100644 index 0000000..4119a59 --- /dev/null +++ b/website/src/remove-entity-from-state/schema.js @@ -0,0 +1,26 @@ +import {schema} from 'normalizr' + +const profileSchema = new schema.Entity('profile') +const commentsSchema = new schema.Entity('comments') +const postsSchema = new schema.Entity('posts') + +postsSchema.define({ + author: profileSchema, + comments: [commentsSchema] +}) + +commentsSchema.define({ + postId: postsSchema +}) + +export { + profileSchema, + commentsSchema, + postsSchema +} + +export const appSchema = { + profileSchema, + commentsSchema, + postsSchema +} \ No newline at end of file diff --git a/website/src/remove-entity-from-state/store.js b/website/src/remove-entity-from-state/store.js new file mode 100644 index 0000000..c995c07 --- /dev/null +++ b/website/src/remove-entity-from-state/store.js @@ -0,0 +1,39 @@ +import {applyMiddleware, combineReducers, compose, createStore} from 'redux' +import {routerMiddleware} from 'react-router-redux' +import {denormalize} from 'normalizr' +import thunk from 'redux-thunk' +import createHistory from 'history/createBrowserHistory' +import {createReducers, setDefaultResponseMapper, setDenormalize} from 'redux-tide' +import DevTools from '../DevTools' +import {customReducer} from './reducer' +import {appSchema} from './schema' + +setDefaultResponseMapper((resp) => { + return resp.data +}) + +setDenormalize(denormalize) + +export const history = createHistory() + +const rootInitialState = {} +const enhancers = [] +const middleware = [ + thunk, + routerMiddleware(history) +] + +enhancers.push(DevTools.instrument()) + +const composedEnhancers = compose( + applyMiddleware(...middleware), + ...enhancers +) +export default createStore( + combineReducers({ + custom: customReducer, + ...createReducers(...appSchema) + }), + rootInitialState, + composedEnhancers +) \ No newline at end of file