Вопрос или проблема
Я работаю с React на фронтенде и пытаюсь использовать Redux с createAsyncThunk для выполнения API вызова. Данные загружаются корректно, и я вижу их с помощью console.log в свойстве ‘extraReducer’ внутри ‘slice’. Но чтобы обновить состояние и вернуть данные в компонент, это возвращает undefined. Странно то, что это работало изначально, но вдруг перестало возвращать обновленное состояние.
store.js
import { configureStore, applyMiddleware } from "@reduxjs/toolkit"
// import { getAvailableLocations } from "./middleware/locationThunk"
import userSlice from "./features/users/userSlice.jsx"
export const store = configureStore({
reducer: {
users: userSlice
},
devTools: process.env.NODE_ENV === 'development',
trace: true
})
util.js
export const initialUserState = {
first_name: '',
last_name: '',
email: '',
password: '',
location: '',
phone_number: '',
region: '',
data_of_birth: '',
gender: '',
conversion: '',
cityId: null,
city_list: [],
state_id: null,
state_list: [],
// status: 'idle', // | 'loading' | 'succeeded' | 'failed'
// error: null
}
userSlice.js
import initialUserState from './util'
export const getAvailableLocations = createAsyncThunk(
'/api/getLocation',
async (id) => {
try {
let getCities = []
const getStates = await GetState(countryid) // countryid
if(id) getCities = await GetCity(countryid, id)
return { getStates, getCities}
} catch (err) {
console.log({'userSlice.js: - nr.19 - getLocation': err})
return err.message
}
})
const userSlice = createSlice({
name: 'users',
initialState:{user: initialUserState, status: 'idle', error: null},
reducers: {
updateUser: {
modify(state, action) {
// state.user.users
console.log({state, action})
}
},
createUser: {createUser(state, action){
console.log({state, action})
state.user.users.push(action.payload)
},
},
updateProfile(state, action){
console.log({state, action})
},
},
extraReducers: builder => {
builder
.addCase(getAvailableLocations.fulfilled, (state, action) => {
state.status="success"
let stateList = action.payload.getStates
let optionStateList = stateList.map((state) => ({
label: state.name,
value: state.id
}))
let cityList = action.payload.getCities
let optionCitiesList = cityList.map((city) => ({
label: city.name,
value: city.id
}))
// console.log({reconstructedStateList: optionStateList, reconstructedCitiesList: optionCitiesList})
// state.user.state_list.push(optionStateList)
// state.user.city_list.push(optionCitiesList)
// state.user.state_list.concat({...optionStateList})
state.user.state_list = {...optionStateList}
state.user.city_list = {...optionCitiesList}
console.log({updated_state: state.user.state_list})
//return action.payload
// console.log({updated_StateList: state.user.state_list, updated_CityList:state.user.city_list})
})
.addCase(getAvailableLocations.pending, (state, action) => {
state.status="loading"
})
.addCase(getAvailableLocations.rejected, (state, action)=> {
state.status="failed"
state.error = action.error
})
},
selectors: {
selectStateList: (state) => state.user.state_list
}
}
)
const{ selectStateList } = userSlice.selectors
// Извлечение объекта создателей действий и редьюсера
const { actions, reducer } = userSlice
// Извлечение и экспорт каждого создателя действия по имени
export {actions, selectStateList} // { createUser, updateUser, editLo }
// Экспорт редьюсера, либо как экспорт по умолчанию, либо как именованный экспорт
export default reducer
компонент:
import { useSelector, useDispatch } from 'react-redux'
import { getAvailableLocations, selectStateList } from '../../../store/features/users/userSlice.jsx' //, getUserProfile
export const ProfileTabInterface = () => {
const reduxDispatch = useDispatch()
const user = useSelector((state) => state.user)
console.log({reduxUser: user})
console.log({reduxStateList: selectStateList})
if(user?.state_list){
console.log('состояние списка прошло?')
const {city_list, state_list} = user
const stateList = state_list
const cityList = city_list
console.log({isEmptyStateList: stateList , isEmptyCityList: cityList})
}
let stateSelected = {id:2612}
let userFinalFormData = {
email: '',
phone_number: ''
}
const getLocations = async (id) => {
await reduxDispatch(getAvailableLocations(id))
// await reduxDispatch(getAvailableLocations(id)).unwrap()
}
useEffect(() => {
// console.log({stateSelected})
getLocations(stateSelected.id)
// reduxDispatch(getAvailableLocations(stateSelected.id))
// reduxDispatch(getUserProfile())
if(user){
const { email, phone_number } = user
userFinalFormData.email = email
userFinalFormData.phone_number = phone_number
Object.entries(userFinalFormData).map(([key, value]) => {
setEnteredInput((prevValues) => ({
...prevValues,
[key]: value
}))
})
}
}, [])
const handleGeneralUserInput = (identifier, value) => {
setEnteredInput((prevValues) => ({
...prevValues,
[identifier]: value
}))
const updateUserForm = {[identifier]: value}
dispatch({type: 'SET_GENERAL_USER_DATA', payload: updateUserForm})
}
const handleStatelUserInput = (identifier, value) => {
console.log({stateEvent: value, identifier})
if(!stateList) return
stateSelected = stateList.find((state) => state.id == Number(value))
handleGeneralUserInput('state_id', stateSelected.id)
handleGeneralUserInput(identifier, stateSelected.name)
reduxDispatch(getAvailableLocations(stateSelected.id))
}
const typeText="text"
const typePhone="tel"
const typeLocation = 'location'
const InterfaceConfiguration = {
buttonname: 'Сохранить изменения',
setItems : [
{ name: 'Email', id: 'email', type: typeText, placeholder: 'email', value: enteredInput?.email, error: errors.email, invalid: enteredInputIsInvalid.email, autoComplete: 'email', required:true, onChange: (e) => handleGeneralUserInput('email', e.target.value), onBlur : (e) => inputBlurHandle('email', e.target.value, setEnteredInputIsInvalid)},
{ name: 'PhoneNumber', id: 'phone_number', type: typePhone, placeholder: 'номер телефона', value: enteredInput?.phone_number, error: errors.phone_number, invalid: enteredInputIsInvalid.phone_number, required:true, onChange: (value) => handleGeneralUserInput('phone_number', value), onBlur : (e) => inputBlurHandle('phone_number', e.target.value, setEnteredInputIsInvalid)},
{ name: 'Location', id: 'location', type: typeLocation, value: enteredInput?.city, cityId: enteredInput?.city_id, stateId: enteredInput?.state_id, error: errors?.location, invalid: enteredInputIsInvalid?.city,
required:true , onChangeState: (e) => handleStatelUserInput('state', e.target.value), onChangeCity: (e) => handleCitylUserInput('city', e.target.value), onBlur : (e) => inputBlurHandle('city', e.target.value, setEnteredInputIsInvalid)},
]
}
console.log({enteredInputProfileTab: enteredInput})
return(
<>
<CreateFormInterface array={InterfaceConfiguration.setItems} />
</>
)
App.js
import { Provider } from 'react-redux'
import { store } from './store/index.js'
function App() {
const router = createBrowserRouter([
{
path: "https://stackoverflow.com/",
element: <RootLayout />,
errorElement: <ErrorPage />,
id: 'root',
loader: tokenLoader,
children: [
{ path: "https://stackoverflow.com/", element: <HomePage /> },
{ path: '/sign-up', element: <SignUpPage />, loader: SignUpLoader },
{ path: '/work', element: <LoginPage /> },
{ path: '/calendar', element: <CalendarPage />, loader: CalendarLoader},
{ path: '/subscribe', element: <OrderPage />},
{ path: '/payment', element: <PaymentPage />},
{ path: '/dashboard/*', element: <DashboardPage />}, //, loader: UserLoader
{ path: "https://stackoverflow.com/contact", element: <ContactPage />},
]
}
])
return (
<Provider store={store}>
<ReactQueryClientProvider>
<UserFormContextProvider>
<RouterProvider router={router} ></RouterProvider>
</UserFormContextProvider>
</ReactQueryClientProvider>
</Provider>
)
}
export default App
Я пытался использовать возвращаемый ‘type’ для возврата обновленного состояния и пытался вернуть полезную нагрузку, но не получил желаемых результатов, все еще не могу получить данные внутри состояния (обновленного).
Ответ или решение
Проблема с обновлением состояния Redux в приложении React
Ваша проблема с обновлением состояния Redux, где вы корректно получаете данные из API, но не можете их извлечь в компоненты, может быть связана с несколькими аспектами вашей реализации. Давайте разберём основные моменты и возможные решения.
1. Правильное использование createAsyncThunk
Вы правильно используете createAsyncThunk
для выполнения асинхронных операций. Убедитесь, что вы правильно обрабатываете результаты запроса в extraReducers
– вы уже это делаете, однако важно убедиться, что состояние действительно обновляется так, как задумано.
2. Структура состояния
Ваше начальное состояние определяется как:
initialState: {user: initialUserState, status: 'idle', error: null}
Когда вы обновляете данные в вашем extraReducer
, вы манипулируете state.user
:
state.user.state_list = {...optionStateList}
state.user.city_list = {...optionCitiesList}
Однако, вместо этого, возможно, стоит использовать push()
или concat()
, если вы хотите добавить данные в массив, поскольку вы перезаписываете значение state.user.state_list
и state.user.city_list
.
Решение проблемы с получением обновленного состояния
-
Проверка селектора
Вы используете селектор для получения списка состояний, но не правильно возвращаете его. Убедитесь, что вы правильно вызываете селектор в компоненте:const stateList = useSelector(selectStateList);
Вместо этого, вы в текущем коде просто выводите его в консоль, но не обращаетесь к состоянию Redux.
-
Изменение структуры данных в
extraReducers
Убедитесь, что вы меняете состояние правильно. Если вам нужно сохранять список состояний и городов, используйте метод push
или связывайте массивы. Пример изменения статуса:
.addCase(getAvailableLocations.fulfilled, (state, action) => {
state.status = "success";
state.user.state_list = action.payload.getStates.map(state => ({
label: state.name,
value: state.id
}));
state.user.city_list = action.payload.getCities.map(city => ({
label: city.name,
value: city.id
}));
});
- Асинхронный вызов в компоненте
Убедитесь, что вы вызываете getAvailableLocations
в useEffect
и правильно обрабатываете результат. Например:
useEffect(() => {
getLocations(stateSelected.id).then(() => {
const userData = userFinalFormData;
userData.email = user.email;
userData.phone_number = user.phone_number;
// Дополнительная логика...
});
}, [user]); // Добавьте 'user' в зависимости
Таким образом, ваш useEffect
будет зависим от обновлений user
и вызовет логику, когда данные будут доступны.
Заключение
Проблема с отсутствующим обновленным состоянием в Redux может быть решена путем тщательной проверки ваших селекторов, корректного управления состоянием и обеспечения правильных вызовов асинхронных действий в компоненте. Убедитесь, что вы передаете правильные значения и устанавливаете их в state правильно. Если проблема сохраняется, дополнительно проверьте консоль на наличие ошибок или предупреждений, которые могут указать на другие проблемы в вашем коде.
Если у вас есть дополнительные вопросы или необходима помощь с отладкой, не стесняйтесь обращаться за поддержкой.