useSelector不工作,react中的redux

nbewdwxp  于 2023-03-30  发布在  React
关注(0)|答案(2)|浏览(125)

当我使用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根本不起作用。有人能帮助我理解吗?

am46iovg

am46iovg1#

操作,像往常一样,非常简单的错字是原因。这么多的时间花了。非常抱歉那些谁花时间试图看看这个,并感谢您的时间。

useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )

不应该有.loadingState但是. state。就是这样。

p4rjhz4m

p4rjhz4m2#

文件use-profile.js中存在打字错误。
线:

useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )

更正:

useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].state : 'idle' )

相关问题