Redux Toolkit,尝试在 reducer 函数中使用过滤器,但它的行为不如预期

Redux Toolkit, trying to use filter in a reducer function, but it doesn't behave as expected

我有一个 reducer 应该过滤数组并更新它的状态


searchByName: (state, action) => {

      state.users = state.users.filter((user) =>
      user.name.toLowerCase().includes(action.payload.toLowerCase())
    ); 
    },

这是为了在 SPA 中包含搜索栏功能。但是,这种方式只有在我在搜索栏中输入时才有效,使用退格键删除它会停止工作。我尝试阅读不同的文档和资源,例如 https://redux.js.org/introduction/getting-started#redux-toolkit-example 以了解正在发生的事情并修改代码以使用 return 但似乎没有任何效果?

如果我按照某些答案中的建议这样使用它

searchByName: (state, action) => {

      return state.users.filter((user) =>
      user.name.toLowerCase().includes(action.payload.toLowerCase())
    ); 
    },

我收到 Uncaught (in promise) Error: [Immer] Immer only supports setting array indices and the 'length' propert 错误

此外,如果我使用 mapsort 它确实以这种方式工作,我不明白为什么 filter 不能。我该如何解决这个问题?

编辑

const slice = createSlice({
  name: "users",
  initialState: {
    users: [],
    isLoading: true,
    search: "",
  },
  reducers: {
    usersSuccess: (state, action) => {
      state.users = action.payload;
      state.isLoading = false;
    searchByName: (state, action) => {

      return {
        ...state.users,
        users: [...state.users].filter((user) => user.name.toLowerCase().includes(action.payload.toLowerCase()))
     };

    },
  },
});

编辑 2

const dispatch = useDispatch();
  const { users, isLoading } = useSelector((state) => state.users);
  const [searchTerm, setSearchTerm] = useState("");

  const changeSearchTerm = (e) => {
    setSearchTerm(e.target.value);
  };

  useEffect(() => {
    dispatch(userActions.searchByName(searchTerm));
    console.log(searchTerm);
  }, [searchTerm]);

  return (
    <div>
      <input onChange={changeSearchTerm} type="text" value={searchTerm}></input>
    </div>
  );
};

编辑 3

我在切片中添加了状态 filteredUsers

initialState: {
    users: [],
    filteredUsers: [],
    isLoading: true,
    search: "",
  },

然后在应该显示用户的组件中我改为

const { filteredUsers, isLoading } = useSelector((state) => state.filteredUsers);

然后映射出来

{filteredUsers.map((user) => (
            <Users user={user} />
          ))}

reducer 函数总是 return 的完整状态。 Reducers use Immer 在内部帮助管理不变性和编写良好的状态更改代码。

以下是您在 searchByName 中 return 的实际做法:

searchByName: (state, action) => {
    // The object you return is the full state object update in your reducer
    return {
       ...state,
       users: [...state.users].filter((user) => user.name.toLowerCase().includes(action.payload.toLowerCase())
    };
},

编辑:https://codesandbox.io/s/elated-estrela-b6o5p3?file=/src/index.js

然而,您的实际问题是,每当您在输入中搜索任何内容时,您都会覆盖您的state.users。因为通过 state.users = state.users.filter(... 甚至我上面建议的方式:return {...state, users: state.users.filter...,当您开始在搜索栏中输入任何内容时,您将丢失原始用户数据。

你的 reducer 无法回到用户列表的原始状态,因为它不知道原始状态是什么,因为它只有 users数组,正在修改中

Reducer 存储应该只用于 state-affecting 操作。在您的情况下,searchByName/搜索栏实现是您实际上只想在使用搜索栏+过滤用户的组件内部执行的操作。

解决方案 1:取消 searchByName 用户操作并直接在您的组件中执行

const dispatch = useDispatch();
  const { users, isLoading } = useSelector((state) => state.users);
  const [filteredUsers, setFilteredUsers] = useState(users);
  const [searchTerm, setSearchTerm] = useState("");

  const changeSearchTerm = (e) => {
    setSearchTerm(e.target.value);
  };

  useEffect(() => {
    setFilteredUsers(users.filter((user) => user.name.toLowerCase().includes(action.payload.toLowerCase());
    
  }, [searchTerm]);

  // Just for debug/logging purposes to see your filteredUsers
  useEffect(() => {console.log(filteredUsers)}, [filteredUsers]);

  return (
    <div>
      <input onChange={changeSearchTerm} type="text" value={searchTerm}></input>
    </div>
  );

解决方案 2:如果您想在其他 components/across 您的应用中使用 filteredUsers,请在您的 redux 存储中添加一个单独的字段来跟踪它们,这样您就不会丢失原始用户字段

const slice = createSlice({
  name: "users",
  initialState: {
    users: [],
    filteredUsers: [],
    isLoading: true,
    search: "",
  },
  reducers: {
    usersSuccess: (state, action) => {
      state.users = action.payload;
      state.isLoading = false;
    searchByName: (state, action) => {

      return {
        ...state,
        filteredUsers: [...state.users].filter((user) => user.name.toLowerCase().includes(action.payload.toLowerCase()))
     };

    },
  },
});

使用一个或另一个,而不是两个解决方案!


编辑:#2 这是一个完整的解决方案解决方案 #2 (https://codesandbox.io/s/elated-estrela-b6o5p3?file=/src/index.js:211-1748)

const users = createSlice({
  name: "users",
  initialState: {
    users: [{ name: "aldo" }, { name: "kiv" }],
    filteredUsers: [{ name: "aldo" }, { name: "kiv" }],
    isLoading: true,
    search: ""
  },
  reducers: {
    usersSuccess: (state, action) => {
      state.users = action.payload;
      state.isLoading = false;
      return {
        users: action.payload,
        filteredUsers: [...action.payload],
        isLoading: false
      };
    },
    searchByName: (state, action) => {
      const filteredUsers = state.users.filter((user) =>
        user.name.toLowerCase().includes(action.payload.toLowerCase())
      );
      return {
        ...state,
        filteredUsers:
          action.payload.length > 0 ? filteredUsers : [...state.users]
      };
    }
  }
});

const userActions = users.actions;

// store
var store = createStore(users.reducer);

// component
function App() {
  const dispatch = useDispatch();
  const users = useSelector((state) => state.users);
  const filteredUsers = useSelector((state) => state.filteredUsers);
  const [searchTerm, setSearchTerm] = useState("");

  console.log(users);

  const changeSearchTerm = (e) => {
    setSearchTerm(e.target.value);
  };

  useEffect(() => {
    dispatch(userActions.searchByName(searchTerm));
  }, [searchTerm, dispatch]);

  return (
    <div>
      <input onChange={changeSearchTerm} type="text" value={searchTerm} />
      <div>
        {filteredUsers.map((user) => (
          <div>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

// --root store config
const store = createStore(users.reducer);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);