英文:
React: Input loses focus a character is typed,
问题
尝试使用React进行开发。
我创建了一个登录页面,使用MUI、formik和yup作为输入字段的电子邮件和密码。但是,每当我尝试在输入一个字符后输入任何文本时,输入框就会失去焦点。其他功能都按预期工作。
如果我将这些代码移动到app.js中,那么就没有问题,但是将其移回login.jsx似乎根本不起作用。
以下是我的代码:
app.js
import {
Navigate,
RouterProvider,
createBrowserRouter,
} from "react-router-dom";
import { ColorModeContext, useMode } from "./theme";
import Login from "./pages/login/Login";
import { useContext, useState } from "react";
import { AuthContext } from "./context/AuthContext";
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
import Home from "./pages/home/Home";
import ErrorPage from "./pages/error/Error";
import SidebarComponent from "./components/sidebar/Sidebar";
import Register from "./pages/register/Register";
function App() {
const currentUser = useContext(AuthContext);
const [theme, colorMode] = useMode();
const Layout = () => {
<Box
sx={{
display: "flex",
height: "100%",
minHeight: "400px",
}}
>
<SidebarComponent />
<main>rtest</main>
</Box>;
};
const ProtectedRoute = ({ children }) => {
if (!currentUser.user) {
return <Navigate to="/login" />;
}
return children;
};
const router = createBrowserRouter([
{
path: "/",
element: (
<ProtectedRoute>
<Layout />
</ProtectedRoute>
),
children: [
{
path: "/",
element: <Home />,
},
],
errorElement: <ErrorPage />,
},
{
path: "/login",
element: <Login />,
},
{
path: "/login",
element: <Register />,
},
]);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<CssBaseline />
<div className="App">
<RouterProvider router={router} />
</div>
</ThemeProvider>
</ColorModeContext.Provider>
);
}
export default App;
Login.jsx
import Logo from "../../assets/backticklogo.svg";
import { Link, useNavigate } from "react-router-dom";
import { useContext, useEffect, useState } from "react";
import { AuthContext } from "../../context/AuthContext";
import apiClient from "../../services/api";
import { Box, Button, Stack, TextField, Typography } from "@mui/material";
import styled from "@emotion/styled";
import { useFormik } from "formik";
import * as yup from "yup";
const validationSchema = yup.object({
email: yup
.string("Enter your email")
.email("Enter a valid email")
.required("Email is required"),
password: yup
.string("Enter your password")
.min(8, "Password should be of minimum 8 characters length")
.required("Password is required"),
});
const Login = () => {
const localplainTextToken = localStorage.getItem("plainTextToken") || null;
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth") || null);
const [plainTextToken, setplainTextToken] = useState(localplainTextToken);
const remember = true;
const navigate = useNavigate();
const { user, dispatch } = useContext(AuthContext);
useEffect(() => {
isAuth && navigate("/");
}, [isAuth]);
const handleUpdateUser = (userInfo) => {
console.log(userInfo);
dispatch({ type: "LOGIN", payload: userInfo });
localStorage.setItem("user", JSON.stringify(userInfo));
localStorage.setItem("isAuth", true);
setIsAuth(true);
};
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: validationSchema,
onSubmit: async (values) => {
// alert(JSON.stringify(values, null, 2));
try {
await apiClient
.post("login", {
email: values.email,
password: values.password,
})
.then((res) => {
console.log(res);
localStorage.setItem("plainTextToken", res.data.plainTextToken);
setplainTextToken(res.data.plainTextToken);
return res.data;
})
.then(async (data) => {
try {
let user = await apiClient.get(`user/${data.user_id}`, {
headers: {
Authorization: `Bearer ${data.plainTextToken}`,
Accept: "application/json",
},
});
console.log(user);
handleUpdateUser(user.data);
// navigate("/");
} catch (error) {}
});
} catch (error) {
console.log(error);
}
},
});
/***********Styled component */
const LoginContainer = styled(Box)(({ theme }) => ({
display: "flex",
flex: "1 1 auto",
height: "100%",
width: "100%",
[theme.breakpoints.up("xs")]: {
flexDirection: "column-reverse",
},
[theme.breakpoints.up("md")]: {
flexDirection: "row",
},
}));
const RightBox = styled(Box)(({ theme }) => ({
backgroundColor: "rgb(14, 8, 39)",
display: "flex",
flexDirection: "column",
maxWidth: "100%",
[theme.breakpoints.up("xs")]: {
flex: "1 1 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "0 0 auto",
justifyContent: "center",
padding: "64px",
width: "600px",
},
}));
const LeftBox = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
backgroundColor: "rgb(28, 37, 54)",
backgroundImage: "url(/assets/gradient-bg.svg)",
backgroundPosition: "center top",
backgroundRepeat: "no-repeat",
color: "rgb(255, 255, 255)",
justifyContent: "center",
[theme.breakpoints.up("xs")]: {
flex: "0 0 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "1 1 auto",
padding: "64px",
},
}));
return (
<LoginContainer>
<LeftBox>
<Box sx={{ maxWidth: "900px" }}>
<Typography variant="h4"> Welcome to Backtic</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Some cool text here to represent out Backtick
</Typography>
</Box>
</LeftBox>
<RightBox>
<div>
<Box mb={"32px"}>
<Link to="/">
<img src={Logo} alt="Backtick Logo" width={140} />
</Link>
</Box>
</div>
<div>
<Stack direction="column" mb={"32px"}>
<Typography variant="h4" sx={{ color: "rgb(108, 115, 127)" }}>
Login
</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Don't have an account? <Link to="/register">Register</Link>
</Typography>
</Stack>
<form onSubmit={formik.handleSubmit}>
<Stack direction="column">
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={
formik.touched.password && Boolean(formik.errors.password)
}
helperText={formik.touched.password && formik.errors.password}
sx={{ marginTop: "24px" }}
/>
</Stack>
<Button
color="primary"
variant="contained"
fullWidth
type="submit"
size="large"
sx={{ marginTop: "24px" }}
>
Submit
</Button>
</form>
</div>
</RightBox>
</LoginContainer>
);
};
export default Login;
英文:
Tyring to play with react
Created a login page to have email and password as input field using MUI, formik and yup
But whenever I try to enter any text after one character the input loses the focus. Everything else works as expected.
If I move those code to app.js then there is no issue , but moving it back to login.jsx seems to not work at all.
below is my code
app.js
import {
Navigate,
RouterProvider,
createBrowserRouter,
} from "react-router-dom";
import { ColorModeContext, useMode } from "./theme";
import Login from "./pages/login/Login";
import { useContext, useState } from "react";
import { AuthContext } from "./context/AuthContext";
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
import Home from "./pages/home/Home";
import ErrorPage from "./pages/error/Error";
import SidebarComponent from "./components/sidebar/Sidebar";
import Register from "./pages/register/Register";
function App() {
const currentUser = useContext(AuthContext);
const [theme, colorMode] = useMode();
const Layout = () => {
<Box
sx={{
display: "flex",
height: "100%",
minHeight: "400px",
}}
>
<SidebarComponent />
<main>rtest</main>
</Box>;
};
const ProtectedRoute = ({ children }) => {
if (!currentUser.user) {
return <Navigate to="/login" />;
}
return children;
};
const router = createBrowserRouter([
{
path: "/",
element: (
<ProtectedRoute>
<Layout />
</ProtectedRoute>
),
children: [
{
path: "/",
element: <Home />,
},
],
errorElement: <ErrorPage />,
},
{
path: "/login",
element: <Login />,
},
{
path: "/login",
element: <Register />,
},
]);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<CssBaseline />
<div className="App">
<RouterProvider router={router} />
</div>
</ThemeProvider>
</ColorModeContext.Provider>
);
}
export default App;
Login.jsx
import Logo from "../../assets/backticklogo.svg";
import { Link, useNavigate } from "react-router-dom";
import { useContext, useEffect, useState } from "react";
import { AuthContext } from "../../context/AuthContext";
import apiClient from "../../services/api";
import { Box, Button, Stack, TextField, Typography } from "@mui/material";
import styled from "@emotion/styled";
import { useFormik } from "formik";
import * as yup from "yup";
const validationSchema = yup.object({
email: yup
.string("Enter your email")
.email("Enter a valid email")
.required("Email is required"),
password: yup
.string("Enter your password")
.min(8, "Password should be of minimum 8 characters length")
.required("Password is required"),
});
const Login = () => {
const localplainTextToken = localStorage.getItem("plainTextToken") || null;
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth") || null);
const [plainTextToken, setplainTextToken] = useState(localplainTextToken);
const remember = true;
const navigate = useNavigate();
const { user, dispatch } = useContext(AuthContext);
useEffect(() => {
isAuth && navigate("/");
}, [isAuth]);
const handleUpdateUser = (userInfo) => {
console.log(userInfo);
dispatch({ type: "LOGIN", payload: userInfo });
localStorage.setItem("user", JSON.stringify(userInfo));
localStorage.setItem("isAuth", true);
setIsAuth(true);
};
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: validationSchema,
onSubmit: async (values) => {
// alert(JSON.stringify(values, null, 2));
try {
await apiClient
.post("login", {
email: values.email,
password: values.password,
})
.then((res) => {
console.log(res);
localStorage.setItem("plainTextToken", res.data.plainTextToken);
setplainTextToken(res.data.plainTextToken);
return res.data;
})
.then(async (data) => {
try {
let user = await apiClient.get(`user/${data.user_id}`, {
headers: {
Authorization: `Bearer ${data.plainTextToken}`,
Accept: "application/json",
},
});
console.log(user);
handleUpdateUser(user.data);
// navigate("/");
} catch (error) {}
});
} catch (error) {
console.log(error);
}
},
});
/***********Styled component */
const LoginContainer = styled(Box)(({ theme }) => ({
display: "flex",
flex: "1 1 auto",
height: "100%",
width: "100%",
[theme.breakpoints.up("xs")]: {
flexDirection: "column-reverse",
},
[theme.breakpoints.up("md")]: {
flexDirection: "row",
},
}));
const RightBox = styled(Box)(({ theme }) => ({
backgroundColor: "rgb(14, 8, 39)",
display: "flex",
flexDirection: "column",
maxWidth: "100%",
[theme.breakpoints.up("xs")]: {
flex: "1 1 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "0 0 auto",
justifyContent: "center",
padding: "64px",
width: "600px",
},
}));
const LeftBox = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
backgroundColor: "rgb(28, 37, 54)",
backgroundImage: "url(/assets/gradient-bg.svg)",
backgroundPosition: "center top",
backgroundRepeat: "no-repeat",
color: "rgb(255, 255, 255)",
justifyContent: "center",
[theme.breakpoints.up("xs")]: {
flex: "0 0 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "1 1 auto",
padding: "64px",
},
}));
return (
<LoginContainer>
<LeftBox>
<Box sx={{ maxWidth: "900px" }}>
<Typography variant="h4"> Welcome to Backtic</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Some cool text here to represent out Backtick
</Typography>
</Box>
</LeftBox>
<RightBox>
<div>
<Box mb={"32px"}>
<Link to="/">
<img src={Logo} alt="Backtick Logo" width={140} />
</Link>
</Box>
</div>
<div>
<Stack direction="column" mb={"32px"}>
<Typography variant="h4" sx={{ color: "rgb(108, 115, 127)" }}>
Login
</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Don't have an account? <Link to="/register">Register</Link>
</Typography>
</Stack>
<form onSubmit={formik.handleSubmit}>
<Stack direction="column">
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={
formik.touched.password && Boolean(formik.errors.password)
}
helperText={formik.touched.password && formik.errors.password}
sx={{ marginTop: "24px" }}
/>
</Stack>
<Button
color="primary"
variant="contained"
fullWidth
type="submit"
size="large"
sx={{ marginTop: "24px" }}
>
Submit
</Button>
</form>
</div>
</RightBox>
</LoginContainer>
);
};
export default Login;
答案1
得分: 0
问题在于你在其他React组件内部声明了React组件。每次Login
重新渲染时,因为状态更新,样式化组件会被重新声明。这会导致旧的“实例”及其整个子React树被卸载,新的“实例”和子React树被挂载。正是在输入框被卸载/挂载时导致它们失去焦点。
将多余的组件声明移出Login
组件。
/***********Styled component */
const LoginContainer = styled(Box)(({ theme }) => ({
display: "flex",
flex: "1 1 auto",
height: "100%",
width: "100%",
[theme.breakpoints.up("xs")]: {
flexDirection: "column-reverse",
},
[theme.breakpoints.up("md")]: {
flexDirection: "row",
},
}));
const RightBox = styled(Box)(({ theme }) => ({
backgroundColor: "rgb(14, 8, 39)",
display: "flex",
flexDirection: "column",
maxWidth: "100%",
[theme.breakpoints.up("xs")]: {
flex: "1 1 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "0 0 auto",
justifyContent: "center",
padding: "64px",
width: "600px",
},
}));
const LeftBox = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
backgroundColor: "rgb(28, 37, 54)",
backgroundImage: "url(/assets/gradient-bg.svg)",
backgroundPosition: "center top",
backgroundRepeat: "no-repeat",
color: "rgb(255, 255, 255)",
justifyContent: "center",
[theme.breakpoints.up("xs")]: {
flex: "0 0 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "1 1 auto",
padding: "64px",
},
}));
const Login = () => {
...
return (
<LoginContainer>
<LeftBox>
<Box sx={{ maxWidth: "900px" }}>
<Typography variant="h4"> Welcome to Backtic</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Some cool text here to represent out Backtick
</Typography>
</Box>
</LeftBox>
<RightBox>
<div>
<Box mb={"32px"}>
<Link to="/">
<img src={Logo} alt="Backtick Logo" width={140} />
</Link>
</Box>
</div>
<div>
<Stack direction="column" mb={"32px"}>
<Typography variant="h4" sx={{ color: "rgb(108, 115, 127)" }}>
Login
</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Don't have an account? <Link to="/register">Register</Link>
</Typography>
</Stack>
<form onSubmit={formik.handleSubmit}>
<Stack direction="column">
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={
formik.touched.password && Boolean(formik.errors.password)
}
helperText={formik.touched.password && formik.errors.password}
sx={{ marginTop: "24px" }}
/>
</Stack>
<Button
color="primary"
variant="contained"
fullWidth
type="submit"
size="large"
sx={{ marginTop: "24px" }}
>
Submit
</Button>
</form>
</div>
</RightBox>
</LoginContainer>
);
};
export default Login;
你可能还需要在App
组件中对Layout
和ProtectedRoute
做同样的操作。
英文:
The issue is that you are declaring React components inside other React components. Each time Login
rerenders because the state is updated, the styled components are redeclared. This has the net effect of unmounting the old "instance" and its entire sub-ReactTree, and mounting the new "instance" and sub-ReactTree. It's when the inputs are unmouted/mounted that causes them to lose focus.
Move the extraneous component declarations out of the Login
component.
/***********Styled component */
const LoginContainer = styled(Box)(({ theme }) => ({
display: "flex",
flex: "1 1 auto",
height: "100%",
width: "100%",
[theme.breakpoints.up("xs")]: {
flexDirection: "column-reverse",
},
[theme.breakpoints.up("md")]: {
flexDirection: "row",
},
}));
const RightBox = styled(Box)(({ theme }) => ({
backgroundColor: "rgb(14, 8, 39)",
display: "flex",
flexDirection: "column",
maxWidth: "100%",
[theme.breakpoints.up("xs")]: {
flex: "1 1 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "0 0 auto",
justifyContent: "center",
padding: "64px",
width: "600px",
},
}));
const LeftBox = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: "center",
backgroundColor: "rgb(28, 37, 54)",
backgroundImage: "url(/assets/gradient-bg.svg)",
backgroundPosition: "center top",
backgroundRepeat: "no-repeat",
color: "rgb(255, 255, 255)",
justifyContent: "center",
[theme.breakpoints.up("xs")]: {
flex: "0 0 auto",
padding: "32px",
},
[theme.breakpoints.up("md")]: {
flex: "1 1 auto",
padding: "64px",
},
}));
const Login = () => {
...
return (
<LoginContainer>
<LeftBox>
<Box sx={{ maxWidth: "900px" }}>
<Typography variant="h4"> Welcome to Backtic</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Some cool text here to represent out Backtick
</Typography>
</Box>
</LeftBox>
<RightBox>
<div>
<Box mb={"32px"}>
<Link to="/">
<img src={Logo} alt="Backtick Logo" width={140} />
</Link>
</Box>
</div>
<div>
<Stack direction="column" mb={"32px"}>
<Typography variant="h4" sx={{ color: "rgb(108, 115, 127)" }}>
Login
</Typography>
<Typography sx={{ color: "rgb(108, 115, 127)" }}>
Don't have an account? <Link to="/register">Register</Link>
</Typography>
</Stack>
<form onSubmit={formik.handleSubmit}>
<Stack direction="column">
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={
formik.touched.password && Boolean(formik.errors.password)
}
helperText={formik.touched.password && formik.errors.password}
sx={{ marginTop: "24px" }}
/>
</Stack>
<Button
color="primary"
variant="contained"
fullWidth
type="submit"
size="large"
sx={{ marginTop: "24px" }}
>
Submit
</Button>
</form>
</div>
</RightBox>
</LoginContainer>
);
};
export default Login;
You will likely want to also do the same for Layout
and ProtectedRoute
in the App
component.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论