Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differentiate results #22

Open
2manyvcos opened this issue Aug 9, 2017 · 8 comments
Open

Differentiate results #22

2manyvcos opened this issue Aug 9, 2017 · 8 comments

Comments

@2manyvcos
Copy link

2manyvcos commented Aug 9, 2017

I like the approach of this package and the simplicity on how it works.

Edit
I tested a lot and simplified this issue based on my tests:

Please look at following situations to understand my issue:

  • Situation A:
    I need to find a list of data sets based on different query options.
    If I wanted to display two lists of data sets on the same page I would need to have two reducers right now in order to distinguish the results and to prevent them from being overwritten.
  • Situation B:
    I get a data set from the server. I have an error message field which should print a warning if the data could not be fetched.
    When the data was successfully fetched from the server I want to change it somehow. So I do an update. If the update failed and I now go back to my preview of the data I get an error message which I can't tell if it was from the get or the update, so I have to add additional state to distinguish.
  • Situation C:
    I find an array of data sets from my server which give me basic information about my entries. I now want to get each entry of the same endpoint afterwards and load them into my view so that my application doesn't hang and the user wouldn't need to wait until all of the data was fetched before he could interact with the UI. How should I get both the result of the find and all results of get into the state?

So here are some possible solutions:

  • Solution for situation A:
    You could set something like a 'namespace' in the action to let the reducer know in which substate the result should be put. This would give the user most control about the data but would cause a lot of substates which may never be reused afterwards. I also don't think that this would be redux conform as you would modify the way the reducer works in the actions which should be prevented if possible.
services.myService.find/get/...('namespaceA', data);
services.myService.find/get/...('namespaceB', ...);
myService: {
  namespaceA: {
    isError, isLoading, isFinished, data, ...
  },
  namespaceB: {
    isError, ...
  }
}
  • Solution for situation B: You could add a field like 'method' to the state.
services.myService.get(...);
myService: {
  method: 'get',
  isError,
  data: { entryData },
  ...
},
  • Solution for situation C:
    A mechanism to inject the result into queryResult?

Please let me know what you think about this.

@2manyvcos
Copy link
Author

2manyvcos commented Aug 9, 2017

Something like the following could be implemented as well to make the state more flexible:

options:

{
  ...,
  shape: ..., // <-- the mapping - if undefined, the current reducer should be returned directly
}
methodReducer.js
import { combineReducers } from 'redux';
import _ from 'lodash';

const childReducer = (reducer, keywords, initState = {}) => (state = initState, action = {}) =>
  (_.isString(action.type) && keywords.find((keyword) => action.type.indexOf(keyword) !== -1))
  ? reducer(state, action)
  : state;

const stringSafe = (value) => _.isString(value) ? [value] : value;

const normalizeMappingValue = (value) => {
  const sValue = stringSafe(value);
  if (_.isArray(sValue)) return normalizeMappingValue({ keywords: sValue });
  if (_.isObject(sValue)) {
    const { keywords, ...rest } = sValue;
    const sKeywords = stringSafe(keywords);
    return {
      ...rest,
      keywords: sKeywords.map((keyword) => `_${keyword.toUpperCase()}`),
    };
  }
  return { keywords: [] };
};

const normalizeMapping = (mapping) => {
  const sMapping = stringSafe(mapping);
  if (_.isArray(sMapping)) {
    return normalizeMapping(_.reduce(
      sMapping,
      (sum, val) => ({ ...sum, [val]: val }),
      {}
    ));
  }
  if (_.isObject(sMapping)) {
    return _.reduce(
      sMapping,
      (sum, value, key) => ({
        ...sum,
        [key]: normalizeMappingValue(value),
      }),
      {}
    );
  }
  return {};
};

export default (reducer, mapping) => !mapping ? reducer : combineReducers(
  _.reduce(
    normalizeMapping(mapping),
    (sum, { keywords, initState }, key) => ({
      ...sum,
      [key]: childReducer(reducer, keywords, initState),
    }),
    {}
  )
);
Usage
import methodReducer from '...';

const likeNowReducer = methodReducer(services.myService.reducer); // same as return services.myService.reducer

const verySimpleReducer = methodReducer(services.myService.reducer, 'find'); // maps only find
/*
{
  find: { ...results of find }
}
*/

const simpleReducer = methodReducer(services.myService.reducer, ['find', 'get', 'create', 'update', 'patch', 'delete']); // maps all methods into different substates
/*
{
  find: { ...results of find },
  get: { ...results of get },
  create: { ...results of create },
  update: { ...results of update },
  patch: { ...results of patch },
  delete: { ...results of delete }
}
*/

const advancedReducer = methodReducer(services.myService.reducer, {
  find: 'find',
  get: 'get',
  put: 'create',
  alter: [ 'update', 'patch' ],
  delete: 'delete'
}); // maps find, get and delete normally but maps create to put and combines update and patch to one substate named alter
/*
{
  find: { ...results of find },
  get: { ...results of get },
  put: { ...results of create },
  alter: { ...results of update and patch },
  delete: { ...results of delete }
}
*/

@eddyystop
Copy link
Collaborator

Just want to let you know I'm still thinking about it.

@eddyystop
Copy link
Collaborator

eddyystop commented Sep 18, 2017

Related is #35
Related #24

@plhosk
Copy link

plhosk commented Oct 4, 2017

I ran into this issue while building a basic redux + feathers application. For now I'm using the base feathers client combined with redux and redux-saga.

@Sicria
Copy link

Sicria commented Nov 7, 2017

Any update on this? The base functionality is great, but Situation A is really common.

Edit - Ended up using the following, seem to provide the functionality I was after.

export function feathersReduxServices(app) {
  return {
    ...reduxifyServices(app, [
      'users',
      'articles',
    ]),
    ...reduxifyServices(app, { articles: 'topArticles' }),
  };
}

@eddyystop
Copy link
Collaborator

I'm tied up with a lot of feathers development, and documentation for the new version right now.

Changes to feathers-redux are on the road-map, including an optional immutable store, but I won't realistically get to it before Jan 2018.

@greyivy
Copy link

greyivy commented May 28, 2018

@Sicria Using this and the state is not being modified correctly... Are you sure this is working?

I'm using the same set-up that you are, but when I dispatch a find on 'articles', both 'articles' and 'topArticles' are modified.

Dispatching find on 'topArticles' causes neither of them to be modified, but I'm able to access the result from the promise.

Has anyone found a solution?

Edit: Never mind: I had my reducers set up incorrectly. Thanks for the solution!

@Sicria
Copy link

Sicria commented May 29, 2018

@hauckwill Glad you got it working, it's not the most elegant solution but it works for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants