npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-nerd

v1.2.3

Published

🤓 Performant, minimal React form library

Downloads

14

Readme

React nerd logo

Performant, minimal React form library

  • Controlled fields like Formik, but fast (every field has it's own context)
  • Excellent Typescript support

EXAMPLE AND DEMO

STRESS TEST WITH 500 FIELDS

npm

Installation

npm install react-nerd
yarn add react-nerd

Usage

import { createForm } from 'react-nerd';

// create the form

const { FormProvider, useField, useFormActions } = createForm({
  initialValues: {
    firstName: '',
    lastName: '',
  },
});

// create field components

const FirstName = () => {
  const { value, setValue } = useField({ name: 'firstName' });

  return (
    <input type="text" value={value} onChange={e => setValue(e.target.value)} />
  );
};

const LastName = () => {
  const { value, setValue } = useField({ name: 'lastName' });

  return (
    <input type="text" value={value} onChange={e => setValue(e.target.value)} />
  );
};

// create form component

const Form = () => {
  const { handleSubmit } = useFormActions();

  return (
    <form onSubmit={handleSubmit}>
      <FirstName />
      <LastName />
    </form>
  );
};

// wrap Form with FormProvider so it can call useFormActions

const FormPage = () => {
  const onSubmit = values => {
    console.log(values);
  };

  return (
    <FormProvider onSubmit={onSubmit}>
      <Form />
    </FormProvider>
  );
};

// And that's it ! 🥳

Validation

This library doesn't support validation schemas out of the box, and it only has field level validation via the validate function. We believe that is enough.

You should return either a boolean or some object whose leaf nodes are boolean from the validate function.

When this library calculates isValid it checks whether any of the leaf nodes in the validation state are false, and if so, the form is not valid.

So, in general, you shouldn't return error message strings from the validate function like you would do in Formik, but rather calculate the error message from the validation result in the render of the component like in the example below.

You can use the nifty library fun-validation

import { isStringLongerThan } from 'fun-validation';

const FirstName = () => {
  const { value, setValue, validation } = useField({
    name: 'firstName',
    // whatever you return from the validate function, you will get back from useField
    validate: value => isStringLongerThan(0)(value),
  });

  const error = validation === false ? 'Required' : undefined;

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      {error && <div style={{ color: 'red' }}>{error}</div>}
    </div>
  );
};
useField: ({name: string, validate: (value: any) => any}) => {value, validation, setValue, setBlur}

Imperative actions

Just use the useFormActions hook and get the functions. This hook never causes a render, because the functions are memoized.

const ResetButton = () => {
  const { resetForm } = useFormActions();

  return <button onClick={resetForm}>Reset</button>;
};

const TriggerValidationButton = () => {
  const { validateField } = useFormActions();

  return (
    <button onClick={() => validateField({ name: 'firstName' })}>
      Trigger validation
    </button>
  );
};

List of all imperative actions

setFieldValue: ({name: string, value?: any, shouldValidate?: boolean}) => void

setFieldValidation: ({name: string, validation: any}) => void

setBlur: ({ name: string }) => void;

validateField: ({name: string}) => Promise<FieldValidation>

validateAllFields: () => Promise<Validation>

submitForm: () => Promise<any>

resetForm: (newState?: Partial<NewState>) => void

type NewState<Values> = {
  values: Partial<Values>;
  validation: Partial<Validation>;
  isSubmitting: boolean;
  submitCount: number;
};

handleSubmit: (e?: any) => void

handleReset: (e?: any) => void

setValues: ({values: Partial<Values>, shouldValidate?: boolean}) => void

setValidation: ({validation: Partial<Validation>}) => void

Accessing field state

If you need the state of another field (value, validation), use the useFieldState hook returned from createForm

const FirstName = () => {
  const { value, setValue } = useField({ name: 'firstName' });
  const { value: lastNameValue } = useFieldState('lastName');

  useEffect(() => {
    // do something with last name value
  }, [lastNameValue]);

  return (
    <input type="text" value={value} onChange={e => setValue(e.target.value)} />
  );
};
useFieldState: ({ name: string }) => FieldState;

type FieldState = {
  value: any;
  validation: any;
};

Accessing form state

There is a separate hook for reading each form state returned by createForm. This is done so you can choose which form state you want to subscribe to.

const {
  useValues,
  useValidation,
  useIsDirty,
  useIsValid,
  useIsSubmitting,
  useSubmitCount,
} = createForm({
  initialValues: {
    firstName: '',
    lastName: '',
  },
});

const FormState = () => {
  const values = useValues();
  const validation = useValidation();
  const isDirty = useIsDirty();
  const isValid = useIsValid();
  const isSubmitting = useIsSubmitting();
  const submitCount = useSubmitCount();

  // do something with form state

  return <div>{/*
      or render something from form state
    */}</div>;
};

List of hooks for accessing form state

useValues: () => Values;

useValidation: () => Validation;

useIsDirty: () => boolean;

useIsValid: () => boolean;

useIsSubmitting: () => boolean;

useSubmitCount: () => number;

Nested fields

This is how you would implement an array of text fields

import { createForm, append, remove } from 'react-nerd';

const { useField } = createForm({
  initialValues: {
    users: ['Frank', 'James'],
  },
});

// the field array component
const Users = () => {
  const { value: users, setValue } = useField({ name: 'users' });

  const addUser = () => {
    setValue(append(users, 'New user'));
  };

  return (
    <div>
      <ul>
        {users.map((user, index) => (
          <UserItem key={index} user={user} index={index} />
        ))}
      </ul>
      <button onClick={addUser}>Add user</button>
    </div>
  );
};

// you should always memoize an item component in a dynamic list
const UserItem = React.memo(({ user, index }) => {
  const { setFieldValue } = useFormActions();

  // notice how the UserItem component nowhere depends on the
  // value of the whole array, and that's why the memoization will work
  const updateUser = (user: string) => {
    setFieldValue({
      name: "users",
      setValue: (users) => replace(users, index, user)
    })
  }

  const removeUser = () => {
    setFieldValue({
      name: 'users',
      setValue: users => remove(users, index),
    });
  };

  return (
    <li>
      <input type="text" value={user} onChange={e => updateUser(e.target.value)}>
      <button onClick={removeUser}>Remove user</button>
    </li>
  );
});

List of exported helpers for field arrays

prepend: <E>(array: E[], newElement: E) => E[]

append: <E>(array: E[], newElement: any) => any[]

remove: <E>(array: E[], index: number) => E[]

replace: <E>(array: E[], index: number, newElement: E) => E[]

insert: <E>(array: E[], index: number, newElement: E) => E[]

swap: <E>(array: E[], indexA: number, indexB: number) => E[]

move: <E>(array: E[], from: number, to: number) => E[]

Usage with Typescript

// All you have to do is when creating the form, define the type of initial values

type MyFormState = {
  firstName: string;
  lastName: string;
};

const { FormProvider, useField, useFormActions } = createForm({
  initialValues: {
    firstName: '',
    lastName: '',
  } as MyFormState,
});

// And that's it, you have intellisense for everything: field keys, types of field values, type of validation result...etc