useSelector 不工作,redux 反应
useSelector is not working, redux in react
当我使用 useSelector 时,变量始终保持其初始状态。我觉得它存储在某个平行星系中并且从未更新过。但是当我用 const store = useStore(); 检索值时store.getState()... 它给出了正确的值(但缺少订阅)。当我在 redux devtools 中检查商店时,我可以看到所有值都正确记录在商店中。只是没有使用 useSelector 从商店中检索值。
我想要实现的是为用户配置文件提供一些缓存,即不在同一页面上多次获取 /api/profile/25。我不想将其视为“缓存”并发出多个请求只是记住请求被缓存并且很便宜而是将其视为从商店获取配置文件并记住在需要时获取配置文件,我意味着一些懒惰的更新。
实现应该看起来像一个钩子,即
// use pattern
const client = useProfile(userId);
// I can also put console.log here to see if the component is getting updated
let outputProfileName;
if( client.state==='pending' ) {
outputProfileName = 'loading...';
} else if( client.state==='succeeded' ) {
outputProfileName = <span>{client.data.name}</span>
} // ... etc
所以我使用了我的代码-profile.js,在配置文件中有 redux-toolkit 切片-slice.js
简介-slice.js
import {
createSlice,
//createAsyncThunk,
} from '@reduxjs/toolkit';
const entityInitialValue = {
data: undefined,
state: 'idle',
error: null
};
export const slice = createSlice({
name: 'profile',
initialState: {entities:{}},
reducers: {
updateData: (state,action) => {
// we received data, update the data and the status to 'succeeded'
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: action.payload.data,
state: 'succeeded',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchPendStart: (state,action) => {
// no data - indicates we started fetching
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: null,
state: 'pending',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchError: (state,action) => {
state.entities[action.payload.id] = {
//...entityInitialValue,
...state.entities[action.payload.id],
data: null,
state: 'failed',
error: action.payload.error
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
},
extraReducers: {
}
});
export const {updateData,dispatchPendStart,dispatchError} = slice.actions;
// export const selectProfile... not used
export default slice.reducer;
使用-profile.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import {
updateData as actionUpdateData,
dispatchPendStart as actionDispatchPendStart,
dispatchError as actionDispatchError,
} from './profile-slice';
//import api...
function useProfile(userId) {
const dispatch = useDispatch();
const actionFunction = async () => {
const response = await client.get(`... api endpoint`);
return response;
};
const store = useStore();
// versionControl is a dummy variable added for testing to make sure the component is updated;
// it is updated: I tried adding console.log to my component function (where I have const client = useProfile(clientId)...)
const [versionControl,setVersionControl] = useState(0);
const updateVersion = () => setVersionControl(versionControl+1);
// TODO: useSelector not working
const updateData = newVal => { dispatch(actionUpdateData({id:userId,data:newVal})); updateVersion(); };
const dispatchPendStart = newVal => { dispatch(actionDispatchPendStart({id:userId})); updateVersion(); };
const dispatchError = newVal => { dispatch(actionDispatchError({id:userId,error:newVal})); updateVersion(); };
const [
getDataFromStoreGetter,
getLoadingStateFromStoreGetter,
getLoadingErrorFromStoreGetter,
] = [
() => (store.getState().profile.entities[userId]||{}).data,
() => (store.getState().profile.entities[userId]||{}).state,
() => (store.getState().profile.entities[userId]||{}).error,
];
const [
dataFromUseSelector,
loadingStateFromUseSelector,
loadingErrorFromUseSelector,
] = [
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].data : undefined ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingError : undefined ),
];
useEffect( async () => {
if( !(['pending','succeeded','failed'].includes(getLoadingStateFromStoreGetter())) ) {
// if(requestOverflowCounter>100) { // TODO: protect against infinite loop of calls
dispatchPendStart();
try {
const result = await actionFunction();
updateData(result);
} catch(e) {
dispatchError(e);
throw e;
}
}
})
return {
versionControl, // "versionControl" is an approach to force component to update;
// it is updating, I added console.log to the component function and it runs, but the values
// from useSelector are the same all the time, never updated; the problem is somewhere else; useSelector is just not working
// get data() { return getDataFromStoreGetter(); }, // TODO: useSelector not working; but I need subscribtions
// get loadingState() { return getLoadingStateFromStoreGetter(); },
// get loadingError() { return getLoadingErrorFromStoreGetter(); },
data: dataFromUseSelector,
loadingState: loadingStateFromUseSelector,
loadingError: loadingErrorFromUseSelector,
};
}
export default useProfile;
store.js
import { configureStore,combineReducers } from '@reduxjs/toolkit';
import profileReducer from '../features/profile/profile-slice';
// import other reducers
export default configureStore({
reducer: {
profile: profileReducer,
// ... other reducers
},
});
component.js - 居然看到上面的use pattern,除了贴出来的几行,没什么意思
所以
当我导出加载状态时(我指的是使用中的最后几行-profile.js;我可以取消最后三行并取消注释其他三行)。因此,如果我使用 getLoadingStateFromStoreGetter(通过 store.getState()... 检索到的值),则一些配置文件名称会显示已获取的名称,而另一些则会显示“正在加载...”并永远卡住。这说得通。从 redux 存储中检索到正确的数据,我们没有订阅。
当我导出使用 useSelector 创建的另一个版本时,我总是得到它的初始状态。我从未收到任何用户名或指示“正在加载”的值。
我在 Whosebug 上阅读了很多答案。一些常见错误包括:
有人说您的组件没有更新。事实并非如此,我测试了它,将 console.log 放置到代码中并添加了 versionControl 变量(请参阅代码)以确保它更新。
有些回答说你没有正确更新 store 和 reducers,它仍然持有相同的对象。事实并非如此,我尝试了两种方法,return 一个新的新对象 {...state,entities:{...state.entities...etc...}} 并改变现有的代理对象 - 我的 reducers 应该提供一个新对象,redux 应该通知更改。
有时会创建多个商店实例,事情会变得一团糟。绝对不是这样,我调用了一次 configureStore() 和一个组件。
我的代码中也没有发现违反钩子规则的情况。我在 useSelector fn 中有一个 if 语句,但 useSelector 挂钩本身被无条件调用。
我不知道还有什么其他原因导致 useSelect 根本不起作用。谁能帮我理解一下?
哎呀,和往常一样,很简单的错字就是原因。花了那么多小时。非常抱歉那些花时间查看此内容的人,感谢您的宝贵时间。
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )
应该不是.loadingState,而是.state。就是这样。
当我使用 useSelector 时,变量始终保持其初始状态。我觉得它存储在某个平行星系中并且从未更新过。但是当我用 const store = useStore(); 检索值时store.getState()... 它给出了正确的值(但缺少订阅)。当我在 redux devtools 中检查商店时,我可以看到所有值都正确记录在商店中。只是没有使用 useSelector 从商店中检索值。
我想要实现的是为用户配置文件提供一些缓存,即不在同一页面上多次获取 /api/profile/25。我不想将其视为“缓存”并发出多个请求只是记住请求被缓存并且很便宜而是将其视为从商店获取配置文件并记住在需要时获取配置文件,我意味着一些懒惰的更新。
实现应该看起来像一个钩子,即
// use pattern
const client = useProfile(userId);
// I can also put console.log here to see if the component is getting updated
let outputProfileName;
if( client.state==='pending' ) {
outputProfileName = 'loading...';
} else if( client.state==='succeeded' ) {
outputProfileName = <span>{client.data.name}</span>
} // ... etc
所以我使用了我的代码-profile.js,在配置文件中有 redux-toolkit 切片-slice.js
简介-slice.js
import {
createSlice,
//createAsyncThunk,
} from '@reduxjs/toolkit';
const entityInitialValue = {
data: undefined,
state: 'idle',
error: null
};
export const slice = createSlice({
name: 'profile',
initialState: {entities:{}},
reducers: {
updateData: (state,action) => {
// we received data, update the data and the status to 'succeeded'
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: action.payload.data,
state: 'succeeded',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchPendStart: (state,action) => {
// no data - indicates we started fetching
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: null,
state: 'pending',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchError: (state,action) => {
state.entities[action.payload.id] = {
//...entityInitialValue,
...state.entities[action.payload.id],
data: null,
state: 'failed',
error: action.payload.error
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
},
extraReducers: {
}
});
export const {updateData,dispatchPendStart,dispatchError} = slice.actions;
// export const selectProfile... not used
export default slice.reducer;
使用-profile.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import {
updateData as actionUpdateData,
dispatchPendStart as actionDispatchPendStart,
dispatchError as actionDispatchError,
} from './profile-slice';
//import api...
function useProfile(userId) {
const dispatch = useDispatch();
const actionFunction = async () => {
const response = await client.get(`... api endpoint`);
return response;
};
const store = useStore();
// versionControl is a dummy variable added for testing to make sure the component is updated;
// it is updated: I tried adding console.log to my component function (where I have const client = useProfile(clientId)...)
const [versionControl,setVersionControl] = useState(0);
const updateVersion = () => setVersionControl(versionControl+1);
// TODO: useSelector not working
const updateData = newVal => { dispatch(actionUpdateData({id:userId,data:newVal})); updateVersion(); };
const dispatchPendStart = newVal => { dispatch(actionDispatchPendStart({id:userId})); updateVersion(); };
const dispatchError = newVal => { dispatch(actionDispatchError({id:userId,error:newVal})); updateVersion(); };
const [
getDataFromStoreGetter,
getLoadingStateFromStoreGetter,
getLoadingErrorFromStoreGetter,
] = [
() => (store.getState().profile.entities[userId]||{}).data,
() => (store.getState().profile.entities[userId]||{}).state,
() => (store.getState().profile.entities[userId]||{}).error,
];
const [
dataFromUseSelector,
loadingStateFromUseSelector,
loadingErrorFromUseSelector,
] = [
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].data : undefined ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingError : undefined ),
];
useEffect( async () => {
if( !(['pending','succeeded','failed'].includes(getLoadingStateFromStoreGetter())) ) {
// if(requestOverflowCounter>100) { // TODO: protect against infinite loop of calls
dispatchPendStart();
try {
const result = await actionFunction();
updateData(result);
} catch(e) {
dispatchError(e);
throw e;
}
}
})
return {
versionControl, // "versionControl" is an approach to force component to update;
// it is updating, I added console.log to the component function and it runs, but the values
// from useSelector are the same all the time, never updated; the problem is somewhere else; useSelector is just not working
// get data() { return getDataFromStoreGetter(); }, // TODO: useSelector not working; but I need subscribtions
// get loadingState() { return getLoadingStateFromStoreGetter(); },
// get loadingError() { return getLoadingErrorFromStoreGetter(); },
data: dataFromUseSelector,
loadingState: loadingStateFromUseSelector,
loadingError: loadingErrorFromUseSelector,
};
}
export default useProfile;
store.js
import { configureStore,combineReducers } from '@reduxjs/toolkit';
import profileReducer from '../features/profile/profile-slice';
// import other reducers
export default configureStore({
reducer: {
profile: profileReducer,
// ... other reducers
},
});
component.js - 居然看到上面的use pattern,除了贴出来的几行,没什么意思
所以
当我导出加载状态时(我指的是使用中的最后几行-profile.js;我可以取消最后三行并取消注释其他三行)。因此,如果我使用 getLoadingStateFromStoreGetter(通过 store.getState()... 检索到的值),则一些配置文件名称会显示已获取的名称,而另一些则会显示“正在加载...”并永远卡住。这说得通。从 redux 存储中检索到正确的数据,我们没有订阅。
当我导出使用 useSelector 创建的另一个版本时,我总是得到它的初始状态。我从未收到任何用户名或指示“正在加载”的值。
我在 Whosebug 上阅读了很多答案。一些常见错误包括:
有人说您的组件没有更新。事实并非如此,我测试了它,将 console.log 放置到代码中并添加了 versionControl 变量(请参阅代码)以确保它更新。
有些回答说你没有正确更新 store 和 reducers,它仍然持有相同的对象。事实并非如此,我尝试了两种方法,return 一个新的新对象 {...state,entities:{...state.entities...etc...}} 并改变现有的代理对象 - 我的 reducers 应该提供一个新对象,redux 应该通知更改。
有时会创建多个商店实例,事情会变得一团糟。绝对不是这样,我调用了一次 configureStore() 和一个组件。
我的代码中也没有发现违反钩子规则的情况。我在 useSelector fn 中有一个 if 语句,但 useSelector 挂钩本身被无条件调用。
我不知道还有什么其他原因导致 useSelect 根本不起作用。谁能帮我理解一下?
哎呀,和往常一样,很简单的错字就是原因。花了那么多小时。非常抱歉那些花时间查看此内容的人,感谢您的宝贵时间。
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )
应该不是.loadingState,而是.state。就是这样。