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