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
错误
此外,如果我使用 map
或 sort
它确实以这种方式工作,我不明白为什么 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")
);
我有一个 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
错误
此外,如果我使用 map
或 sort
它确实以这种方式工作,我不明白为什么 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")
);