Formik 重新呈现反应状态变化
Formik re-renders on react state change
我在 formik 字段中使用自定义组件,需要在函数内部更改文本时调用一个函数(以便获得与输入相关的所有匹配提示)
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
key="assignees"
className="input-fields"
placeholder="Type names of assignees to start getting suggestions"
onChange={(e) => {
form.setFieldValue(field.name, e.target.value);
getMatching(field.value);
}}
{...props}
{...form}
// {...field}
/>
);
}}
/>
上面代码调用的匹配函数如下。它更新状态,然后该状态显示在 formik 字段中的跨度内。
const getMatching = (keyword) => {
for (const item of users) {
if (keyword !== "" && item.name.startsWith(keyword)) {
setMatching(item.name);
return;
} else {
setMatching(undefined);
}
}
};
预期行为
Formik 输入不会失去焦点并保留我在状态更改或重新渲染时添加的所有输入文本
实际行为
每当 getMatching
函数
设置状态时,输入组件失去焦点并且字段被重置
编辑-代码沙盒LINK
https://codesandbox.io/s/wizardly-merkle-w191l
几件事:
- 无需在状态中存储
matching
,可以通过查看受让人文本框值和用户列表来导出
- 受让人文本框(我称之为
searchAssignee
)和选定受让人列表(assignees
)本质上是两个不同的输入,因此必须是单独的 Formik 输入
- 将以上两个输入分开解决焦点问题
formik 的整个想法是它消除了在状态中存储输入值的需要。我删除了不必要的状态并分离了上面的输入。
const getMatching = (users, keyword) => {
if (!keyword) return null;
const match = users.find(({ name }) => name.startsWith(keyword));
if (!match) return null;
return match.name;
};
const FormContainer = ({ users }) => {
return (
<Formik
initialValues={{
summary: "",
description: "",
searchAssignee: "",
assignees: []
}}
onSubmit={async (values) => {
console.log(values);
}}
>
<Form>
<Row>
<Col>
<div className="textcolor">Summary</div>
<Field
id="summary"
name="summary"
placeholder="Add a short summary for your task"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
className="input-fields"
{...props}
// {...form}
{...field}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Description</div>
<Field
id="description"
name="description"
placeholder="Description"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<ReactQuill
theme="snow"
value={field.value}
onChange={field.onChange(field.name)}
style={{ minHeight: "5rem" }}
{...field}
{...props}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Assignees</div>
<Col className="assignee-wrapper d-flex">
<div className="d-flex">
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => (
<>
{field.value.map((item) => (
<div
className="assignee-tag d-flex"
onClick={() => {
const newAssignees = field.value.filter(
(val) => val !== item
);
form.setFieldValue(field.name, newAssignees);
}}
>
<div>{item}</div>
<GrFormClose />
</div>
))}
</>
)}
/>
</div>
<div className="d-flex col">
<Field
id="searchAssignee"
name="searchAssignee"
placeholder="Type names of assignees to start getting suggestions"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
const suggestion = getMatching(users, field.value);
return (
<>
<input
className="input-fields"
{...props}
{...form}
{...field}
/>
{suggestion && (
<span
className="text-nowrap"
onClick={() => {
if (!form.values.assignees.includes(suggestion)) {
form.setFieldValue("assignees", [
...form.values.assignees,
suggestion
]);
}
}}
>
{suggestion}
</span>
)}
</>
);
}}
/>
</div>
</Col>
</Col>
</Row>
<div className="mt-2 float-end">
<button className="btn btn-dark btn-sm" type="submit">
Add
</button>
<button className="btn btn-light btn-sm ms-2" type="reset">
Cancel
</button>
</div>
</Form>
</Formik>
);
};
const App = ({ isModalVisible, setModalVisible }) => {
const [users, setUsers] = useState([
{ name: "Hello World" },
{ name: "Foo Bar" }
]);
return (
<Modal show={true}>
<Modal.Body>
<Row>
<div>
<GrFormClose
className="float-end"
onClick={() => setModalVisible(false)}
style={{ cursor: "pointer" }}
/>
</div>
<div>
<FormContainer
users={users}
/>
</div>
</Row>
</Modal.Body>
</Modal>
);
};
我在 formik 字段中使用自定义组件,需要在函数内部更改文本时调用一个函数(以便获得与输入相关的所有匹配提示)
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
key="assignees"
className="input-fields"
placeholder="Type names of assignees to start getting suggestions"
onChange={(e) => {
form.setFieldValue(field.name, e.target.value);
getMatching(field.value);
}}
{...props}
{...form}
// {...field}
/>
);
}}
/>
上面代码调用的匹配函数如下。它更新状态,然后该状态显示在 formik 字段中的跨度内。
const getMatching = (keyword) => {
for (const item of users) {
if (keyword !== "" && item.name.startsWith(keyword)) {
setMatching(item.name);
return;
} else {
setMatching(undefined);
}
}
};
预期行为
Formik 输入不会失去焦点并保留我在状态更改或重新渲染时添加的所有输入文本
实际行为
每当 getMatching
函数
编辑-代码沙盒LINK
https://codesandbox.io/s/wizardly-merkle-w191l
几件事:
- 无需在状态中存储
matching
,可以通过查看受让人文本框值和用户列表来导出 - 受让人文本框(我称之为
searchAssignee
)和选定受让人列表(assignees
)本质上是两个不同的输入,因此必须是单独的 Formik 输入 - 将以上两个输入分开解决焦点问题
formik 的整个想法是它消除了在状态中存储输入值的需要。我删除了不必要的状态并分离了上面的输入。
const getMatching = (users, keyword) => {
if (!keyword) return null;
const match = users.find(({ name }) => name.startsWith(keyword));
if (!match) return null;
return match.name;
};
const FormContainer = ({ users }) => {
return (
<Formik
initialValues={{
summary: "",
description: "",
searchAssignee: "",
assignees: []
}}
onSubmit={async (values) => {
console.log(values);
}}
>
<Form>
<Row>
<Col>
<div className="textcolor">Summary</div>
<Field
id="summary"
name="summary"
placeholder="Add a short summary for your task"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
className="input-fields"
{...props}
// {...form}
{...field}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Description</div>
<Field
id="description"
name="description"
placeholder="Description"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<ReactQuill
theme="snow"
value={field.value}
onChange={field.onChange(field.name)}
style={{ minHeight: "5rem" }}
{...field}
{...props}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Assignees</div>
<Col className="assignee-wrapper d-flex">
<div className="d-flex">
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => (
<>
{field.value.map((item) => (
<div
className="assignee-tag d-flex"
onClick={() => {
const newAssignees = field.value.filter(
(val) => val !== item
);
form.setFieldValue(field.name, newAssignees);
}}
>
<div>{item}</div>
<GrFormClose />
</div>
))}
</>
)}
/>
</div>
<div className="d-flex col">
<Field
id="searchAssignee"
name="searchAssignee"
placeholder="Type names of assignees to start getting suggestions"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
const suggestion = getMatching(users, field.value);
return (
<>
<input
className="input-fields"
{...props}
{...form}
{...field}
/>
{suggestion && (
<span
className="text-nowrap"
onClick={() => {
if (!form.values.assignees.includes(suggestion)) {
form.setFieldValue("assignees", [
...form.values.assignees,
suggestion
]);
}
}}
>
{suggestion}
</span>
)}
</>
);
}}
/>
</div>
</Col>
</Col>
</Row>
<div className="mt-2 float-end">
<button className="btn btn-dark btn-sm" type="submit">
Add
</button>
<button className="btn btn-light btn-sm ms-2" type="reset">
Cancel
</button>
</div>
</Form>
</Formik>
);
};
const App = ({ isModalVisible, setModalVisible }) => {
const [users, setUsers] = useState([
{ name: "Hello World" },
{ name: "Foo Bar" }
]);
return (
<Modal show={true}>
<Modal.Body>
<Row>
<div>
<GrFormClose
className="float-end"
onClick={() => setModalVisible(false)}
style={{ cursor: "pointer" }}
/>
</div>
<div>
<FormContainer
users={users}
/>
</div>
</Row>
</Modal.Body>
</Modal>
);
};