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

Allow to have slices return selectors state and derivate values #413

Closed
yeion7 opened this issue Mar 8, 2020 · 7 comments
Closed

Allow to have slices return selectors state and derivate values #413

yeion7 opened this issue Mar 8, 2020 · 7 comments

Comments

@yeion7
Copy link

yeion7 commented Mar 8, 2020

First at all, thank for redux-toolkit ❤️

One thing that seems a lot of boilerplate for me in redux is to have a lot of selector and all the boilerplate with reselect to access the state or create derivate values

Here's a rough initial API proposal:

const todosSlice = createSlice({
  name: "todos",
  initialState: { todos: [], filter: "ALL" },
  reducers: { ... },
  derived: {
    total: state => state.todos.length,
    currentTodos: state => state.todos.filter(todo => todo.status === state.filter)
  }
})

And inside of the component

function Todos(props) {
  const { total,  currentTodos, filter} = useSelector(todosSlice.selectors)
  return ...
}

Maybe it couldn’t be possible but in this way I think redux-toolkit could help to reduce a lot of code

What are everyone's thoughts?

@markerikson
Copy link
Collaborator

The issue is that we have no idea where in the state tree this slice reducer might be used. It could be at state.todos, state.entities.todos, state.someThingElseEntirely, etc. So, we found it was best to not auto-generate selectors that just try to read the slice data, and removed the previous attempt at generating selectors.

Having said that, createEntityAdapter will generate some selectors that know how to read the entities data, but it's up to you to provide an initial selector that retrieves the slice out of the entire state.

@yeion7
Copy link
Author

yeion7 commented Mar 8, 2020

@markerikson Could be possible apply the same approach to createSlice?

I think the ability to get selectors and derive data is the only missing piece to be able of model domain using createSlice

@phryneas
Copy link
Member

phryneas commented Mar 8, 2020

Besides it being on the same "slice" object, is there any benefit of your suggestion over just writing the selectors in the same file, one line below createSlice?

My reason for this question: right now, everything used in createSlice actually requires everything else in there.

The action creators use the slice name. The reducer uses the action creators as cases. ExtraReducers need to be defined within createSlice as they need to be part of the reducer.

There is nothing "passed in and out without usage" - everything is actually used and it would not work without that. From what I see, nothing in there would actually need to be defined within createSlice to provide a benefit.

Also, what you are suggesting in your first post is quite the dangerous antipattern.
The code you are writing results on one selector for all possible selectors of the full slice - and even if you only destructure out only total from a component, the component will be subscribed to currentTodos as well - as the selector returns both.

@yeion7
Copy link
Author

yeion7 commented Mar 9, 2020

@phryneas good point, in my perspective, require creating selectors for key add a lot of indirection in redux, which could be a lot of pain when you start learn it.

For my the biggest benefit is avoid the indirection and be able to model a domain with all the required parts in a single place but as you said could be enough with have selector in the same file.

If we can track witch data is access inside a component we could only check that values and avoid the performance issue of suscribe to all the data.

Also this proposal was inspire by

@markerikson
Copy link
Collaborator

I don't want to add any other special syntax to createSlice at this time. There's no syntactic difference between writing a selector separately from the createSlice call (like, say, 5 lines below it), and trying to jam more options into createSlice. And, as @phryneas said, all of the options for createSlice are currently used as part of the reducer/action generation process. There's no benefit to having another option in there.

@phryneas
Copy link
Member

phryneas commented Mar 9, 2020

As for your other points: redux(-toolkit) and MST are conceptually very different, as they have a completely different data-flow model. Using the same approach for both is usually not a good idea.

MST is a per-object approach. You call actions on one object, that object is modified.
Redux is a event-based approach. You dispatch an event and things in the state might change depending on that, or even not at all. That's why we recommend not to name actions "setX" but rather "Yhappened". Your business logic is in the reducer. The login page emits the information that the user triggered a login event, the state reacts accordingly without the login page even really needing the information which part of the state reacts to that.
So in the end, while a slice could be a "complete" model of a domain, often it isn't. Multiple slices can react to the same action, even from other slices. There is no guarantee that everything is always in one place.

As for tracking data access, this PR might be of interest to you.

@ashish-r
Copy link

ashish-r commented May 9, 2021

@yeion7 @markerikson create-global-state-selector derives global state selectors from the local slice selectors based on the store signature provided.

const { selectX, selectY, selectZ } = createGlobalStateSelector(
  {
    selectX: (state: Record<string, any>): number => state.x,
    selectY: (state: Record<string, any>): number => state.y,
    selectZ: (state: Record<string, any>): string => state.z,
  },
  'a',
  'b'
);

// Final store signature after combineReducers
const  store = { a: { b: { x:  55, y:  65, z:  'temp' } } };

selectX(store) // 55
selectY(store) // 65
selectZ(store) // 'temp'

the use case explained here also handles a paradigm issue of how should we store the global selector functions in the project folder, such that minimum change is required after some change in the combineReducer structure?

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

4 participants