Доступ к значению состояния redux в веб-приложении React + Redux + Hooks + Typescript
Я пытаюсь получить доступ к состоянию редукции, чтобы отобразить его значение на моем веб-сайте. Я использую перехватчики React redux с функциональными компонентами и Typescript.
Ситуация: у меня есть магазин с двумя редьюсерами: UI и user. Исходное состояние:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: null
}
}
Когда пользователь входит в систему, происходит действие signinUser, которое правильно изменяет состояние redux. Например, для недопустимого входа состояние редукции будет следующим:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: {
general: 'wrong credentials, please try again'
}
}
}
Проблема: я пытаюсь получить доступ к UI.errors, чтобы отобразить их на своем веб-сайте. У меня есть функция в компоненте Signin с именем submitForm, которая вызывает действие signinUser, которое правильно отправляет действия. Моя проблема в том, что после этого я хочу получить state.ui.errors и не могу понять, как это сделать.
Я все это пробовал:
componentWillRecieveProps(nextProps) { ... }
это решение предназначено для компонентов класса, и я использую функциональные компонентыuseSelector((state: StoreState) => state.UI);
Если я делаю это внутри, то submitForm недействителен, потому что React Hooks не позволяет вызывать внутри функции. Если я сделаю это снаружи, он получит старое состояние.
Вот мои файлы (части, связанные с этой проблемой)
store.tsx
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
// Reducers
import userReducer from './reducers/userReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
UI: uiReducer
});
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(...middleware),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
userActions.tsx
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types';
import axios from 'axios';
// Interfaces
import { ISigninForm } from '../../utils/types';
// Redux
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
export const signinUser = (
userData: ISigninForm,
dispatch: Dispatch,
handleDialogClose: () => void
) => {
console.log('signinuser in userActions');
dispatch({ type: LOADING_UI });
axios
.post('/signin', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
getUserData(dispatch);
dispatch({ type: CLEAR_ERRORS });
handleDialogClose();
// history.push("/profile"); // this will redirect to a page not built yet
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
});
});
};
export const getUserData = (dispatch: Dispatch) => {
console.log('getUserData');
axios
.get('/user')
.then((res) => {
console.log('/user', res);
dispatch({
type: SET_USER,
payload: res.data
});
})
.catch((err) => console.log('err', err));
};
uiReducer.tsx
import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI, IAction } from '../types';
const initialState = {
loading: false,
errors: null
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_ERRORS:
return {
...state,
loading: false,
errors: action.payload
};
case CLEAR_ERRORS:
return {
...state,
loading: false,
errors: null
};
case LOADING_UI:
return {
...state,
loading: true
};
default:
return state;
}
}
userReducer.tsx
import {
SET_USER,
SET_AUTHENTICATED,
SET_UNAUTHENTICATED,
IAction
} from '../types';
const initialState = {
authenticated: false,
credentials: {}
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState;
case SET_USER:
console.log('SET_USER', action);
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
Signin.tsx
function Signin({ history }: RouteComponentProps): JSX.Element {
// States
const [dialogOpen, setDialogOpen] = React.useState(false);
const [errorsAPI, setErrorsAPI] = React.useState<ISigninErrors>({});
const [loading, setLoading] = React.useState(false);
// Dialog
const handleDialogOpen = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
};
// Form
const { register, handleSubmit, errors } = useForm<ISigninForm>();
const submitForm = (data: ISigninForm) => {
signinUser(data, dispatch, handleDialogClose);
};
return (
// HTML content
);
}
export default withRouter(Signin);
Решение: все это время у меня было решение, но я неправильно использовал эту функцию.
const state = useSelector((state: StoreState) => state);
Это вызывается внутри функционального компонента Signin. Затем, когда я возвращаю объект HTML, я просто вызываю
{state.UI.errors !== null && 'general' in state.UI.errors && (
<p>{state.UI.errors.general}</p>
)}