Страница перенаправления при входе в систему с использованием response-router-v5 и redux-toolkit

Я использую response-router-dom v5.2.

После входа в систему я хочу, чтобы моя страница перенаправлялась на /home с /. Форма входа находится по адресу /.

Когда я пытаюсь выполнить аутентификацию без какой-либо асинхронной функции (например, сравнивая имя пользователя и пароль с жестко запрограммированными значениями в реакции), все работает отлично.

Но когда я выполняю аутентификацию с использованием экспресс и mongo, перенаправление при входе в систему перестает работать. Если я снова войду в систему, произойдет перенаправление. Защищенные маршруты по-прежнему работают (перенаправление на страницу входа, если пользователь не вошел в систему).

Вот небольшая демонстрация проблемы, в которой я использую do auth с помощью express + mongo, т.е. асинхронное сокращение. Это работает не так, как задумано. https://youtu.be/Zxm5GOYymZQ

Вот ссылка на приложение, в котором я использую жестко запрограммированные имя пользователя и пароль (оба «тестовые») для аутентификации. Здесь нет асинхронности. Это работает как задумано. Имя пользователя и пароль являются "тестовыми". https://poke-zoo.herokuapp.com/


Вот App.js:

const ProtectedRoute = ({ component: Component, ...rest }) => {
  const authState = useSelector(selectorAuth)
  // const location = useLocation()
  return (
    <Route
      {...rest}
      render={props => {
        if (authState.isUserLoggedIn) {
          return <Component {...props} />
        } else {
          return (
            <Redirect
              to={{
                pathname: "/",
                state: {
                  from: props.location,
                },
              }}
            />
          )
        }
      }}
    />
  )
}

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Switch>
          <Route exact path="/" component={Landing} />
          <ProtectedRoute path="/home" component={Home} />
          <ProtectedRoute path="/explore" component={Explore} />
          <Route path="*" component={() => "404 Not found."} />
        </Switch>
      </div>
    </Router>
  )
}

Вот ModalLogin.js.

const ModalLogin = props => {
  const { loginModalBool, setLoginModalBool } = props
  const [username, setUsername] = useState("")
  const [password, setPassword] = useState("")

  const dispatch = useDispatch()
  const history = useHistory()

  const attemptLogin = e => {
    e.preventDefault()
    dispatch(tryLogin(username, password))
    history.push("/home")
  }

  return (
    <div tw="flex flex-col text-center h-full w-64 bg-gray-200 text-gray-900 rounded-lg shadow-lg p-2 md:p-4 lg:p-6">
      <div tw="flex flex-row justify-between">
        <p tw="text-lg">Login</p>
        <button tw="text-sm" onClick={() => setLoginModalBool(!loginModalBool)}>
          close
        </button>
      </div>
      <div tw="flex flex-col justify-around my-1">
        <form onSubmit={attemptLogin} tw="">
          <input
            tw="my-1"
            value={username}
            onChange={e => setUsername(e.target.value)}
            placeholder="username"
          />
          <input
            tw="my-1"
            value={password}
            onChange={e => setPassword(e.target.value)}
            type="password"
            placeholder="password"
          />
          <button
            type="submit"
            tw="my-1 p-1 rounded bg-gray-800 text-gray-100 hover:bg-gray-900"
          >
            log in
          </button>
        </form>
      </div>
    </div>
  )
}

Вот authSlice.js.

import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const initialState = {
  isUserLoggedIn: false,
  username: "",
}

export const authSlice = createSlice({
  name: "auth",
  initialState: initialState,
  reducers: {
    login: (state, action) => {
      const user = action.payload

      if (!user) return alert("Login failed. Incorrect username or password.")

      state.username = user.username
      state.isUserLoggedIn = true
    },
    logout: (state, action) => {
      // window.localStorage.removeItem("loggedInUser")
      state.username = ""
      state.isUserLoggedIn = false
    },
    signup: (state, action) => {
      const user = action.payload
      state.username = user.data.username
      state.isUserLoggedIn = true
    },
  },
})

export const tryLogin = (username, password) => {
  return async dispatch => {
    try {
      const response = await axios.post("/api/auth/login", {
        username: username,
        password: password,
      })

      const user = {
        token: response.headers["auth-token"],
        username: response.data.username,
      }

      // window.localStorage.setItem("token", response.headers["auth-token"])

      dispatch(login(user))
    } catch (e) {
      alert("Incorrect Username/Password.")
    }
  }
}

export const selectorAuth = state => state.auth
export const { login, logout } = authSlice.actions
export default authSlice.reducer


Я неправильно использую response-router с redux-toolkit?

Вот репозиторий Github


person theairbend3r    schedule 02.06.2020    source источник
comment
У меня работает ссылка Horeku, отключи свои расширения   -  person Kerem atam    schedule 04.06.2020
comment
Да, ссылки на героку работают. Потому что он не содержит асинхронной аутентификации с использованием экспресс и монго. Но при использовании для него express и mongo (как показано на видео) перенаправление перестает работать.   -  person theairbend3r    schedule 05.06.2020
comment
Плохо, я быстро читал: D Я клонировал и исправил ваш код. Вы можете попробовать и сообщить мне, сработает ли это.   -  person Kerem atam    schedule 06.06.2020


Ответы (3)


Ваш код не определяет логику перенаправления после входа в систему. Вы можете сделать это двумя способами.

1-й: вы можете определить другую оболочку перенаправления для аутентификации, если хотите, чтобы ваши маршруты перенаправлялись в случае аутентификации.

const AuthRoute = ({ component: Component, ...rest }) => {
  const authState = useSelector(selectorAuth)
  const location = useLocation()
  return (
    <Route
      {...rest}
      render={props => {
        if (!authState.isUserLoggedIn) {
          return <Component {...props} />
        } else {
          return (
            <Redirect
              to={{
                pathname: "/home",
                state: {
                  from: location,
                },
              }}
            />
          )
        }
      }}
    />
  )
}

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Switch>
          // It is for login users to redirect to home page
          <AuthRoute exact path="/" component={Landing} />
          <ProtectedRoute path="/home" component={Home} />
          <ProtectedRoute path="/explore" component={Explore} />
          <Route path="*" component={() => "404 Not found."} />
        </Switch>
      </div>
    </Router>
  )
}

2-й: Другим подходом может быть императивная обработка с помощью history.push () или history.replace ():

const Layout = () => {
  const authState = useSelector(selectorAuth);
  const history = useHistory();

  useEffect(() => {
    // if isUserLoggedIn turned to true redirect to /home
    if (authState.isUserLoggedIn) { 
      history.push("/home");
    }
  }, [authState.isUserLoggedIn]); // triggers when isUserLoggedIn changes

  return (
    <Switch>
      <Route exact path="/" component={Landing} />
      <ProtectedRoute path="/home" component={Home} />
      <ProtectedRoute path="/explore" component={Explore} />
      <Route path="*" component={() => "404 Not found."} />
    </Switch>
  );
};

const App = () => {
  return (
    <Router>
      <div tw="flex flex-col bg-green-100 min-h-screen">
        <Navbar />
        <Layout />
      </div>
    </Router>
  );
};

Почему ваш код не работал? Взгляните на код ниже:

      <Route exact path="/" component={Landing} />
      <ProtectedRoute path="/home" component={Home} />
      <ProtectedRoute path="/explore" component={Explore} />
      <Route path="*" component={() => "404 Not found."} />

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

Итак, вернемся к вашему случаю; когда вы входите в систему, вы не покидаете путь «/». Потому что не реализована логика, позволяющая покинуть путь «/». Таким образом, он снова совпадает с целевой страницей, даже если он аутентифицирован. Он совпадает с путем маршрута (целевая страница) и остается там. Это не продолжается и попробуйте свою логику на ProtectedRoute.

person Kerem atam    schedule 06.06.2020
comment
Спасибо за ответ. Теперь это работает! : D Но я не особо понял, почему это работает сейчас, что я делал не так раньше? Не могли бы вы объяснить это поподробнее? React-router меня действительно сбивает с толку (не так много мест, которые объясняют, что на самом деле происходит). Кроме того, можете ли вы объяснить (с помощью кода) второй подход? Я следил за кодом из упомянутой вами ссылки, прежде чем я застрял. - person theairbend3r; 06.06.2020

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

Проблема: слишком быстрое перенаправление

const attemptLogin = e => {
  e.preventDefault()
  dispatch(tryLogin(username, password))
  history.push("/home")
}

Этот код отправляет tryLogin и немедленно вызывает history.push("/home"). Он не ждет завершения отправленного действия и обновления хранилища.

Когда вы вызываете history.push("/home"), ваше приложение загружает ProtectedRoute для компонента Home. Внутри ProtectedRoute вы используете селектор для проверки значения authState.isUserLoggedIn. Если это false, вы перенаправляете на "/".

Таким образом, если вы перенаправляете на "/home" до того, как значение authState.isUserLoggedIn было обновлено, вы в конечном итоге будете перенаправлены обратно на "/" вместо того, чтобы иметь возможность просматривать компонент Home.

Вот почему у вас возникают следующие проблемы с асинхронной аутентификацией:

Когда я пытаюсь выполнить аутентификацию без какой-либо асинхронной функции (например, сравнивая имя пользователя и пароль с жестко запрограммированными значениями в реакции), все работает отлично.

Но когда я выполняю аутентификацию с использованием экспресс и mongo, перенаправление при входе в систему перестает работать.

Решение: дождитесь перенаправления

Вам нужно дождаться обновления authState перед перенаправлением на защищенный маршрут.

Есть много способов сделать это, но в целом мы хотим использовать селекторы для прослушивания изменений, отправляемых в состояние redux. Вот один из подходов, который использует ModalLogin для условного рендеринга Redirect компонента. Я также рекомендовал бы включить какой-то селектор для ошибок входа в систему на случай, если отправка завершилась, но вход в систему был неудачным. Вы хотели бы показать пользователю сообщение об ошибке в модальном окне.

const ModalLogin = props => {
  const { loginModalBool, setLoginModalBool } = props
  const [username, setUsername] = useState("")
  const [password, setPassword] = useState("")

  const dispatch = useDispatch()
  const history = useHistory()
  
  // Look at the authState from redux.  This value will automatically update.
  const authState = useSelector(selectorAuth)

  const attemptLogin = e => {
    e.preventDefault()
    dispatch(tryLogin(username, password))
    // don't redirect here anymore
  }

  // Once logged in, render the redirection
  if ( authState.isUserLoggedIn ) {
     return (
       <Redirect to="/home" />
     );
  }
  
  // Otherwise, render the modal
  return (
    /* your current code */
  )
}
person Linda Paiste    schedule 24.02.2021

честно говоря, я использую простой старый javascript для изменения местоположения, когда пользователь входит в систему

window.location = "/redirect"
person Brendan Power    schedule 04.06.2020
comment
Куда бы я поместил вышеуказанные файлы? ModalLogin.js? Любой способ сделать это с помощью реактивного маршрутизатора? - person theairbend3r; 05.06.2020
comment
спасибо, иногда самый простой способ - лучший способ ... - person Hank; 08.05.2021