In all examples if write something in input, than clear it, the field name will be dropped from state. But is it possibly maybe with some additional prop like notRemoveWhenEmpty in Field, the value will be null, or empty or anything else.
This would be nice to have.
Some times a form needs an empty string to be stored in the DB, and undefined != "".
@alex-shatalov
UPDATE: Actually looking at the source code, you can override the default with the parse function doing parse={value=>value}, that way and empty string can be used
The fact that parse, by default, isn't an identity function (e.g., value => (value)) is not what I would expect. @erikras, thoughts?
I can speak to my use case (maybe it applies to others-- I am migrating from redux-form, but I don't want to sound like a special snowflake, either). I have an existing record I am editing via HTTP PATCH. The field in question cannot be null -- this is enforced at the database level. If a user clears the field, it will be null, and thus its key will be set to undefined in the onSubmit function. (Analogously, I believe it would have been an empty string in redux-form).
By the time the object makes it to my ORM and ultimately becomes a SQL statement, the deleted property isn't included in the UPDATE sql statement. So the HTTP PATCH request successfully executes. The user then assumes the request is valid, but if they refresh the page, they would see the value they attempted to delete is still there. The fact that the user had entered an empty string in the text input was valuable -- I used that to throw an error.
Again, this might be my personal super snowflake use case, but I'm curious if it's something others have encountered. Maybe this is more of an issue with my ORM neglecting to attempt passing the undefined value.
In the meantime, I've wrote a simple adapter for the Field component, and my application code imports this adapter component in place of the official one:
import {Field} from 'react-final-form';
import React from 'react';
const identity = value => (value);
/* This wraps react-final-form's <Field/> component.
* The identity function ensures form values never get set to null,
* but rather, empty strings.
*
* See https://github.com/final-form/react-final-form/issues/130
*/
export default props => <Field parse={identity} {...props}/>;
@petermikitsh Thanks for sharing! I'm gonna go with the same fix 馃憣.
The reasoning for this decision is somewhat explained here.
Your notRemoveWhenEmpty proposal sounds like a decent compromise. 馃憤
thank you for the parse tip.
I had the same issue but I wanted a fix at the form level. So my solution was to wrap my submit handler in the form like so:
handleSubmit = (values) => {
const {initialValues, onSubmit} = this.props;
// look for key-value pairs present in initialValues but not in values
const data = Object.keys(initialValues).reduce((acc, key) => {
acc[key] = typeof values[key] === "undefined" ? '' : values[key];
return acc;
},{});
// add key-value pairs that might be in values but not in initialValues:
Object.assign(data, values);
// continue submit
onSubmit(data);
};
This way, if a field is initially set, then submitted empty, it will be part of the payload.
Hope this helps.
Note: this doesn't work for nested properties
update to @VincentCharpentier for nested data, quick recursive. thank you @VincentCharpentier and @erikras
import { merge, isObject } from 'lodash';
const checkForInitialValues = (initialValues, newValues) => {
// library issue via https://github.com/final-form/react-final-form/issues/130#issuecomment-493447888
const emptiedData = Object.keys(initialValues).reduce((acc, key) => {
if (isObject(newValues[key])) {
acc[key] = checkForInitialValues(initialValues[key], newValues[key]);
} else {
acc[key] = typeof newValues[key] === 'undefined' ? '' : newValues[key];
}
return acc;
}, {});
// need to deep merge to get new child properties
return merge(emptiedData, newValues);
};
update: included null check via @Soundvessel -- thank you.
update (9-9-19): replaced null check and typeof object check with lodash
Be aware if you use the above but usenull instead of undefined for your emptied fields, needed for our particular API, you run into the odd issue where null makes typeof newValues[key] === 'object' true so you should add a check make it something like typeof newValues[key] === 'object' && newValues[key] !== null.
The solution provided by @JackHowa is incoimplete - it does not work in some corner cases (e.g. dates, arrays).
This is where we've got so far with marmelab/react-admin:
const sanitizeEmptyValues = (initialValues: object, values: object) => {
// For every field initially provided, we check whether it value has been removed
// and set it explicitly to an empty string
if (!initialValues) return values;
const initialValuesWithEmptyFields = Object.keys(initialValues).reduce(
(acc, key) => {
if (values[key] instanceof Date || Array.isArray(values[key])) {
acc[key] = values[key];
} else if (
typeof values[key] === 'object' &&
values[key] !== null
) {
acc[key] = sanitizeEmptyValues(initialValues[key], values[key]);
} else {
acc[key] =
typeof values[key] === 'undefined' ? null : values[key];
}
return acc;
},
{}
);
// Finally, we merge back the values to not miss any which wasn't initially provided
return merge(initialValuesWithEmptyFields, values);
};
Let me say that finding this kind of corner case should not be left to us. @erikras I strongly believe this should be in the react-final-form core.
by now I've face this issue a lot and I'm still not sure what would be the best generic solution.
Sometimes I just don't want to submit empty keys, sometimes I need it some it gets erased in DB.
Indeed I ran into the same issue with date and array as you did @fzaninotto
anyway this is what I've come up with this my last message, if this can be useful to anyone.
Note that I'm using null as default empty value
function enforceSubmitClearedFormKeys(initialValues, values) {
const _initialValues = typeof initialValues === 'object' ? initialValues : {};
const _values = typeof values === 'object' ? values : {};
const keys = Array.from(
new Set(Object.keys(_initialValues || {}).concat(Object.keys(_values || {}))),
);
return keys.reduce((result, key) => {
let value;
if (_values !== null) {
value = _values[key];
}
let initValue;
if (_initialValues !== null) {
initValue = _initialValues[key];
}
const initDefined = typeof initValue !== 'undefined' && initValue !== null;
const valueDefined = typeof value !== 'undefined' && value !== null;
if (initDefined && valueDefined) {
// both defined, we may need to apply same logic on this property
if (typeof value === 'object' && !(value instanceof Date) && !(value instanceof Array)) {
value = enforceSubmitClearedFormKeys(initValue, value);
}
} else if (initDefined && !valueDefined) {
// property was erased
value = null;
}
result[key] = value;
return result;
}, {});
}
I do agree there should be some configuration available in final-form to handle that but at least we have a workaround :)
Your
notRemoveWhenEmptyproposal sounds like a decent compromise. 馃憤
What about something like submitEmptyValue that not only keeps the property in values but allows you to set what that value is when empty such as null, undefined, whatever...?
We could allow the values state to be untouched and instead have this as some kind of parse done inside the handleSubmit?
Edit: Or not, I guess that means we can't pull the same form API in our onSubmit...
This is difficult because our API will ignore undefined properties. We have to send null with optional fields to clear them.
Thanks for all the answers on the thread! I've added a variant to the solution posted by @fzaninotto as I need to handle an array of fields:
``` const initialValuesWithEmptyFields = Object.keys(initialValues).reduce(
(acc, key) => {
const value = values[key];
if (
value instanceof Date ||
(Array.isArray(value) && typeof value[0] !== 'object')
) {
acc[key] = value;
} else if (Array.isArray(value) && typeof value[0] === 'object') {
acc[key] = value.map((val, itr) => {
return includeRemovedValues(initialValues[key][itr], val);
});
} else if (typeof value === 'object' && value !== null) {
acc[key] = includeRemovedValues(initialValues[key], value);
} else {
acc[key] = typeof value === 'undefined' ? null : value;
}
return acc;
},
{}
);
```
In the meantime, I've wrote a simple adapter for the Field component, and my application code imports this adapter component in place of the official one:
import {Field} from 'react-final-form'; import React from 'react'; const identity = value => (value); /* This wraps react-final-form's <Field/> component. * The identity function ensures form values never get set to null, * but rather, empty strings. * * See https://github.com/final-form/react-final-form/issues/130 */ export default props => <Field parse={identity} {...props}/>;
This solution worked to me, but a notRemoveWhenEmpty option will be great.
Releasing support for this feature would be much appreciated. Thank you for a great library anyway 馃憤
Would be nice to see this case supported. Thanks for the good work so far!
Most helpful comment
In the meantime, I've wrote a simple adapter for the Field component, and my application code imports this adapter component in place of the official one: