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

Reducer querying #943

Closed
RoyalIcing opened this issue Oct 23, 2015 · 9 comments
Closed

Reducer querying #943

RoyalIcing opened this issue Oct 23, 2015 · 9 comments

Comments

@RoyalIcing
Copy link

Is it possible to query reducers, such as for determining whether an async action needs to run or not?

If not, I would like to propose a discussion on the possibility of introducing them. These queries would be their own action type with a payload, except a reducer returns a response instead of the entire state.

They would be polymorphic – not coupled to any particular reducer. This allows more flexible code, rather than having actions having specific knowledge about the structure of the state tree that getState requires (in redux-thunk).

Note this is also distinct from the helper functions that I have seen in examples, where alongside a reducer, other functions are exported to extract data from its state. These are coupled to a particular reducer.

// Action Creator
import { QUERY_TYPE } from 'redux';
const QUERY_WANTS_TO_LOAD_REDDIT = 'QUERY_WANTS_TO_LOAD_REDDIT';

export function fetchPostsIfNeeded(reddit) {
  return (dispatch, getState, query) {
    if (query({ type: QUERY_TYPE, query: QUERY_WANTS_TO_LOAD_REDDIT, reddit }).some(Boolean)) {
      fetchPosts(reddit);
    }
  };
}

// Reducer

function posts(state = { items: [], didInvalidate: true }, action) {
  if (action.type === QUERY_TYPE) {
    switch (action.query) {
      case QUERY_WANTS_TO_LOAD_REDDIT:
        return state.didInvalidate;
    }
  }

  switch (action.type) {
    // Normal handling...
  }
}

export function postsByReddit(state, action) {
  if (action.type === QUERY_TYPE) {
    switch (action.query) {
      case QUERY_WANTS_TO_LOAD_REDDIT:
        return posts(state[action.reddit], action);
    }
  }

  switch (action.type) {
    // Normal handling...
  }
}

I have implemented a system like this in my Redux additions library Flambeau (see https://github.com/BurntCaramel/flambeau/blob/master/docs/reducers.md#introspection), but it would be great to have similar functionality in Redux.

Even just the bare minimum of sending arbitrary messages to reducers without affecting state, and then the rest could be built on top as a plugin?

@mindjuice
Copy link
Contributor

A reducer in Redux is just a function to return new state. It can't return any other value except the new state. It's fundamental to how Redux works.

Your action creator though, can certainly 'query' the state to determine whether to start an async action. Exactly how that works will depend on whether you are using plain objects or Immutable or Updeep, etc., but the state is available to your action creator.

I was going to suggest you look at the async example, but obviously you have since your example is based on it.

What exactly is it in the async example that you find lacking?

@gaearon
Copy link
Contributor

gaearon commented Oct 23, 2015

How is this different from defining “selector” functions alongside the reducers?
This is the approach we suggest.

The “shopping cart” example in Flux Comparison shows this approach.

@gaearon
Copy link
Contributor

gaearon commented Oct 23, 2015

(Note: I see how it is technically different. However I find separating queries (aka selectors or getters) into separate functions easier to understand.)

@RoyalIcing
Copy link
Author

Yes I like that system of selector functions – they are sort of like a public interface, that allows the structure of the state to be encapsulated. I ported your Redux example to use my library Flambeau (sorry I hope you are not sick of me talking about it) – you can see where I’ve 'queried' reducers here with its own syntax (getConsensus): https://github.com/voronianski/flux-comparison/blob/master/redux-flambeau/js/actions/Cart.js

It’s different in that a selector is tied to a particular reducer. And, that is tied to knowing the position of the reduced data in the state tree.

This couples the async action creator to a particular reducer. Essentially removing some of the benefits of decoupling that Flux allows. It means that the async action creator can’t be reused anywhere, say in another part of the project, or another project all together. And the reducer can’t be moved around the state tree, without having to go in and change the async action creator too.

Ideally, I think, the async action creator should have no knowledge of the state tree, of reducers at all. All it does is send actions, and what I am proposing is a way for it to also receive, in a way that it defines. The action creators define the action types and the query types, and it’s up to the reducers to respond back.

@gaearon
Copy link
Contributor

gaearon commented Oct 24, 2015

Yes, I understand what you're proposing.
However I don't think we'll implement this.
Feel free to continue exploring this in your library!

@RoyalIcing
Copy link
Author

That’s cool. Is it ok if I propose other small changes that would make such a thing more natural? Currently it relies on a reference to the root reducer, as that is what it queries. However, there is no way of accessing this outside of where myself or another user declare the root reducer, as the store API only allows setting a root reducer, not getting it.

I understand it is encapsulated for a reason, so that the scope of Redux’s store is as small as possible. However, could there be a way of accessing the root reducer, say in react-redux’s connect()? Or other 'metadata' that could be added to the Redux store?

I could create a store enhancer to do this myself, e.g. add a getInfo(key) method or similar, for arbitrary data. But other store enhancers will strip this away, as they only wrap or bring across dispatch, getState, and a couple of others that are the store’s API. And connect() wouldn’t have access. A getInfo method could also be added to the store, that by default returns undefined for all keys.

Anyway, just a proposal, I’m not sure if there are other issues that would benefit from this. Appreciate the work on Redux, and would like to help in some other ways too than a rambling issue, if possible.

@stavalfi
Copy link

@gaearon redux docs reference this issue for its importance.

Can you share your reasons for rejecting this proposal?

@markerikson
Copy link
Contributor

The concept isn't idiomatic Redux, and there's no reason to build something like this into the core.

As the last comment mentions, if you really wanted to do this you could implement it as a store enhancer.

@stavalfi
Copy link

@markerikson I'm not sure I understand how to implement the original proposal as a store enhancer.

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

No branches or pull requests

5 participants