Аутентифицировать асинхронность с помощью react-router-v4

У меня есть этот компонент PrivateRoute (из документов):

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    isAuthenticated ? (
      <Component {...props}/>
    ) : (
      <Redirect to={{
        pathname: '/login',
        state: { from: props.location }
      }}/>
    )
  )}/>
)

Я хотел бы изменить isAuthenticated на запрос aysnc isAuthenticated(). Однако до того, как ответ будет возвращен, страница перенаправляется.

Чтобы уточнить, функция isAuthenticated уже настроена.

Как я могу дождаться завершения асинхронного вызова, прежде чем решить, что отображать?


person tommyd456    schedule 11.09.2017    source источник


Ответы (3)


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

class PrivateRoute extends React.Component {
  state = {
    loading: true,
    isAuthenticated: false,
  }
  componentDidMount() {
    asyncCall().then((isAuthenticated) => {
      this.setState({
        loading: false,
        isAuthenticated,
      });
    });
  }
  render() {
    const { component: Component, ...rest } = this.props;
    if (this.state.loading) {
      return <div>LOADING</div>;
    } else {
      return (
        <Route {...rest} render={props => (
          <div>
            {!this.state.isAuthenticated && <Redirect to={{ pathname: '/login', state: { from: this.props.location } }} />}
            <Component {...this.props} />
          </div>
          )}
        />
      )
    }
  }
}
person pizzarob    schedule 11.09.2017
comment
state = … не работал у меня внутри объявления класса. Вместо этого мне пришлось использовать конструктор для установки начального состояния. - person raddevon; 04.10.2017
comment
Вам нужно будет настроить Babel для использования преобразования свойств класса или использовать предустановленную стадию Babel 0–2, чтобы это работало @raddevon - person pizzarob; 04.10.2017
comment
@ pizza-r0b, можете ли вы сказать мне, как настройка маршрутизатора index.js подходит для вашего кода? - person AnBisw; 09.06.2018
comment
насколько безопасно то, что isAuthenticated действительно хранится как переменная состояния? Не мог ли пользователь просто открыть консоль разработчика и изменить значение на true? - person J. Pedro; 28.09.2018
comment
@ J.Pedro по своей природе JS на стороне клиента небезопасен. Чтобы изменить переменную isAuthenticated на true, потребуется немного больше, чем просто открыть консоль разработчика — вам, вероятно, придется просмотреть скомпилированный минимизированный код, а затем проксировать новый JS-файл. Если кто-то был достаточно полон решимости сделать это, он смог бы увидеть некоторый isAuthenticated пользовательский интерфейс, однако серверная часть отвечает за отображение всех аутентифицированных данных и проверку подлинности пользователя и его разрешения на просмотр данных. Таким образом, даже если кто-то увидел аутентифицированный пользовательский интерфейс, безопасность конфиденциальных данных зависит от серверной части. - person pizzarob; 29.09.2018
comment
Метод componentDidMount() @pizza-r0b вызывается только один раз. Он работает только при вызовах, когда пользователь нажимает на первую ссылку. Он не удовлетворяет условию, т. е. всякий раз, когда пользователь посещает частный маршрут, должен вызываться componentDidMount или метод, содержащий обещание. Как я могу достичь этого? - person Rohit Sawai; 03.12.2019
comment
Почему бы просто не пройти аутентификацию один раз и не сохранить токен аутентификации? - person pizzarob; 04.12.2019

Если кого-то интересует реализация @CraigMyles с использованием хуков вместо компонента класса:

export const PrivateRoute = (props) => {
    const [loading, setLoading] = useState(true);
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    const { component: Component, ...rest } = props;

    useEffect(() => {
        const fetchData = async () => {
            const result = await asynCall();

            setIsAuthenticated(result);
            setLoading(false);
        };
        fetchData();
    }, []);

    return (
        <Route
            {...rest}
            render={() =>
                isAuthenticated ? (
                    <Component {...props} />
                ) : loading ? (
                    <div>LOADING...</div>
                ) : (
                    <Redirect
                        to={{
                            pathname: "/login",
                            state: { from: props.location },
                        }}
                    />
                )
            }
        />
    );
};


Что отлично работает при вызове с помощью:

<PrivateRoute path="/routeA" component={ComponentA} />
<PrivateRoute path="/routeB" component={ComponentB} />
person Josnei Luis Olszewski Junior    schedule 14.04.2020
comment
Я считаю, что вы можете предотвратить ненужные асинхронные вызовы, передав [setIsAuthenticated] в качестве второго аргумента в useEffect(). - person iamfrank; 26.07.2020
comment
Почему необходимо иметь состояние, которое отслеживает загрузку? Можно ли пересмотреть тернарный оператор, чтобы он учитывал только переменную состояния аутентификации? - person iamfrank; 26.07.2020
comment
Совсем забыл про второй аргумент, он обязателен! Из-за необходимости иметь отдельную загрузку, поскольку isAuthenticated, я полагаю, вы можете запустить последний undefined и перейти оттуда, это правильный подход! Спасибо за советы - person Josnei Luis Olszewski Junior; 10.08.2020
comment
Этот метод просто отображает экран загрузки, но он не обновляется после завершения асинхронных вызовов, он просто остается на экране загрузки, что мне с этим делать? - person sid_508; 29.01.2021

Решение @pizza-r0b отлично сработало для меня. Однако мне пришлось немного изменить решение, чтобы блок загрузки не отображался несколько раз (по одному разу для каждого PrivateRoute, определенного в приложении), путем рендеринга блока загрузки внутри, а не снаружи маршрута (аналогично Пример аутентификации React Router):

class PrivateRoute extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      loading: true,
      isAuthenticated: false
    }
  }

  componentDidMount() {
    asyncCall().then((isAuthenticated) => {
      this.setState({
        loading: false,
        isAuthenticated
      })
    })
  }

  render() {
    const { component: Component, ...rest } = this.props
    return (
      <Route
        {...rest}
        render={props =>
          this.state.isAuthenticated ? (
            <Component {...props} />
          ) : (
              this.state.loading ? (
                <div>LOADING</div>
              ) : (
                  <Redirect to={{ pathname: '/login', state: { from: this.props.location } }} />
                )
            )
        }
      />
    )
  }
}

Выдержка из моего App.js для полноты:

<DashboardLayout>
  <PrivateRoute exact path="/status" component={Status} />
  <PrivateRoute exact path="/account" component={Account} />
</DashboardLayout>
person Craig Myles    schedule 14.05.2018
comment
Эй, Крейг, это действительно работает, но не могли бы вы сказать мне, куда будет перенаправлен /login? Я имею в виду к какому компоненту? Где-то это должен быть компонент входа, но если я определю /login как <PrivateRoute exact path="/login" component={Login} /> в App.js, это будет бесконечный цикл. - person AnBisw; 08.06.2018
comment
@Annjawn Вы не определили бы маршрут /login как маршрут PrivateRoute, а обычный маршрут Route. Страницы входа не являются частными. - person empz; 16.07.2018