Доступ к значению состояния redux в веб-приложении React + Redux + Hooks + Typescript - PullRequest
0 голосов
/ 11 июля 2020

Я пытаюсь получить доступ к состоянию redux, чтобы отобразить его значение на моем веб-сайте. Я использую перехватчики 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);

Это вызывается внутри компонента функции входа. Затем, когда я возвращаю объект HTML, я просто вызываю

 {state.UI.errors !== null && 'general' in state.UI.errors && (
              <p>{state.UI.errors.general}</p>
            )}
...