Skip to content
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

Feat: ability to validate atoms , validateAtoms #5

Merged
merged 12 commits into from
Jul 8, 2022

Conversation

barelyhuman
Copy link
Collaborator

@barelyhuman barelyhuman commented Jul 2, 2022

Adds the validateAtoms implementation for overall form atom validation.

Usage example:

import { atomWithValidate, validateAtoms } from "jotai-form";

const nameAtom = atomWithValidate("foo", {
  validate: (v) => v, // individual atom validation (nothing here just for this example)
});

const ageAtom = atomWithValidate(18, {
  validate: (v) => v, // individual atom validation (nothing here just for this example)
});

const formStateAtom = validateAtoms(
  {
    name: nameAtom,
    age: ageAtom,
  },
  async (values) => {
   // validating the entire atom group for a specific form
    await Yup.object()
      .shape({
        name: Yup.string().min(3).required(),
        age: Yup.number().min(18).required(),
      })
      .validate(values);
  }
);

export const Component = () => {
  const [name, setName] = useAtom(nameAtom);
  const [age, setAge] = useAtom(ageAtom);
  const [formState] = useAtom(formStateAtom);

  return (
    <>
      <input
        placeholder="Name"
        value={name.value}
        onChange={(e) => setName(e.target.value)}
      />
      <input
        type="number"
        placeholder="Age"
        value={age.value}
        onChange={(e) => setAge(parseInt(e.target.value,10))}
      />
      <button type="submit" disabled={!formState.isValid}>Submit</button>
    </>
  );
};

@barelyhuman
Copy link
Collaborator Author

@dai-shi there is a ts-expect statement in the code that I'd like you to check. I think it's from the official jotai libraries expectation of the Getter type but would need confirmation

@dai-shi
Copy link
Member

dai-shi commented Jul 3, 2022

Types are fixed. You can't have a union type for getter. I'm not sure if it's possible to improve.

will change the API to avoid adding a helper to reduce developer work
for basic interaction
Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick review

remove the un-needed types from the API change
and also make the validator function consistent
with atomWithValidate
Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the explanation is enough, but let it go.

@barelyhuman
Copy link
Collaborator Author

barelyhuman commented Jul 4, 2022

Do not merge even if it suffices, have to write tests for async validation

@dai-shi dai-shi marked this pull request as draft July 5, 2022 09:08
Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other than that, the code looks okay.


export type Validator = <Values extends Record<string, unknown>>(
values: Values,
) => Promise<unknown>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be unknown | Promise<unknown> or void | Promise<void>. We support sync validator, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that totally depends on the loadable API but I think the sync one should be working, i'll check in the evening

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.
Though, there's an issue with using mixed typed atoms right now. The types for validator and labelled atoms needs to be fixed for that.

On it next.

will add sync validator function in types
and adds a test trying it out with an async test library
will infer the atom's internal generic
and use that to generate the type
sent to the validator
@dai-shi dai-shi marked this pull request as ready for review July 7, 2022 12:05
Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final review.

Comment on lines 35 to 43
const $getSourceAtomVals = (get: Getter) => {
const values = Object.fromEntries(
Object.entries(labeledAtoms).map(([k, v]) => {
const atomValue = get(v);
return [k, atomValue.value];
}),
);
return values as Record<Keys, inferGeneric<Vals>>;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thinks it's better to make this an atom.

Suggested change
const $getSourceAtomVals = (get: Getter) => {
const values = Object.fromEntries(
Object.entries(labeledAtoms).map(([k, v]) => {
const atomValue = get(v);
return [k, atomValue.value];
}),
);
return values as Record<Keys, inferGeneric<Vals>>;
};
const valsAtom = atom((get) => {
const values = Object.fromEntries(
Object.entries(labeledAtoms).map(([k, v]) => {
const atomValue = get(v);
return [k, atomValue.value];
}),
);
return values as Record<Keys, inferGeneric<Vals>>;
});

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many listeners on the store?
cause it's being used in 2 places as a simple helper right now

const valsAtom = atom((get: Getter) => {
    const values = Object.fromEntries(
      Object.entries(labeledAtoms).map(([k, v]) => {
        const atomValue = get(v);
        return [k, atomValue.value];
      }),
    );
    return values as Record<Keys, inferGeneric<Vals>>;
  });

  const baseAtom = atom(async (get) => {
    // extract value from each atom and assign to the given key as label
    return validator(get(valsAtom));
  });

  const normalizerAtom = atom((get) => {
    const values = get(valsAtom);
    const state = get(loadable(baseAtom));
    return {
      ...state,
      values,
    };
  });

  const derv = atom((get) => {
    const validatorState = get(normalizerAtom);
//.... other code
})

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valsAtom can be more efficient because it caches the result, than a simple helper.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, pushed

});

const derv = atom((get) => {
const validatorState = get(normalizerAtom);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const validatorState = get(normalizerAtom);
const values = get(valsAtom);
const state = get(loadable(baseAtom));
const validatorState = {
...state,
values,
};

Does this work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test's say it's working,
that's nice, 1 listener reduced.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed with other simplifications

- remove un-needed variables
- use switch instead to avoid case checks
Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Please update the PR description.

Feel free to merge it and ship it.

@barelyhuman barelyhuman changed the title validateForm Feat: ability to validate atoms , validateAtoms Jul 8, 2022
@barelyhuman barelyhuman merged commit ace612e into main Jul 8, 2022
@barelyhuman barelyhuman mentioned this pull request Jul 10, 2022
@BenBeattieHood
Copy link

Oh wow! I go away for a break, I come back and you've solved this 🤩 Love your work, thank you 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants