Redux-toolkit: createEntityAdapter with react-redux useSelector is a bit painful.

Created on 12 Apr 2020  路  3Comments  路  Source: reduxjs/redux-toolkit

I like the normalized CRUD of createEntityAdapter to manipulate the data, because it is very professional, but when I take out the results, I always need to use 'selectAll' to parse the data, and always want to import Adapter, which makes me Mastered the magic and at the same time endured the magic to hurt me.

Edit new

import React from 'react';
import { Provider, useDispatch, useSelector, shallowEqual } from 'react-redux';
import {
  configureStore,
  createSlice,
  createEntityAdapter
} from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

const { reducer, actions, name } = createSlice({
  name: 'todos',
  initialState: todosAdapter.getInitialState(),
  reducers: {
    add: todosAdapter.addOne
  }
});

const store = configureStore({
  reducer: {
    [name]: reducer
  }
});

const { add } = actions;

const Adapter = () => {
  const dispatch = useDispatch();
  const todos = useSelector(store => store.todos, shallowEqual);
  console.log('todos: ', todos);
  // { "ids": [ 1 ], "entities": { "1": { "id": 1, "todo": "one" } } }
  // Can't get the original value

  // I have to parse the value every time
  const todosSelectors = todosAdapter.getSelectors(state => state.todos);
  const todos2 = useSelector(
    store => todosSelectors.selectAll(store),
    shallowEqual
  );
  console.log('todos2: ', todos2);
  // [ { "id": 1, "todo": "one" } ]
  // It took me some steps to get the result I want.

  return (
    <div>
      <button onClick={() => dispatch(add({ id: 1, todo: 'one' }))}>Add</button>
      <p>{JSON.stringify(todos, null, '  ')}</p>
      <p>{JSON.stringify(todos2, null, '  ')}</p>
    </div>
  );
};

export default () => (
  <Provider store={store}>
    <Adapter />
  </Provider>
);

Most helpful comment

The idea is more that you define const todosSelectors = todosAdapter.getSelectors(state => state.todos); in the same file as your slice (or your rootReducer, your choice) and import it from there, not that you do it in every component file. And especially not in each component render, as that would create new selectors every time.

Also, you can use it like

  const todos2 = useSelector(
-    store => todosSelectors.selectAll(store),
+    todosSelectors.selectAll,
-    shallowEqual
  );

The shallowEqual should in this case not be necessary if I'm not mistaken, as selectAll should memoize.

All 3 comments

The idea is more that you define const todosSelectors = todosAdapter.getSelectors(state => state.todos); in the same file as your slice (or your rootReducer, your choice) and import it from there, not that you do it in every component file. And especially not in each component render, as that would create new selectors every time.

Also, you can use it like

  const todos2 = useSelector(
-    store => todosSelectors.selectAll(store),
+    todosSelectors.selectAll,
-    shallowEqual
  );

The shallowEqual should in this case not be necessary if I'm not mistaken, as selectAll should memoize.

Do you mean this? For the convenience of presentation, I have put them all together.

import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import {
  configureStore,
  createSlice,
  createEntityAdapter
} from '@reduxjs/toolkit';

// Slice File
const todosAdapter = createEntityAdapter();
const todosSelectors = todosAdapter.getSelectors(state => state.todos);

const { reducer, actions, name } = createSlice({
  name: 'todos',
  initialState: todosAdapter.getInitialState(),
  reducers: {
    add: todosAdapter.addOne
  }
});

const { add } = actions;

// Root Reducer
const store = configureStore({
  reducer: {
    [name]: reducer
  }
});

const Adapter = () => {
  const dispatch = useDispatch();
  const todos = useSelector(todosSelectors.selectAll);
  console.log('todos2: ', todos);

  return (
    <div>
      <button onClick={() => dispatch(add({ id: 1, todo: 'one' }))}>Add</button>
      <p>{JSON.stringify(todos, null, '  ')}</p>
    </div>
  );
};

export default () => (
  <Provider store={store}>
    <Adapter />
  </Provider>
);

Pretty much that, yeah.

Was this page helpful?
0 / 5 - 0 ratings