当我使用useSelector时,变量总是保持其初始状态。我有一种感觉,它存储在一些平行星系中,从未更新过。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
所以我将代码放在use-profile.js中,将redux-toolkit切片放在profile-slice.js中
profile-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;
use-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-profile.js中的最后几行;我可以隐藏最后三行并取消其他三行的注解)。因此,如果我使用getLoadingStateFromStoreGetter(values retrieved via store.getState()...),那么一些配置文件名称将显示已获取的名称,而一些配置文件名称持有“loading...”并永远卡住。这是有意义的。从redux store中检索正确的数据,并且我们没有订阅。
当我导出使用useSelector创建的另一个版本时,我总是得到它的初始状态。我从未收到任何用户名或指示“正在加载”的值。
我在StackOverflow上看过很多答案。一些常见的错误包括:
- 有些人说你的组件没有得到更新。事实并非如此,我测试了它,将console.log放置到代码中,并添加versionControl变量(参见代码)以确保它更新。
- 有些人的回答是,你没有正确地用reducer更新store,它仍然保存着相同的对象。事实并非如此,我尝试了两种方法,返回一个新的对象{... state,entities:{... state. entities... etc...}}并改变现有的代理对象-这两种方式我的reducer都应该提供一个新的对象,redux应该通知更改。
- 有时候创建多个store示例会把事情搞得一团糟。绝对不是这样的,我只调用了一个configureStore()和一个组件。
- 我在useSelector fn中有一个if语句,但是useSelector钩子本身是无条件调用的。
我不知道还有什么其他原因导致useSelect根本不起作用。有人能帮助我理解吗?
2条答案
按热度按时间am46iovg1#
操作,像往常一样,非常简单的错字是原因。这么多的时间花了。非常抱歉那些谁花时间试图看看这个,并感谢您的时间。
不应该有.loadingState但是. state。就是这样。
p4rjhz4m2#
文件use-profile.js中存在打字错误。
线:
更正: