I'm building a form and there are inputs binded with state. The problem is that as I type in the field "Order Name" or "Output Requirement" the input fields keep on loosing focus. I have read that this may happen if the form is returned inside a function but that's not the case for me.
I'm also sure that "LabelledIconInput" is not the problem as I'm using it inside the login page and I don't have such a problem there.
Can anyone provide any pointers why this might be happening?
Inputs having the problem (these are defined in a separate files residing in /components/):
<LabelledDropdown
label="SEASON*"
selectedValue={seasonName}
placeholder="Select a season"
options={seasons}
onSelectOptions={handleSeasonName}
></LabelledDropdown>
<LabelledTextArea
label="OUTPUT REQUIREMENT"
value={requirements}
placeholder={null}
onChange={handleRequirements}
></LabelledTextArea>
NewOrder form:
interface INewOrder {}
const NewOrder: React.FC<INewOrder> = (props) => {
const [orderName, setOrderName] = useState("");
const [seasonName, setSeasonName] = useState("");
const [categoryName, setCategoryName] = useState("");
const [requirements, setRequirements] = useState("");
const [dateValue, setDateValue] = useState(null);
const [newUserInfo, setNewUserInfo] = useState({
profileImages: [],
});
const updateUploadedFiles = (files) =>
setNewUserInfo({ ...newUserInfo, profileImages: files });
const handleOrderName = (event) => {
console.log(event);
setOrderName(event);
};
const handleSeasonName = (event) => {
console.log(event);
setSeasonName(event);
};
const handleCategoryName = (event) => {
console.log(event);
setCategoryName(event);
};
const handleDateChange = (event) => {
console.log(event);
setDateValue(event);
};
const handleRequirements = (event) => {
setRequirements(event);
};
return (
<Container>
<MyForm onSubmit={null}>
<PageTitle>New Order</PageTitle>
<ProgressIndication>
<Dots></Dots>
<Dots></Dots>
<Dots></Dots>
</ProgressIndication>
<br />
<FormBackground>
<Subheading>Tell Us a bit more...</Subheading>
<LabelledIconInput
label="ORDER NAME"
value={orderName}
placeholder="Name"
onChange={handleOrderName}
></LabelledIconInput>
<br /> <br />
<MySpan>
<LabelledDropdown
label="SEASON*"
selectedValue={seasonName}
placeholder="Select a season"
options={seasons}
onSelectOptions={handleSeasonName}
></LabelledDropdown>
<LabelledDropdown
label="SERVICE CATEGORY"
selectedValue={categoryName}
placeholder="Select a Category"
options={category}
onSelectOptions={handleCategoryName}
></LabelledDropdown>
</MySpan>
<br /> <br />
<FileUploadComponent
label="UPLOAD FILES"
multiple
></FileUploadComponent>
<LabelledTextArea
label="OUTPUT REQUIREMENT"
value={requirements}
placeholder={null}
onChange={handleRequirements}
></LabelledTextArea>
<br />
<LabelledDateInput
label="Expected Delivery Date"
placeholder="Select a delivery date"
dateValue={dateValue}
onChange={handleDateChange}
></LabelledDateInput>
<br />
<LabelledDropdown
label="3D Software"
width="100%"
selectedValue={software[0].value}
placeholder="Select a Category"
options={software}
onSelectOptions={handleSeasonName}
></LabelledDropdown>
</FormBackground>
<br />
<PrimaryButton onClick={null}>Next</PrimaryButton>
</MyForm>
</Container>
);
};
Styled components (Present inside the same file as the form):
const MyForm = styled("form")`
margin: auto;
`;
const Container = styled("div")`
display: grid;
justify-content: center;
grid-template-columns: auto auto;
`;
const MySpan = styled("span")``;
const PageTitle = styled("h2")`
color: ${colors.theme};
text-align: center;
font-weight: 600;
`;
const FormBackground = styled("div")`
background-color: white;
border-radius: 10px;
padding: 20px;
`;
const Subheading = styled("h3")`
color: ${colors.theme};
font-weight: 500;
`;
const Dots = styled("li")<IDots>`
width: 15px;
height: 15px;
text-align: center;
line-height: 2em;
border-radius: 1em;
background: ${(props) => (props.isActive ? colors.theme : "#e1e5f7")};
margin: 0 50px;
display: inline-block;
color: white;
position: relative;
&&::before {
content: "";
position: absolute;
top: 6px;
left: -100px;
width: 7em;
height: 0.2em;
background: ${(props) => (props.isActive ? colors.theme : "#e1e5f7")};
z-index: -1;
}
&&:first-child::before {
display: none;
}
`;
const ProgressIndicator = styled("div")`
text-align: center;
`;
Getting following warning in the browser when form loads:
[Error] Warning: Expected server HTML to contain a matching <div> in <div>.
in div (created by Styled(div))
in Styled(div) (at HyperlinkButton.tsx:21)
in HyperlinkButton (at Navbar.tsx:111)
in div (at Navbar.tsx:109)
in div (created by Styled(div))
in Styled(div) (at Navbar.tsx:108)
in div (created by Styled(div))
in Styled(div) (at Navbar.tsx:99)
in div (at Navbar.tsx:98)
in Navbar (at ProtectedPageWrapper.tsx:44)
in div (created by Styled(div))
in Styled(div) (at ProtectedPageWrapper.tsx:43)
in ProtectedPageWrapper (at new-order.tsx:48)
in Index (created by withI18nextTranslation(Index))
in withI18nextTranslation(Index) (at _app.tsx:36)
in MsalProvider (at _app.tsx:35)
in CookiesProvider (at _app.tsx:34)
in ErrorBoundary (at _app.tsx:33)
in MyApp (created by withI18nextSSR(MyApp))
in withI18nextSSR(MyApp) (created by AppWithTranslation)
in NextStaticProvider (created by withI18nextTranslation(NextStaticProvider))
in withI18nextTranslation(NextStaticProvider) (created by AppWithTranslation)
in I18nextProvider (created by AppWithTranslation)
in AppWithTranslation (created by withRouter(AppWithTranslation))
in withRouter(AppWithTranslation) (at withRedux.tsx:12)
in Provider (at withRedux.tsx:11)
in withRedux(withRouter(AppWithTranslation))
in ErrorBoundary (created by ReactDevOverlay)
in ReactDevOverlay (created by Container)
in Container (created by AppContainer)
in AppContainer
in Root
(anonymous function) (next-dev.js:60)
printWarning (react-dom.development.js:88)
error (react-dom.development.js:60)
warnForInsertedHydratedElement (react-dom.development.js:6603)
didNotFindHydratableInstance (react-dom.development.js:7803)
insertNonHydratedInstance (react-dom.development.js:16504)
tryToClaimNextHydratableInstance (react-dom.development.js:16575)
updateHostComponent (react-dom.development.js:17269)
beginWork$1 (react-dom.development.js:23179)
performUnitOfWork (react-dom.development.js:22154)
workLoopSync (react-dom.development.js:22130)
performSyncWorkOnRoot (react-dom.development.js:21756)
scheduleUpdateOnFiber (react-dom.development.js:21188)
updateContainer (react-dom.development.js:24373)
(anonymous function) (react-dom.development.js:24758)
unbatchedUpdates (react-dom.development.js:21903)
legacyRenderSubtreeIntoContainer (react-dom.development.js:24757)
renderReactElement (index.js:742)
doRender (index.js:904)
tryCatch (runtime.js:45)
invoke (runtime.js:274)
asyncGeneratorStep (index.js:189)
_next (index.js:207)
(anonymous function) (index.js:212)
Promise
(anonymous function) (index.js:204)
_callee$ (index.js:588)
tryCatch (runtime.js:45)
invoke (runtime.js:274)
asyncGeneratorStep (index.js:189)
_next (index.js:207)
promiseReactionJob
Complete code:
import styled from "@emotion/styled";
import React, { useState } from "react";
import PrimaryButton from "../buttons/PrimaryButton";
import LabelledDateInput from "../inputs/LabelledDateInput";
import LabelledDropdown from "../inputs/LabelledDropdown";
import LabelledIconInput from "../inputs/LabelledIconInput";
import { colors } from "../../utilities/colors";
import LabelledTextArea from "../inputs/LabelledTextArea";
import FileUploadComponent from "../inputs/FileUploadComponent";
interface INewOrder {}
interface IDots {
isActive?: boolean;
}
const NewOrder: React.FC<INewOrder> = (props) => {
const MyForm = styled("form")`
margin: auto;
`;
const Container = styled("div")`
display: grid;
justify-content: center;
grid-template-columns: auto auto;
`;
const MySpan = styled("span")``;
const PageTitle = styled("h2")`
color: ${colors.theme};
text-align: center;
font-weight: 600;
`;
const FormBackground = styled("div")`
background-color: white;
border-radius: 10px;
padding: 20px;
`;
const Subheading = styled("h3")`
color: ${colors.theme};
font-weight: 500;
`;
const Dots = styled("li")<IDots>`
width: 15px;
height: 15px;
text-align: center;
line-height: 2em;
border-radius: 1em;
background: ${(props) => (props.isActive ? colors.theme : "#e1e5f7")};
margin: 0 50px;
display: inline-block;
color: white;
position: relative;
&&::before {
content: "";
position: absolute;
top: 6px;
left: -100px;
width: 7em;
height: 0.2em;
background: ${(props) => (props.isActive ? colors.theme : "#e1e5f7")};
z-index: -1;
}
&&:first-child::before {
display: none;
}
`;
const ProgressIndicator = styled("div")`
text-align: center;
`;
const [orderName, setOrderName] = useState("");
const [seasonName, setSeasonName] = useState("");
const [categoryName, setCategoryName] = useState("");
const [requirements, setRequirements] = useState("");
const [dateValue, setDateValue] = useState(null);
const [newUserInfo, setNewUserInfo] = useState({
profileImages: [],
});
const updateUploadedFiles = (files) =>
setNewUserInfo({ ...newUserInfo, profileImages: files });
const seasons = [
{
value: "New Season",
label: "New Season",
},
{
value: "Summer 2022",
label: "Summer 2022",
},
{
value: "Winter 2022",
label: "Winter 2022",
},
];
const category = [
{
value: "Style",
label: "Style",
},
{
value: "Trim",
label: "Trim",
},
{
value: "Fabric",
label: "Fabric",
},
{
value: "Block",
label: "Block",
},
];
const software = [
{
value: "3D Max",
label: "3D Max",
},
{
value: "Unity",
label: "Unity",
},
{
value: "Blender",
label: "Blender",
},
];
const handleOrderName = (event) => {
console.log(event);
setOrderName(event);
};
const handleSeasonName = (event) => {
console.log(event);
setSeasonName(event);
};
const handleCategoryName = (event) => {
console.log(event);
setCategoryName(event);
};
const handleDateChange = (event) => {
console.log(event);
setDateValue(event);
};
const handleRequirements = (event) => {
setRequirements(event);
};
const onFormSubmit = (event) => {
console.log("Form submitted...");
};
const onNextButtonClick = () => {
console.log("Next button clicked");
};
return (
<Container>
<MyForm onSubmit={onFormSubmit}>
<PageTitle>New Order</PageTitle>
<ProgressIndicator>
<Dots isActive={true}></Dots>
<Dots></Dots>
<Dots></Dots>
</ProgressIndicator>
<br />
<FormBackground>
<Subheading>Tell Us a bit more...</Subheading>
<LabelledIconInput
label="ORDER NAME"
value={orderName}
placeholder="Name"
onChange={handleOrderName}
></LabelledIconInput>
<br /> <br />
<MySpan>
<LabelledDropdown
label="SEASON*"
selectedValue={seasonName}
placeholder="Select a season"
options={seasons}
onSelectOptions={handleSeasonName}
></LabelledDropdown>
<LabelledDropdown
label="SERVICE CATEGORY"
selectedValue={categoryName}
placeholder="Select a Category"
options={category}
onSelectOptions={handleCategoryName}
></LabelledDropdown>
</MySpan>
<br /> <br />
<FileUploadComponent
label="UPLOAD FILES"
multiple
></FileUploadComponent>
<LabelledTextArea
label="OUTPUT REQUIREMENT"
value={requirements}
placeholder={null}
onChange={handleRequirements}
></LabelledTextArea>
<br />
<LabelledDateInput
label="Expected Delivery Date"
placeholder="Select a delivery date"
dateValue={dateValue}
onChange={handleDateChange}
></LabelledDateInput>
<br />
<LabelledDropdown
label="3D Software"
width="100%"
selectedValue={software[0].value}
placeholder="Select a Category"
options={software}
onSelectOptions={handleSeasonName}
></LabelledDropdown>
</FormBackground>
<br />
<PrimaryButton onClick={onNextButtonClick}>Next</PrimaryButton>
</MyForm>
</Container>
);
};
export default NewOrder;
CodePudding user response:
Issue
The issue is that you've declared all the styled components inside another React component. Each time the NewOrder
rerenders it redeclares the MyForm
component, which remounts it and all its form field components. Any field that had focus will be remounted and lose focus.
Solution
Define styled components outside of the render method
It is important to define your styled components outside of the render method, otherwise it will be recreated on every single render pass. Defining a styled component within the render method will thwart caching and drastically slow down rendering speed, and should be avoided.
The entire function body of a React function component is the "render method".
Declare all styled components on their own, outside of other React components.
const MyForm = styled("form")`
margin: auto;
`;
const Container = styled("div")`
...
`;
const MySpan = styled("span")``;
const PageTitle = styled("h2")`
...
`;
const FormBackground = styled("div")`
...
`;
const Subheading = styled("h3")`
...
`;
const Dots = styled("li")<IDots>`
...
`;
const ProgressIndicator = styled("div")`
text-align: center;
`;
const NewOrder: React.FC<INewOrder> = (props) => {
...
CodePudding user response:
something is triggering a re render and that's why its losing focus.
I had a similar issue inside bootstrap tabs. Although I couldn't figure it out. But the way around I did was the state variable which had re render i put it inside a useEffect and set the focus back to the input field. This is not a good approach however but atleast that is the reason.
So the below code is what I did. So on every change of successfulOrderReport I would focus the input field back.
const searchInput = useRef();
useEffect(() => {
searchInput.current.focus();
}, [successfulorderReport]);
you can try this for testing but for you I would suggest is to see what is causing the re render maybe first.