Redux не обновляет состояние, и я не могу понять, как сделать это правильно.

Вопрос или проблема

Я работаю с 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.

Решение проблемы с получением обновленного состояния

  1. Проверка селектора
    Вы используете селектор для получения списка состояний, но не правильно возвращаете его. Убедитесь, что вы правильно вызываете селектор в компоненте:

    const stateList = useSelector(selectStateList);

    Вместо этого, вы в текущем коде просто выводите его в консоль, но не обращаетесь к состоянию Redux.

  2. Изменение структуры данных в 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
    }));
});
  1. Асинхронный вызов в компоненте

Убедитесь, что вы вызываете 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 правильно. Если проблема сохраняется, дополнительно проверьте консоль на наличие ошибок или предупреждений, которые могут указать на другие проблемы в вашем коде.

Если у вас есть дополнительные вопросы или необходима помощь с отладкой, не стесняйтесь обращаться за поддержкой.

Оцените материал
Добавить комментарий

Капча загружается...