I want to implement an edit dialog for some metadata that I get from the backend. For this, I'm using Formik. When the user changes one metadata field then an icon is shown, which indicates that the field was changed. On submit, only the updated values should be sent to the backend. I read in other posts that you should compare the current field values with the initial values provided to the Formik form. This works perfectly fine for single values, like a changed title. However, the form I need to implement has also multi-value fields like creators. I implemented a custom field for that where the user can choose/provide multiple values for one field. The values of this field are saved as an array in the Formik form. The problem is that Formik also changes the initialValue of this field to the currently saved array and therefore I'm no longer able to check if the field is updated or not. Furthermore, I save the metadata fields provided by the backend in a state value because the response contains some further information needed for further implementation. This state value also contains the current value (before update) that a metadata field has and is used as initial values for the Formik form. Strangely, the multi-field component not only overwrites the initialValue of the field of the Formik form but also the value of the field in the state which is only read and never directly updated.
My dialog for editing metadata looks as follows:
const EditMetadataEventsModal = ({ close, selectedRows, updateBulkMetadata }) => {
const { t } = useTranslation();
const [selectedEvents, setSelectedEvents] = useState(selectedRows);
const [metadataFields, setMetadataFields] = useState({});
const [fetchedValues, setFetchedValues] = useState(null);
useEffect(() => {
async function fetchData() {
let eventIds = [];
selectedEvents.forEach((event) => eventIds.push(event.id));
// metadata for chosen events is fetched from backend and saved in state
const responseMetadataFields = await fetchEditMetadata(eventIds);
let initialValues = getInitialValues(responseMetadataFields);
setFetchedValues(initialValues);
setMetadataFields(responseMetadataFields);
}
fetchData();
}, []);
const handleSubmit = (values) => {
const response = updateBulkMetadata(metadataFields, values);
close();
};
return (
<>
<div className="modal-animation modal-overlay" />
<section className="modal wizard modal-animation">
<header>
<a className="fa fa-times close-modal" onClick={() => close()} />
<h2>{t('BULK_ACTIONS.EDIT_EVENTS_METADATA.CAPTION')}</h2>
</header>
<MuiPickersUtilsProvider utils={DateFnsUtils} locale={currentLanguage.dateLocale}>
<Formik initialValues={fetchedValues} onSubmit={(values) => handleSubmit(values)}>
{(formik) => (
<>
<div className="modal-content">
<div className="modal-body">
<div className="full-col">
<div className="obj header-description">
<span>{t('EDIT.DESCRIPTION')}</span>
</div>
<div className="obj tbl-details">
<header>
<span>{t('EDIT.TABLE.CAPTION')}</span>
</header>
<div className="obj-container">
<table className="main-tbl">
<tbody>
{metadataFields.mergedMetadata.map(
(metadata, key) =>
!metadata.readOnly && (
<tr key={key} className={cn({ info: metadata.differentValues })}>
<td>
<span>{t(metadata.label)}</span>
{metadata.required && <i className="required">*</i>}
</td>
<td className="editable ng-isolated-scope">
{/* Render single value or multi value input */}
{console.log('field value')}
{console.log(fetchedValues[metadata.id])}
{metadata.type === 'mixed_text' &&
!!metadata.collection &&
metadata.collection.length !== 0 ? (
<Field name={metadata.id} fieldInfo={metadata} component={RenderMultiField} />
) : (
<Field
name={metadata.id}
metadataField={metadata}
showCheck
component={RenderField}
/>
)}
</td>
</tr>
),
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{/* Buttons for cancel and submit */}
<footer>
<button
type="submit"
onClick={() => formik.handleSubmit()}
disabled={!(formik.dirty && formik.isValid)}
className={cn('submit', {
active: formik.dirty && formik.isValid,
inactive: !(formik.dirty && formik.isValid),
})}
>
{t('UPDATE')}
</button>
<button onClick={() => close()} className="cancel">
{t('CLOSE')}
</button>
</footer>
<div className="btm-spacer" />
</>
)}
</Formik>
</MuiPickersUtilsProvider>
</section>
</>
);
};
const getInitialValues = (metadataFields) => {
// Transform metadata fields provided by backend
let initialValues = {};
metadataFields.mergedMetadata.forEach((field) => {
initialValues[field.id] = field.value;
});
return initialValues;
};
Here is RenderMultiField:
const childRef = React.createRef();
const RenderMultiField = ({ fieldInfo, field, form }) => {
// Indicator if currently edit mode is activated
const [editMode, setEditMode] = useState(false);
// Temporary storage for value user currently types in
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// Handle click outside the field and leave edit mode
const handleClickOutside = (e) => {
if (childRef.current && !childRef.current.contains(e.target)) {
setEditMode(false);
}
};
// Focus current field
if (childRef && childRef.current && editMode === true) {
childRef.current.focus();
}
// Adding event listener for detecting click outside
window.addEventListener('mousedown', handleClickOutside);
return () => {
window.removeEventListener('mousedown', handleClickOutside);
};
}, []);
// Handle change of value user currently types in
const handleChange = (e) => {
const itemValue = e.target.value;
setInputValue(itemValue);
};
const handleKeyDown = (event) => {
// Check if pressed key is Enter
if (event.keyCode === 13 && inputValue !== '') {
event.preventDefault();
// add input to formik field value if not already added
if (!field.value.find((e) => e === inputValue)) {
field.value[field.value.length] = inputValue;
form.setFieldValue(field.name, field.value);
}
// reset inputValue
setInputValue('');
}
};
// Remove item/value from inserted field values
const removeItem = (key) => {
field.value.splice(key, 1);
form.setFieldValue(field.name, field.value);
};
return (
// Render editable field for multiple values depending on type of metadata field
editMode ? (
<>
{fieldInfo.type === 'mixed_text' && !!fieldInfo.collection && (
<EditMultiSelect
collection={fieldInfo.collection}
field={field}
setEditMode={setEditMode}
inputValue={inputValue}
removeItem={removeItem}
handleChange={handleChange}
handleKeyDown={handleKeyDown}
/>
)}
</>
) : (
<ShowValue setEditMode={setEditMode} field={field} form={form} />
)
);
};
// Renders multi select
const EditMultiSelect = ({ collection, setEditMode, handleKeyDown, handleChange, inputValue, removeItem, field }) => {
const { t } = useTranslation();
return (
<>
<div ref={childRef}>
<div onBlur={() => setEditMode(false)}>
<input
type="text"
name={field.name}
value={inputValue}
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
{/* Display possible options for values as dropdown */}
<datalist id="data-list">
{collection.map((item, key) => (
<option key={key}>{item.value}</option>
))}
</datalist>
</div>
{/* Render blue label for all values already in field array */}
{field.value instanceof Array &&
field.value.length !== 0 &&
field.value.map((item, key) => (
<span className="ng-multi-value" key={key}>
{item}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))}
</div>
</>
);
};
// Shows the values of the array in non-edit mode
const ShowValue = ({ setEditMode, form: { initialValues }, field }) => {
return (
<div onClick={() => setEditMode(true)}>
{field.value instanceof Array && field.value.length !== 0 ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">{''}</span>
)}
<i className="edit fa fa-pencil-square" />
<i className={cn('saved fa fa-check', { active: initialValues[field.name] !== field.value })} />
</div>
);
};
export default RenderMultiField;
This is initialValues before a change and after:
InitialValues before change
InitialValues after change
This is the state of MetadataFields and FetchedValues before and after change:
State before change
State after change
Related
I have a list of users and I want to display in another component on the same page the user data in input fields for every user that I click on.
When no user is selected, I want the component to just render some text and a button to add a user. When the button is clicked the component renders the form with empty input fields so that we can add a new user.
I tried the following, but it's just showing the data for the first one I click on. It's not updating.
The main page:
const index = (props) => {
const [selectedUser, setSelectedUser] = useState(null);
const [state, setState] = useState("Index");
const onChange = (item) => {
setState("Edit");
setSelectedUser(item);
};
const onClick = (e, item) => {
if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
onChange(item);
} else {
console.log('prevented "onClick" on keypress');
}
};
const renderComponent = () => {
switch (state) {
case "Index":
return (
<>
<div className="btn" onClick={(e) => setState("Edit")}>
+ New Staff
</div>
<img src="/storage/illustrations/collaboration.svg" />
</>
);
case "Edit":
return (
<div>
<StaffForm profile={selectedUser} />
</div>
);
}
};
return (
<>
<div>
<div>
<h1>Staff</h1>
</div>
<div>
<div>
{profiles.map((item, index) => {
return (
<div key={index} onClick={(e) => onClick(e, item)}>
<input
type={"radio"}
name={"staff"}
checked={state === item}
onChange={(e) => onChange(item)}
/>
<span>{item.user.name}</span>
</div>
);
})}
</div>
<div>{renderComponent()}</div>
</div>
</div>
</>
);
};
The Staff Form Component:
const StaffForm = ({ profile }) => {
const { data, setData, post, processing, errors, reset } = useForm({
email: profile ? profile.user.email : "",
name: profile ? profile.user.name : "",
phone_number: profile ? profile.user.phone_number : "",
avatar: profile ? profile.user.avatar : "",
});
const [file, setFile] = useState(data.avatar);
const handleImageUpload = (e) => {
setFile(URL.createObjectURL(e.target.files[0]));
setData("avatar", e.target.files[0]);
};
const onHandleChange = (event) => {
setData(
event.target.name,
event.target.type === "checkbox"
? event.target.checked
: event.target.value
);
};
return (
<div>
<ImageUpload
name={data.name}
file={file}
handleImageUpload={handleImageUpload}
/>
<TextInput
type="text"
name="name"
value={data.name}
autoComplete="name"
isFocused={true}
onChange={onHandleChange}
placeholder={t("Name")}
required
/>
<TextInput
type="text"
name="phone_number"
value={data.phone_number}
autoComplete="phone_number"
placeholder={t("Phone Number")}
onChange={onHandleChange}
required
/>
<TextInput
type="email"
name="email"
value={data.email}
autoComplete="email"
onChange={onHandleChange}
placeholder={t("Email")}
required
/>
</div>
);
};
First of all something you should avoid is the renderComponent() call.Check here the first mistake mentioned in this video. This will most likely fix your problem but even if it doesn't the video explains why it should not be used.
Something else that caught my eye(possibly unrelated to your question but good to know) is the onChange function. When two pieces of state change together it is a potential source of problems, check out this article on when to use the useReducer hook.
Also be careful with naming React Components, they need to be capital case, this question contains appropriate answers explaining it.
(To only solve your problem stick to no.1 of this list, there are some improvements i'd do here overall for code quality and beauty, msg me for more details)
I have a list of task which inside have another task(subTask).
I use a map to list all the tasks and have a button to add more subtasks to that task.
The input button is supposed to be invisible unless the button is pressed.
The problem is that I used the useState hook to conditional render the input but whenever I click in any of the button, the inputs of all tasks open and is supposed to only open the input from that certain index.
const Task = () => {
const [openTask, setOpenTask] = useState(false);
return task.map((item, index) => {
return (
<div>
<button onClick={() => setOpenTask(!openTask)}>Add</button>
{item.name}
{openTask && <input placeholder="Add more tasks..."/>}
{item.subTask.map((item, index) => {
return (
<>
<span>{item.name}</span>
</>
);
})}
</div>
);
});
};
Try using array
const Task = () => {
const [openTask, setOpenTask] = useState([]);
const openTaskHandler = (id) => {
setOpenTask([id])
}
return task.map((item, index) => {
return (
<div>
<button onClick={() => openTaskHandler(item.id)}>Add</button>
{item.name}
{openTask.includes(item.id) && <input placeholder="Add more tasks..."/>}
{item.subTask.map((item, index) => {
return (
<>
<span>{item.name}</span>
</>
);
})}
</div>
);
});
};
That's because you are setting openTask to true for all the items. You can create object and store the specific id to true.
for eg.
const [openTask, setOpenTask] = useState({});
and then on button onClick you can set the specific id to open
setOpenTask({...openTask,[`${item.name}-${index}`]:true});
and then check against specific id
{openTask[`${item.name}-${index}`] && <input placeholder="Add more tasks..."/>}
I'm new to nextjs/react so bare with me here. In my project I have multiple select elements with multiple options in each one. When an option is selected or changed I want to pass that value to another component. I was able to pass an onClick event to another component but when I tried a similar solution I wasn't able to get it to work. So the select elements are being mapped in Component A2, but the options for those elements are also being mapped in Component A3 and I need to pass the value to Component B2. You will see in my code I tried to pass it with the "handleOnChange". I'm not very good at explaining things so here is my code snippets, I hope this makes sense:
Parent Component
export default function Post({ globalProps, page, globalPages, sidebarProps }) {
const [addFlexItem, setAddFlexItem] = useState(false)
const [addFlexItemStyles, setFlexItemStyles] = useState()
return (
<Layout globalProps={globalProps}>
<main className={styles.container}>
<FlexSidebar sidebarProps={sidebarProps} onClick={() => setAddFlexItem(true)} handleOnChange={() => setFlexItemStyles()} />
<FlexContainer addFlexItem={addFlexItem} addFlexItemStyles={addFlexItemStyles} />
</main>
</Layout>
)
}
Component A1
const FlexSidebar = ({ sidebarProps, onClick, handleOnChange }) => {
return (
<aside className={styles.left_sidebar}>
<section className={styles.wrap}>
{/* we are padding the onClick to the child component */}
{container === true && <FlexSidebarContainer sidebarProps={sidebarProps} onClick={onClick} handleOnChange={handleOnChange} />}
{items === true && <FlexSidebarItems sidebarProps={sidebarProps} />}
</section>
</aside>
)
}
Component A2
const FlexSidebarContainer = ({ sidebarProps, onClick, handleOnChange }) => {
const options = sidebarProps.options
return (
<>
<p className={styles.warning}>{sidebarProps.containerWarningText}</p>
<button type="button" className="btn" onClick={() => onClick()}>
{sidebarProps.addItemBtn}
</button>
<form className={styles.form}>
{options.map((option, index) => {
return (
<div key={index} className={styles.form_item}>
<div className={styles.form_label_wrap}>
<label>{option.title}</label>
</div>
<FlexSidebarSelect options={option.items} handleOnChange={handleOnChange} />
</div>
);
})}
</form>
</>
)
}
Component A3
const FlexSidebarSelect = ({ options, handleOnChange }) => {
return (
<div className={styles.form_item_wrap}>
<select onChange={(value) => handleOnChange(value)}>
{options.map((item, index) => {
return (
<option key={index} value={item.value}>{item.item}</option>
)
})}
</select>
</div>
)
}
Component B1
const FlexContainer = ({ addFlexItem, addFlexItemStyles }) => {
return (
<section className={styles.right_content}>
<FlexItem addFlexItem={addFlexItem} addFlexItemStyles={addFlexItemStyles} />
</section>
)
}
Component B2
const FlexItem = ({ addFlexItem, addFlexItemStyles }) => {
const [isaddFlexItem, setaddFlexItem] = useState(addFlexItem)
useEffect(() => {
setaddFlexItem(addFlexItem)
}, [addFlexItem])
return (
isaddFlexItem ?
<div className={styles.flex_item}>
<div className={styles.flex_item_wrap}>
<div className={styles.flex_item_inner}>
</div>
<button className={styles.trash}>
</button>
</div>
</div>
: "empty"
)
}
I will add that if I change the code in Component A3 to this, im able to log the value, but I cant get it to work in the parent component.
const FlexSidebarSelect = ({ options, handleOnChange }) => {
const [value, setValue] = useState("")
const handleOptionChange = (e) => {
let value = e.target.value
setValue({
value
})
}
return (
<div className={styles.form_item_wrap}>
<select onChange={handleOptionChange}>
{options.map((item, index) => {
return (
<option key={index} value={item.value}>{item.item}</option>
)
})}
</select>
</div>
)
}
i am using react.i have 2 inputs that by clicking a button dynamically ganerats.
this the code:
useEffect(() => {
const myFields = fieldsArray.map((field) => {
return (
<div key={field} className="col-12 m-auto d-flex">
<input id={field} name={`question${field}`} onChange={handleChangeFields} placeholder='سوال' className="form-control col-4 m-2" />
<input id={field} name={`answer${field}`} onChange={handleChangeFields} placeholder='جواب' className="form-control col-4 m-2" />
</div>
)
})
setFields(myFields)
}, [fieldsArray])
i need to save value of 2 inputs together in an opjects like this :
[{question : '',answer : ''}]
and the new 2 input's value are going to update the above array like this:
[{question : '',answer : ''}, {question : '',answer : ''}]
i can save each input's value separately like this :
const handleChangeFields = (e) => {
const { name, value } = e.target
setFieldsValue([...fieldsValue, { [name]: value }])
}
but i want each 2 input's value together
i need to save each 2 inputs together.
how do i do that?
This is a general example of how I think you can tie in what you're currently doing, and add in as little new code as possible. This logs the new data state when the button is clicked.
Have two states. One for collecting the updated form information (form), and another for the combined form data array (data).
Have a form with a single onChange listener (event delegation) so you can catch events from all the inputs as they bubble up the DOM. That listener calls the handleChange function which updates the form state.
Have a button with an onClick listener that calls handleClick which takes the current form state and adds it to the data state.
I would be a bit wary of storing JSX in state. You should have that map in the component return and only updating the actual field data with basic information.
One final issue - your inputs cannot have the same id. ids must be unique. I'd get rid of them altogether.
const { useEffect, useState } = React;
function Example() {
// Initialise two states. `data` is an array
// and `form` is an object
const [ data, setData ] = useState([]);
const [ form, setForm ] = useState({});
// Add the form data to the `data` array
function handleClick() {
setData([ ...data, form ]);
}
// We're using event delegation so we need to
// check what element we changed/clicked on
// - in this case the INPUT element. We can then
// update the form state with the name/value pair of that input
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Just logs the data state after a change
useEffect(() => console.log(data), [data]);
return (
<form onChange={handleChange}>
<input name="question" type="text" />
<input name="answer" type="text" />
<button
type="button"
onClick={handleClick}
>Update form
</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
this problem solved for me :
function App() {
const [results, setResults] = useState([{ question: "", answer: "" }])
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target
const list = [...results]
list[index][name] = value
setResults(list)
}
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...results]
list.splice(index, 1)
setResults(list)
}
// handle click event of the Add button
const handleAddClick = () => {
setResults([...results, { question: "", answer: "" }])
}
// handle submit to servers
const handlesubmit = () => {
// axios
console.log(results)
}
return (
<div className="App">
{results?.map((result, index) => {
return (
<div key={index}>
<input
name="question"
value={result.question}
onChange={(e) => handleInputChange(e, index)}
/>
<input
name="answer"
value={result.answer}
onChange={(e) => handleInputChange(e, index)}
/>
{results.length !== 1 && (
<button onClick={() => handleRemoveClick(index)}>
Remove
</button>
)}
{results.length - 1 === index && (
<button onClick={handleAddClick}>add</button>
)}
</div>
)
})}
<div style={{ marginTop: 20 }}>{JSON.stringify(results)}</div>
<button onClick={handlesubmit}>submit</button>
</div>
)
}
export default App
For some reason when I submit a form I can't get the value of the checked boxes I don't mean the check value but the value attribute of the HTML element itself.
This is my element in the parent component :
function parent(props){
const [filterCriteria, setFilterCriteria] = useState([]);
const [companies, setCompanies] = useState([]);
const [categories, setCategories] = useState([]);
useEffect(() => {
axios
.get("http://localhost:4000/api/companies")
.then((res) => {
companiesData = res.data.companies;
setCompanies([...new Set(companiesData.map((item) => item.company))]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
useEffect(() => {
axios
.get("http://localhost:4000/api/categories")
.then((res) => {
categoriesData = res.data.categories;
setCategories([
...new Set(categoriesData.map((item) => item.category)),
]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
const onFilteringHandler = (e) => {
e.preventDefault();
//fun fact the e.target is looped useing "for of" not "for in" although it's an
object
for (const element of e.target) {
if (element.type === "checkbox" &&
element.checked === true &&
companies.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
if (element.type === "checkbox" &&
element.checked === true &&
categories.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
}
};
return (
//this element is in the medal of tother elements but for simplification i
removed them
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
)
}
This is my element in the child "FilterBox" component :
function FilterBox(props) {
// this is to map the categories into check boxes with values, keys and label
let categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={category} />
</label>
);
});
// this is to map the categories into check boxes with values, keys and label
let companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
Nowhere is the issue, I can't get the value in this line:
setFilterCriteria([...filterCriteria, element.value]);
Although the element.value exist on the e.target?
How to do that?
FilterBox Component
function FilterBox(props) {
const categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
<input type="checkbox" value={category} name={category} />
</label>
);
});
const companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
<input type="checkbox" value={company} name={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
App Component:
function App() {
const [companies] = React.useState(["companyOne", "companyTwo"]);
const [categories] = React.useState(["categoryOne", "categoryTwo"]);
const onFilteringHandler = (e) => {
e.preventDefault();
const filterCriteria = { companies: [], categories: [] };
for (const element of e.target) {
if (element.type === "checkbox" && element.checked) {
if (companies.includes(element.value)) {
filterCriteria.companies.push(element.name);
}
if (categories.includes(element.value)) {
filterCriteria.categories.push(element.name);
}
}
}
console.log(filterCriteria);
};
return (
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
);
}
I took the liberty to disconnect the initial API loading of the companies & categories react state arrays and hardcoded them for simplicity. The below code should be enough to send meaningful data to be able to filter out on the backend.
I took the approach with a non-controlled form state, as your code seemed to favour this kind of approach. You can try and keep the filter values in the react state too but I believe this complicates the solution a bit (especially that state update is async) and it seems that you only need to collect the current filters state value on a button press. If however, you need it for some other purposes, then a controlled form state would most likely be needed.