Я столкнулся с проблемой, которую не могу решить. В моей функции handleLogIn в компоненте ContextProvider я использую перехватчик useNavigate для перенаправления страницы на «/», если пользователь успешно входит в систему. Однако после успешного входа в систему он не перенаправляется на главную страницу; вместо этого они перенаправляются обратно на страницу входа. После второй отправки формы перенаправление страницы работает правильно.
Как я могу перенаправить пользователя со страницы «/sign-in» или «/sign-up» сразу после отправки формы?
вот мой contextProviderComponent:
export const ContextAPI = createContext<null | TContextAPI>(null);
const ContextProvider = ({ children }: { children: React.ReactNode }) => {
const navigate = useNavigate();
const [currentUser, setCurrentUser] = useState<firebase.User | undefined | null>(undefined);
const [currentUserId, setCurrentUserId] = useState<string | undefined | null>(undefined);
const [loading, setLoading] = useState<boolean>(true);
const { signUpValues, SignUpInputConstructor, handleRegister, signUpError } = useAuth();
const { logInValues, SignInInputConstructor } = useLogIn();
const { handleLogout, logOutError } = useLogOut();
const { newGroupName, isShowGroupCreator, setIsShowGroupCreator, setNewGroupName } = useGroupMenu();
const handleUserGroups = () => {
if (currentUserId) {
const groupRef = doc(db, `/user_groups/${currentUserId}/${newGroupName}`, uuid());
if (newGroupName === "" || newGroupName.length > 20) {
console.info("group name is incorrect");
} else {
setDoc(groupRef, { merge: true });
console.info("db updated sucessfully");
navigate("/");
}
} else {
console.info("currentUserId is not set");
}
};
const [logInError, setLogInError] = useState<string>("");
const logIn = (email: string, password: string) => {
return auth.signInWithEmailAndPassword(email, password);
};
const handleLogIn = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!/^[a-zA-Z\s].*@.*$/.test(logInValues.email)) {
return setLogInError("email is not correct");
}
if (logInValues.password === "") {
return setLogInError("password field can not be empty");
} else {
try {
setLogInError("");
setLoading(true);
logIn(logInValues.email, logInValues.password).then(() => {
navigate("/");
});
} catch (errors) {
setLogInError("Failed to log-in in account");
}
setLoading(false);
}
};
const vals: TContextAPI = {
setNewGroupName,
isShowGroupCreator,
setIsShowGroupCreator,
newGroupName,
currentUserId,
handleUserGroups,
handleLogout,
logOutError,
setLoading,
currentUser,
signUpValues,
SignUpInputConstructor,
handleRegister,
loading,
signUpError,
logInValues,
SignInInputConstructor,
handleLogIn,
logInError,
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
setCurrentUserId(user?.uid || null);
});
return unsubscribe;
}, []);
return <ContextAPI.Provider value = {vals}> {!loading && children} </ContextAPI.Provider>;
};
и это мой компонент входа в систему:
const LogIn = () => {
const SignInInputConstructor = useContextSelector(ContextAPI, (v) => v?.SignInInputConstructor);
const loading = useContextSelector(ContextAPI, (v) => v?.loading);
const currentUser = useContextSelector(ContextAPI, (v) => v?.currentUser);
const handleLogIn = useContextSelector(ContextAPI, (v) => v?.handleLogIn);
const logInError = useContextSelector(ContextAPI, (v) => v?.logInError);
return (
<>
<h1>Log In page</h1>
<form
onSubmit = {(e) => {
handleLogIn?.(e);
}}
>
{currentUser?.email}
{logInError && <Alert severity = "error">{logInError}</Alert>}
{SignInInputConstructor?.map((item) => (
<>
<Typography>{item.typography}</Typography>
<TextField
id = {item.id}
placeholder = {item.placeholder}
variant = {item.variant}
value = {item.value}
onChange = {item.onChange}
/>
</>
))}
<Button variant = "contained" type = "submit" disabled = {loading}>
Log In
</Button>
<Typography>
Need an account? <Link to = {"/sign-up"}>Sign Up</Link>
</Typography>
</form>
</>
);
};
и это мой компонент PrivateRoute:
const PrivateRoute = () => {
const currentUser = useContextSelector(ContextAPI, (v) => v?.currentUser);
if (currentUser === undefined) return null;
return currentUser ? <Outlet /> : <Navigate to = "/sign-in" />;
};
export default PrivateRoute;
и последний — мой основной компонент со всеми маршрутами:
const Main = () => {
const router = createBrowserRouter(
[
{
path: "/",
element: <App />,
children: [
{
path: "/sign-in",
element: <LogIn />,
},
{
path: "/sign-up",
element: <Register />,
},
{
path: "/",
element: <PrivateRoute />,
children: [
{
path: "/",
element: <GroupMenu />,
children: [
{
path: "/add-group",
element: <AddGroupModal />,
},
],
},
{
path: "/group-content",
element: <GroupContent />,
},
{
path: "/dashboard",
element: <Dashboard />,
},
],
},
],
},
],
{ basename: "/thatswhy_items_counter/" }
);
return (
<React.StrictMode>
<ThemeProvider theme = {theme}>
<RouterProvider router = {router} />
</ThemeProvider>
</React.StrictMode>
);
};
Если вам нужна дополнительная информация, вот репозиторий проекта на GitHub: ссылка на репо Заранее спасибо всем за помощь!
Я попытался переместить свои функции входа из пользовательского перехватчика входа в компонент ContextProvider. Я пытался управлять состоянием условной загрузки в компоненте PrivateRoute. Я также попытался обновить свое текущее состояниеUser в функции входа в компонент ContextProvider. К сожалению, мне это не помогло. Я думаю, что у меня возникла эта проблема, потому что моей функции входа в систему требуется больше времени для входа в систему пользователя, а моя функция handleLogIn не ждет.
@DrewReese это Codesandbox моего кода: Редактировать проблему авторизации счетчика





Вы объявили свой router в ReactTree, поэтому, когда компонент по какой-либо причине перерисовывается, router переобъявляется, отключает старое дерево маршрутизации и монтирует новое, и это прерывает любые активные действия навигации.
Переместите объявление router из ReactTree.
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{
path: "/sign-in",
element: <LogIn />,
},
{
path: "/sign-up",
element: <Register />,
},
{
element: <PrivateRoute />,
children: [
{
path: "/",
element: <GroupMenu />,
children: [
{
path: "/add-group",
element: <AddGroupModal />,
},
],
},
{
path: "/group-content",
element: <GroupContent />,
},
{
path: "/dashboard",
element: <Dashboard />,
},
],
},
],
},
]);
const Main = () => {
return (
<React.StrictMode>
<ThemeProvider theme = {theme}>
<RouterProvider router = {router} />
</ThemeProvider>
</React.StrictMode>
);
};
Или запомните его, чтобы его можно было использовать в качестве стабильного справочного материала.
const Main = () => {
const router = useMemo(createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{
path: "/sign-in",
element: <LogIn />,
},
{
path: "/sign-up",
element: <Register />,
},
{
element: <PrivateRoute />,
children: [
{
path: "/",
element: <GroupMenu />,
children: [
{
path: "/add-group",
element: <AddGroupModal />,
},
],
},
{
path: "/group-content",
element: <GroupContent />,
},
{
path: "/dashboard",
element: <Dashboard />,
},
],
},
],
},
]), []);
return (
<React.StrictMode>
<ThemeProvider theme = {theme}>
<RouterProvider router = {router} />
</ThemeProvider>
</React.StrictMode>
);
};
Я рекомендую также преобразовать ваш обратный вызов handleLogIn в функцию async, чтобы вы могли awaitlogIn разрешать состояние loading и лучше управлять им, т. е. устанавливать loading false только после того, как попытка входа в систему была успешной или неудачной.
const handleLogIn = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!/^[a-zA-Z\s].*@.*$/.test(logInValues.email)) {
return setLogInError("email is not correct");
}
if (logInValues.password === "") {
return setLogInError("password field can not be empty");
}
try {
setLogInError("");
setLoading(true);
await logIn(logInValues.email, logInValues.password);
navigate("/");
} catch (errors) {
setLogInError("Failed to log-in in account");
} finally {
setLoading(false);
}
};
Кажется, все еще существует небольшая проблема с синхронизацией между тем, когда logIn или auth.signInWithEmailAndPassword успешно аутентифицирует пользователя, и когда Firebase может отправить изменение аутентификации вашему onAuthStateChange прослушивателю, который обновляет currentUser состояние на стороне клиента.
Вот еще пара вещей, которые вы можете попробовать:
Захватите значение userCredential.user (см. UserCredential) из разрешенного вызова signInWithEmailAndPassword и обновите состояние currentUser.
const ContextProvider = ({ children }: { children: React.ReactNode }) => {
const navigate = useNavigate();
const [currentUser, setCurrentUser] = useState<firebase.User | undefined | null>(undefined);
...
const logIn = (email: string, password: string) => {
return auth.signInWithEmailAndPassword(email, password);
};
const handleLogIn = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!/^[a-zA-Z\s].*@.*$/.test(logInValues.email)) {
return setLogInError("email is not correct");
}
if (logInValues.password === "") {
return setLogInError("password field can not be empty");
}
try {
setLogInError("");
setLoading(true);
const userCredential = await logIn(logInValues.email, logInValues.password);
setCurrentUser(userCredential.user);
navigate("/");
} catch (errors) {
setLogInError("Failed to log-in in account");
} finally {
setLoading(false);
}
};
...
Поместите вызов navigate в конец очереди событий Javascript. Обратите внимание, что это своего рода хак, но задержка вызова navigate позволяет Javascript сначала обработать любые другие дополнительные обратные вызовы, поставленные в очередь, а затем выполнить действие навигации.
const handleLogIn = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!/^[a-zA-Z\s].*@.*$/.test(logInValues.email)) {
return setLogInError("email is not correct");
}
if (logInValues.password === "") {
return setLogInError("password field can not be empty");
}
try {
setLogInError("");
setLoading(true);
await logIn(logInValues.email, logInValues.password);
setTimeout(() => navigate("/"));
} catch (errors) {
setLogInError("Failed to log-in in account");
} finally {
setLoading(false);
}
};
Большое спасибо за ваш ответ. Предложенные вами улучшения оказались очень полезными и сделали мой код лучше. К сожалению, моя проблема не решилась. Если пользователь не вошел в систему, ему все равно придется дважды отправить форму входа, чтобы он был перенаправлен на страницу «/». Я заметил, что по какой-то причине после первой отправки пользователь перенаправляется на страницу «/», а затем обратно на страницу «/sign-in». Второе отправление позволяет пользователю оставаться на странице «/» после отправки формы. Я добавил «гифку проблемы со входом в систему» в качестве наглядного объяснения под своим вопросом.
@ dimonchik11 Как я уже сказал, похоже, существует проблема со временем между тем, когда один API возвращает сообщение о том, что пользователь успешно прошел аутентификацию, и прослушивателем onAuthStateChanged, который его подхватывает. Вы выполняете действие навигации между ними. Позвольте мне добавить пару дополнительных предложений.
Большое спасибо за Вашу помощь. Отсрочка вызова навигации помогла мне избавиться от этой проблемы. Это действительно странно, потому что я уже пытался поместить вызов навигации в метод .then(), и он вызывал navigate("/") в конце всех обратных вызовов в очереди. Но теперь это работает, и я это очень ценю.
@dimonchik11 Это потому, что, несмотря на то, что это была цепочка обещаний, код по-прежнему был синхронным, т. е. не встречался await, поэтому цепочка обрабатывалась синхронно до завершения и функция возвращалась, а затем остальные обратные вызовы в очереди обрабатывались Javascript, включая обновление состояния React. Я надеюсь, что это имело смысл, несмотря на некоторую неловкость.
Я не вижу явных проблем с вашим кодом внешнего интерфейса, поэтому подозреваю, что это скорее проблема времени, когда
auth.signInWithEmailAndPasswordразрешается и возвращается (и перемещается) до того, какauth.onAuthStateChangedобнаружит, что состояние аутентификации изменилось..thenдолжен задерживать навигацию как минимум на цикл рендеринга. Думаете, вы могли бы создать работающую Codesandbox вашего кода, воспроизводящую проблему, которую мы могли бы проверить вживую?