Я столкнулся с проблемой, которую не могу решить. В моей функции 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 не ждет.
проблема со входом в систему gif
🤔 А знаете ли вы, что...
React Testing Library - это инструмент для тестирования компонентов React.
Вы объявили свой 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
, чтобы вы могли await
logIn
разрешать состояние 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);
}
};