-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added nested values proposal prototype (#202) #207
Conversation
Here was my version ( very simiiliar). Mine uses developit's dlv utility, which does _.get in 128 Bytes. (see below) My concern with keeping this in import React from 'react';
import PropTypes from 'prop-types';
import dlv from 'dlv';
// inspired by @developit's linkState
function setDeep(path, value, obj) {
let res = {};
let i = 0;
for (; i < path.length - 1; i++) {
res = res[path[i]] || (res[path[i]] = (!i && obj[path[i]]) || {});
}
res[path[i]] = value;
return res;
}
class Field extends React.Component {
static contextTypes = {
formik: PropTypes.object,
};
handleChange = e => this.setValue(this.props.name, e.target.value);
handleBlur = e => this.setTouch(this.props.name, true);
setValue = (key, value) => {
let path = key.split('.');
if (path.length > 1) {
this.context.formik.setFieldValue(
path[0],
setDeep(path, value, this.context.formik.values)
);
} else {
this.context.formik.setFieldValue(key, value);
}
};
setTouch = (key, value) => {
let path = key.split('.');
if (path.length > 1) {
this.context.formik.setFieldTouched(
path[0],
setDeep(path, value, this.context.formik.touched)
);
} else {
this.context.formik.setFieldTouched(key, value);
}
};
render() {
const { component = 'input', name, ...props } = this.props;
const field = {
value:
props.type === 'radio' || props.type === 'checkbox'
? props.value
: dlv(this.context.formik.values, name),
name,
onChange: this.handleChange,
onBlur: this.handleBlur,
};
const bag =
typeof component === 'string'
? field
: {
field,
form: {
...this.context.formik,
handleChange: this.handleChange,
handleBlur: this.handleBlur,
setFieldValue: this.setValue,
setFieldTouched: this.setTouch
}
};
return React.createElement(component, {
...props,
...bag,
});
}
} |
But I guess your approach is different, right? In my pull request I agree that setters logic shouldn't be part of So to sum up, would you consider adding nested objects handling in this library? If yes, what are steps required to merge it? I can think of the following:
Btw, this form library is really perfect, I think that nested object handling is the only thing missing 👍 |
package.json
Outdated
@@ -34,6 +34,7 @@ | |||
"gen-docs": "all-contributors generate && doctoc README.md" | |||
}, | |||
"dependencies": { | |||
"lodash": "4.17.4", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use dlv as described. no need to import all of lodash 😄 !
package.json
Outdated
@@ -45,6 +46,7 @@ | |||
"devDependencies": { | |||
"@types/enzyme": "2.8.4", | |||
"@types/jest": "20.0.6", | |||
"@types/lodash": "4.14.77", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove
To make things easier. Remove dlv and inline it along with setDeep: // utils.ts
/**
* @private Deeply get a value from an object via it's dot path.
* See https://github.com/developit/dlv/blob/master/index.js
*/
export function dlv(obj: any, key: string | string[], def: any, p: number = 0) {
key = (key as string).split ? (key as string).split('.') : key;
while (obj && p < key.length) {
obj = obj[key[p++]];
}
return obj === undefined ? def : obj;
}
/**
* @private Deeply set a value from in object via it's dot path.
* See https://github.com/developit/linkstate
*/
export function setDeep(obj: any, key: string | string[], value: any) {
key = (key as string).split ? (key as string).split('.') : key;
let res: any = {};
let i = 0;
for (; i < key.length - 1; i++) {
res = res[key[i]] || (res[key[i]] = (!i && obj[key[i]]) || {});
}
res[key[i]] = value;
return res;
} |
I removed lodash dependency. For deepSet I used your suggestion, but I needed to adjust it slightly as it should return the whole object, not only nested part of it. Also, path can contain brackets to set array items, so I tested nested values including array and now sth like |
Problem I see is that we are fully cloning and replacing all of values 2x on each keystroke now. I need to run this on my benchmarking suite to see perf implications. |
Regarding your comment, like I mentioned above, please see my adjusted
Regarding perf implications, I am waiting for the results |
I believe it should be fine, as we do only shallow clone by Not to mention that actually values were already cloned before this PR, for example: values: {
...prevState.values as object,
[field]: val,
} |
I inlined Btw, what is this Also, we have small problem with arrays and yup with following schema: const schema = yup.object().shape({
list: yup.array().of(yup.string().required())
}); The problem is that our implementation won't save values as array but as object like |
I troubled me too much and now our setDeep supports arrays, yup doesn't complain anymore. |
When do you merge this pull request? I need this feature) |
Looks like |
@gangstaJS What is not working, maybe your value is not correctly displayed in |
@klis87 As I see it replaced to an empty string Looks like deep elements like arrays etc. are lost reference after |
It is probably because you are pushing and removing array items. I tested arrays and it is working for me, but my array had a fixed length. I didn't try to add new items or remove them. I will play with this use case today and I will give you my results. If necessary, I will try to fix it. Another tip - if you have |
@klis87 I have hot fixed problem. See my bullshit code :)
there is 3275 line in trunspiled not minifed code I haven't any warnings with the issue, regarding yours question above |
So it was bug in your app? Or we need to add something to Formik to handle this case? |
I think this is a bug of formik. I have fixed it at the source code of formik |
Ok, so again, I will try to prepare some demo for this use case later today. |
I am not very familiar with TS so I just edit complied code, sorry |
No problem. I feel that this is not formik issue anyway, but they way you add items to your array. The demo will show us what to do |
i do it with redux case ADD_NEW_PHONE_ROW:
current = Object.assign({}, state.current);
current.phone.push({value: '', type: 'work'});
return Object.assign({}, state, {
current,
}); |
But you can't write types for something like this api, no? <Field name={`list.${i}`} /> |
dlv is the get method. I think we might need to adjust it |
@goodmind it should work. I used current version of Formik with dynamic arrays and it was fine (as long as you have an item with a given index in your list. Here you can see a demo I used: const schema = yup.object().shape({
list: yup.array().of(yup.string().required())
});
<Formik
initialValues={{ list: ['a', 'b'] }}
validationSchema={schema}
onSubmit={(values) => {
console.log(values);
}}
>
{({ values, setFieldValue }) => (
<Form>
{values.list.map((v, i) => (
<div key={i}>
<Field name={`list.${i}`} />
<button
type="button"
onClick={() => {
const n = [...values.list];
n.splice(i, 1);
setFieldValue('list', n);
// we should update touched and potentially status state here
}}
>
Remove {i}
</button>
</div>
))}
<button
type="button"
onClick={() => {
setFieldValue('list', [...values.list, '']);
}}
>
add
</button>
<button>Save</button>
</Form>
)}
</Formik> |
@goodmind to get an index shouldn't it be: |
@jaredpalmer I think |
@klis87 thanks for this PR btw. I think we should add bracket support to dlv? |
@jaredpalmer Thanks for considering and merging this :) I think we should add this. Especially I needed to add bracket support for our nested setter, because |
Btw, would you consider adding sth like this from redux form ? Because without it is is hard to add/remove items from array - you need to do it like i did in my snippet, instead we could expose alternative actions to |
@klis87 I agree and think lodash's path syntax is the goal. As for array helpers, my plan is see how far I can get with a |
@klis Let's make a new issue for that as this has been merged |
I meant TypeScript types |
Sorry, misread that. It's still a |
Hi, I've spent the past few hours trying to figure out how to use the changes from this pull request. I can't find any documentation on using nested values. Can someone provide an example of how to create a form with nested values using the changes made here? |
@zachlatta Have you installed the |
Oh wow, thanks for that. I feel like a release should be cut if this issue is marked as closed.
Few things more confusing than running into an issue with a library, googling for it, finding a PR that got merged, and then trying the example from it and having it not work.
… On Dec 21, 2017, at 03:00, Ben Smith ***@***.***> wrote:
@zachlatta Have you installed the next branch? That caught me out - you need to run yarn add ***@***.***, and then it should work out of the box. The example is also updated.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
I tried
causes Chrome tab to crash. |
Ok, this is not a Formik issue. My |
Nested numeric ids seem to be problem but it's not dev tools. It suffers without it too. I opened up redux-form/redux-form#4034 but the exact same thing happens with Formik as well. Maybe the same or similar causes exists here too (the code to set nested objects paths is probably the likely cause) . Might be a good idea to check it out... |
Thanks @jaredpalmer . This look good |
This is my quick suggestion to solve issue #202 . It is far from perfect (I don't use Typescript so I neglected types), also, I couldn't make lodash/fp working with Typescript, so I needed to use mutable
_.set
, and sadly I used it withdeepClone
. Of course this would need to be refactored, but this is just a prototype.If you like this idea, we could work on it more, add missing tests etc.
The good news is, that all tests are passing so it is backward compatible change :)