Аутентифицируйте асинхронность с реакции-маршрутизатором-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 функция уже настроена.

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

2 ответа

Если вы не используете 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>
          )}
        />
      )
    }
  }
}

Если кого-то интересует реализация @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} />

Решение @pizza-r0b отлично сработало для меня. Однако мне пришлось немного изменить решение, чтобы не отображать загрузочный div несколько раз (по одному разу для каждого PrivateRoute, определенного в приложении), отображая загрузочный div внутри, а не снаружи, в Route (аналогично примеру аутентификации 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>
Другие вопросы по тегам