При изменении состояния Redux все компоненты получают обновленные значения, КРОМЕ одного - PullRequest
2 голосов
/ 20 марта 2020

В моем компоненте A я хочу получить реквизит isAuthenticated из состояния Redux. Это всегда null, что является начальным значением, тогда как во всех других компонентах это всегда самое последнее значение.

Когда пользователь аутентифицируется, я отправляю его учетные данные на сервер. В случае успеха я сохраняю token в состоянии Redux. Но мой компонент A не видит, что token.

Я пытался подключить хранилище Redux напрямую к компоненту A и извлечь это isAuthenticated, но опять же, когда я console.log(this.props.isAuthenticated)* 1011, он всегда равен нулю. *

Полный код находится в репозитории GitHub

Компонент A:

import React from "react";
import { connect } from "react-redux";
import { auth } from "../../store/actions/index";
import { Redirect } from "react-router-dom";
import Input from "../../components/UI/input/input";
import Button from "../../components/UI/button/button";

class Auth extends React.Component {
  state = {
    controls: {
      email: {
        elType: "input",
        elConfig: {
          type: "email",
          name: "email",
          placeholder: "Email",
          label: "Email"
        },
        value: "",
        validation: {
          required: true,
          isEmail: true
        },
        valid: false,
        touched: false
      },
      password: {
        elType: "input",
        elConfig: {
          type: "password",
          name: "password",
          label: "Password",
          placeholder: "Password"
        },
        value: "",
        validation: {
          required: true,
          minLength: 9
        },
        valid: false,
        touched: false,
        redirect: null
      }
    }
  };

  checkValidity = (value, rules) => {
    let valid = true;
    if (!rules) return true;

    if (rules.required) valid = value.trim() !== "" && valid;
    if (rules.minLength) valid = value.length >= rules.minLength && valid;
    if (rules.maxLength) valid = value.length <= rules.maxLength && valid;

    if (rules.isEmail) {
      const pattern = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
      valid = pattern.test(value) && valid;
    }
    return valid;
  };

  inputChangedHandler = (e, elId) => {
    const updatedControls = {
      ...this.state.controls,
      [elId]: {
        ...this.state.controls[elId],
        value: e.target.value,
        valid: this.checkValidity(
          e.target.value,
          this.state.controls[elId].validation
        ),
        touched: true
      }
    };
    this.setState({
      controls: updatedControls
    });
  };

  signUpRedirect = () => {
    this.setState({
      redirect: "/sign-up"
    });
  };

  signInRedirect = () => {
    this.setState({
      redirect: "/sign-in"
    });
  };

  submitHandler = e => {
    e.preventDefault();
    this.props.onAuth(
      this.state.controls.email.value,
      this.state.controls.password.value,
      this.props.signUp
    );
  };

  render() {
    console.log(this.props.isAuthenticated);
    console.log(this.props);

    const formElementsArray = [];
    const { controls } = this.state;

    for (let key in controls) {
      formElementsArray.push({
        id: key,
        config: controls[key]
      });
    }

    const form = formElementsArray.map(el => (
      <Input
        key={el.id}
        elType={el.config.elType}
        elConfig={el.config.elConfig}
        value={el.config.value}
        shouldValidate={el.config.validation}
        touched={el.config.touched}
        valid={el.config.valid}
        name={el.config.elConfig.name}
        label={el.config.elConfig.label}
        placeholder={el.config.elConfig.placeholder}
        changed={e => this.inputChangedHandler(e, el.id)}
      />
    ));

    return (
      <section className="section-auth">
        {this.state.redirect !== null && this.state.redirect !== undefined && (
          <Redirect to={this.state.redirect} />
        )}
        <form onSubmit={this.submitHandler} className="section-auth__form">
          <h2 className="section-auth__title">
            {this.props.signUp ? "Sign up" : "Sign in"}
          </h2>
          {form}
          <Button
            type="submit"
            className="button--secondary"
            style={{
              marginTop: "2rem"
            }}
          >
            SUBMIT
          </Button>

          {this.props.signUp ? (
            <div className="section-auth__btn-box">
              <p className="section-auth__text">Already have an account?</p>
              <Button
                clicked={this.signInRedirect}
                type="button"
                className="button--primary section-auth__btn"
              >
                Sign in
              </Button>
            </div>
          ) : (
            <div className="section-auth__btn-box">
              <p className="section-auth__text">Don't have an account?</p>
              <Button
                clicked={this.signUpRedirect}
                type="button"
                className="button--primary section-auth__btn"
              >
                Sign up
              </Button>
            </div>
          )}
        </form>
      </section>
    );
  }
}

const mapStateToProps = state => ({
  isAuthenticated: state.auth.token
});

const mapDispatchToProps = dispatch => ({
  onAuth: (email, password, signUp) => dispatch(auth(email, password, signUp))
});

export default connect(mapStateToProps, mapDispatchToProps)(Auth);

Создатели действий:

import * as firebase from "firebase";
import {
  AUTH_START,
  AUTH_SUCCESS,
  AUTH_FAIL,
  AUTH_SIGNOUT
} from "./actionTypes";

const tokenExpTimeInMs = 3600000;

const saveToLocalStorage = (token, expDate, userId) => {
  localStorage.setItem("token", token);
  localStorage.setItem("expirationDate", expDate);
  localStorage.setItem("userId", userId);
};

export const authStart = () => ({ type: AUTH_START });

export const authSuccess = (token, userId) => ({
  type: AUTH_SUCCESS,
  idToken: token,
  userId: userId
});

export const authFail = error => ({ type: AUTH_FAIL, error: error });

export const authSignout = () => {
  localStorage.removeItem("token");
  localStorage.removeItem("expirationDate");
  localStorage.removeItem("userId");
  return {
    type: AUTH_SIGNOUT
  };
};

export const checkAuthTimeout = expTime => dispatch => {
  setTimeout(() => {
    dispatch(authSignout());
  }, expTime);
};

export const auth = (email, password, signUp) => dispatch => {
  dispatch(authStart());

  if (signUp) {
    firebase
      .auth()
      .createUserWithEmailAndPassword(email, password)
      .then(response => {
        response.user.getIdToken().then(token => {
          const expirationDate = new Date(
            new Date().getTime() + tokenExpTimeInMs
          );
          saveToLocalStorage(token, expirationDate, response.user.uid);
          dispatch(checkAuthTimeout(tokenExpTimeInMs));
          dispatch(authSuccess(token, response.user.uid));
        });
      })
      .catch(error => {
        console.log(error);
        dispatch(authFail(error));
      });
  } else {
    firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(response => {
        response.user.getIdToken().then(token => {
          const expirationDate = new Date(
            new Date().getTime() + tokenExpTimeInMs
          );
          saveToLocalStorage(token, expirationDate, response.user.uid);
          dispatch(checkAuthTimeout(tokenExpTimeInMs));
          dispatch(authSuccess(token, response.user.uid));
        });
      })
      .catch(error => {
        console.log(error);
        dispatch(authFail(error));
      });
  }
};

export const authCheckState = () => dispatch => {
  const token = localStorage.getItem("token");

  if (!token) {
    dispatch(authSignout());
  } else {
    const expDate = new Date(localStorage.getItem("expirationDate"));
    if (expDate <= new Date()) {
      dispatch(authSignout());
    } else {
      const userId = localStorage.getItem("userId");
      dispatch(authSuccess(token, userId));
      dispatch(checkAuthTimeout(expDate.getTime() - new Date().getTime()));
    }
  }
};

Редуктор:

import {
  AUTH_START,
  AUTH_SUCCESS,
  AUTH_FAIL,
  AUTH_SIGNOUT
} from "../actions/actionTypes";

const updateObject = (oldObject, updatedProperties) => {
  return {
    ...oldObject,
    ...updatedProperties
  };
};

const initialState = {
  token: null,
  userId: null,
  error: null
};

const authStart = (state, action) => updateObject(state, { error: null });

const authSuccess = (state, action) => {
  return updateObject(state, {
    token: action.idToken,
    userId: action.userId,
    error: null
  });
};

const authFail = (state, action) =>
  updateObject(state, {
    error: action.error
  });

const authSignout = (state, action) =>
  updateObject(state, { token: null, userId: null });

export default (state = initialState, action) => {
  switch (action.type) {
    case AUTH_START:
      return authStart(state, action);
    case AUTH_SUCCESS:
      return authSuccess(state, action);
    case AUTH_FAIL:
      return authFail(state, action);
    case AUTH_SIGNOUT:
      return authSignout(state, action);
    default:
      return state;
  }
};

Магазин Redux:

import thunk from "redux-thunk";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";

import authReducer from "./reducers/auth";
import userCdtReducer from "./reducers/userCountdownTimers";
import eventFormReducer from "./reducers/eventForm";

const rootReducer = combineReducers({
  auth: authReducer,
  userCdt: userCdtReducer,
  eventData: eventFormReducer
});

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunk))
);

Ответы [ 4 ]

2 голосов
/ 20 марта 2020

Ваш код работает, но после входа в систему это происходит . Поэтому, когда приложение отображает, маршрут sign-in исчезает, а Auth никогда не отображается.

Попробуйте выполнить следующее в src/App.js:

if (isAuth !== null) {
  routes = (
    <Switch>
      <Route path="/new-countdown-timer" component={EventForm} />
      <Route path="/my-countdown-timers" component={UserCountdownTimers} />
      <Route path="/signout" component={Signout} />
      <Route path="/" exact component={Home} />
      <Route path="*" exact={true} component={() => <h1>404</h1>} />
    </Switch>
  );
}

Затем при входе вы получите nice 404. Возможно, сделайте history.replace после успешного входа в систему

1 голос
/ 20 марта 2020

Ваш мини-проект работает для меня, я вижу токен.

Единственная причина, по которой вы не видите его в своей версии, заключается в том, что вы используете componentDidMount, который вызывается только один раз, прежде чем вы создадите API вызов. Если вы заменили компонент, монтированный на:

  componentDidUpdate(prevProps) {
      if (prevProps.state.token !== this.props.state.token) {
          console.log("[APP.JS], TOKEN:", this.props.state.token);
      }
  }

Вы увидите это,

здесь работает> codeSandbox

1 голос
/ 20 марта 2020

Мало шансов, что это проблема, но я думаю, что лучше всего объявлять состояние в конструкторе компонента. => https://reactjs.org/docs/state-and-lifecycle.html

Трудно отладить этот объем кода, просто взглянув на него. Я хотел бы создать меньший проект с упрощенной рабочей версией кода. Если он работает, начните с него и проверьте причину проблемы ...

0 голосов
/ 20 марта 2020

Я создал небольшую версию, и она работает. Я получаю токен в моем компоненте. Я бы предоставил коды и коробку с ним, но есть ошибка, которую я не могу обработать.

Учетные данные пользователя:

электронная почта t@t.com пароль 123456

GitHub repo

Я бы действительно использовал некоторую помощь, или я думаю, что потрачу дни, пытаясь ее исправить

Компонентное приложение. js

import React from "react";
import { connect } from "react-redux";
import { auth } from "./actions.js";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      emailValue: "",
      passwordValue: ""
    };
  }

  inputChangeHandler = (e, elName) => {
    if (elName === "email") {
      this.setState({
        emailValue: e.target.value
      });
    } else {
      this.setState({
        passwordValue: e.target.value
      });
    }
  };

  formSubmiHandler = e => {
    e.preventDefault();
    this.props.onAuth(this.state.emailValue, this.state.passwordValue);
  };

  componentDidMount() {
    console.log("[APP.JS], TOKEN:", this.props.state.token);
    console.log("[APP.JS], STATE:", this.props.state);
  }

  render() {
    console.log("[APP.JS], TOKEN:", this.props.state.token);
    console.log("[APP.JS], STATE:", this.props.state);
    return (
      <div className="App">
        {this.props.state.token !== null && "You're logged in"}
        <form onSubmit={this.formSubmiHandler}>
          <input
            type="text"
            placeholder="email"
            value={this.state.emailValue}
            onChange={e => this.inputChangeHandler(e, "email")}
          />
          <input
            type="password"
            placeholder="password"
            value={this.state.passwordValue}
            onChange={e => this.inputChangeHandler(e, "password")}
          />
          <button type="submit">submit</button>
        </form>
      </div>
    );
  }
}

const mapDispatchToProps = dispatch => {
  return {
    onAuth: (email, password) => dispatch(auth(email, password))
  };
};

const mapStateToProps = state => {
  return {
    state: state,
    isAuth: state.token
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(App);

index. js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

import thunk from "redux-thunk";
import { createStore, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";

import reducer from "./reducer";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  rootElement
);

Действия действия. js

import * as firebase from "firebase";

const firebaseConfig = {
};

firebase.initializeApp(firebaseConfig);

export const authStart = () => ({ type: "AUTH_START" });

export const authSuccess = (token, userId) => {
  return {
    type: "AUTH_SUCCESS",
    idToken: token,
    userId: userId
  };
};

export const authFail = error => ({ type: "AUTH_FAIL", error: error });

export const auth = (email, password) => dispatch => {
  dispatch(authStart());

  firebase
    .auth()
    .signInWithEmailAndPassword(email, password)
    .then(response => {
      response.user.getIdToken().then(token => {
        dispatch(authSuccess(token, response.user.uid));
        console.log(response);
      });
    })
    .catch(error => {
      console.log(error);
      dispatch(authFail(error));
    });
};

Редуктор-редуктор. js

const updateObject = (oldObject, updatedProperties) => {
  return {
    ...oldObject,
    ...updatedProperties
  };
};

const initialState = {
  token: null,
  userId: null,
  error: null
};

const authStart = (state, action) => updateObject(state, { error: null });

const authSuccess = (state, action) => {
  return updateObject(state, {
    token: action.idToken,
    userId: action.userId,
    error: null
  });
};

const authFail = (state, action) =>
  updateObject(state, {
    error: action.error
  });

export default (state = initialState, action) => {
  switch (action.type) {
    case "AUTH_START":
      return authStart(state, action);
    case "AUTH_SUCCESS":
      return authSuccess(state, action);
    case "AUTH_FAIL":
      return authFail(state, action);
    default:
      return state;
  }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...