Когда я использую 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
поэтому я поместил свой код в 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;
использовать-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 - посмотрите на шаблон использования выше, кроме опубликованных строк ничего интересного нет.
So
Когда я экспортирую состояние загрузки (я имею в виду последние строки в use-profile.js; я могу подавить последние три строки и раскомментировать остальные три). Итак, если я использую getLoadingStateFromStoreGetter (значения, полученные через store.getState () ...), тогда некоторые имена профилей будут отображать имена, которые были получены, а некоторые сохраняют загрузку ... и застревают навсегда. Это имеет смысл. Правильные данные извлекаются из хранилища redux, и у нас нет подписок.
Когда я экспортирую другую версию, созданную с помощью useSelector, я всегда получаю ее исходное состояние. Я никогда не получаю ни имени пользователя, ни значения, указывающего на загрузку.
Я прочитал много ответов на StackOverflow. Вот некоторые распространенные ошибки:
Некоторые говорят, что ваш компонент не обновляется. Это не тот случай, я протестировал его, поместив console.log в код и добавив переменную versionControl (см. В коде), чтобы убедиться, что он обновляется.
В некоторых ответах говорится, что вы не обновляете хранилище с помощью редукторов правильно, и он по-прежнему содержит тот же объект. Это не тот случай, я попробовал оба подхода, чтобы вернуть свежий новый объект {... state, entity: {... state.entities ... etc ...}} и изменить существующий прокси-объект - в обоих направлениях редукторы должны предоставлять новый объект, а редукторы должны уведомлять об изменениях.
Иногда создается несколько экземпляров магазина, и все происходит в беспорядке. Это определенно не так, у меня есть единственный вызов configureStore () и один компонент.
Также я не вижу нарушения правил хуков в своем коде. У меня есть оператор if внутри useSelector fn, но сам хук useSelector вызывается безоговорочно.
Я понятия не имею, по каким еще причинам useSelect просто не работает. Может ли кто-нибудь помочь мне понять?