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

@panenco/formik-wizard-form

v0.0.3

Published

Formik wizard form wrapper

Downloads

56

Readme

Wizard Form 🧙‍♂️

This package was created to make step-by-step forms easier... I hope so

Installation

To install formik-wizard-form please follow next steps:

  1. Check formik@^2.0.0 to be also installed as a peerDependency
  2. Run npm i @panenco/formik-wizard-form

Usage

The main phylosopy of this wizard form is 'divide and conquer'. Therefore, step containers contain all needed properties for working. Idea is to keep all fields and step related data in the same place like: Title, Validation, onSubmit, NoReturn.

WizardProps

Here is the interface that represents WizardForm props. They are obviously extend FormikConfig.

interface WizardStepContainer<Values> {
  onSubmit?: (values?: Values, formikHelpers?: FormikHelpers<Values>) => void | Promise<any>;
  NoReturn?: boolean;
  Title?: React.ReactNode;
  Validation?: any | (() => any);
}

interface WizardProps<Values> extends FormikConfig<Values> {
  steps: (React.ComponentType<any> & WizardStepContainer<Values>)[];
  children?: (
    current: {
      step: React.ReactNode,
    } & WizardStepMeta,
  ) => React.ReactNode | React.ReactNode;
  component?: React.ComponentType<any>;
  navigator?: INavigatorConstructor;
  initialStep?: number;
}

About INavigator you can read in navigator README.md

Create your step containers

There are couple different steps containers definitions examples below. For steps that send data independtly you can define class component method onSubmit or in functional components use hook useImperativeHandler in combination with forwardRef for exposing methods.

import React from 'react';
import { Form } from 'formik';
import Field from '@panenco/formik-form-field';
import { PrimaryButton, TextInput, SelectInput } from '@panenco/pui';

export const PhilosophersStone = () => {
  return (
    <Form>
      <Field name="firstName" placeholder="First name" component={TextInput} />
      <Field name="lastName" placeholder="Last name" component={TextInput} />
      <div>
        <PrimaryButton type="submit">Next</PrimaryButton>
      </div>
    </Form>
  );
};

const faculties = [
  { label: 'Gryffindor', value: 'Gryffindor' },
  { label: 'Hufflepuff', value: 'Hufflepuff' },
  { label: 'Ravenclaw', value: 'Ravenclaw' },
  { label: 'Slytherin', value: 'Slytherin' },
];

export const TheChamberOfSecrets = React.forwardRef((props, ref) => {
  React.useImperativeHandle(ref, () => ({
    onSubmit: async (values, formikBag) => {
      await sayToHat(values, formikBag);
    },
  }));

  return (
    <Form>
      <Field name="faculty" placeholder="Faculty" component={SelectInput} options={faculties} />
      <div>
        <PrimaryButton type="submit">Make choice</PrimaryButton>
      </div>
    </Form>
  );
});

TheChamberOfSecrets.Title = 'The Chamber of Secrets';
TheChamberOfSecrets.Validation = Yup.object().required('You need to choose faculty');
TheChamberOfSecrets.NoReturn = true;

export class ThePrisonerOfAzkaban extends React.Component {
  static Title = 'The Prisoner of Azkaban';

  onSubmit = (...args) => {
    console.log(...args);
  };

  render() {
    return (
      <Form>
        <Field name="email" placeholder="Email" component={TextInput} />
        <div>
          <PrimaryButton type="submit">Submit</PrimaryButton>
        </div>
      </Form>
    );
  }
}

Init WizardForm

WizardForm is a wrapper around Formik, so you need to pass props that are used to init formik form. For instance, initialValues is still required, but onSubmit- not (in case you have defined one as step container method, otherwise it will just do on step validation and do next()).

WizardBag

There are couple props passed in MagicalContext and to all steps as props

export interface WizardStepMeta {
  stepIndex: number;
  title: React.ReactNode;
  noReturn: boolean;
  touched: boolean;
}

export interface MagicalContext {
  currentStepIndex: number;
  stepsMeta: Array<WizardStepMeta>;
  next: () => void;
  back: () => void;
  toStep: (step: number) => void;
  toFirstStep: () => void;
  toLastStep: () => void;
  setWizardState: (state: any | Function) => void;
  readonly wizardState: any;
}

Rendering

There are couple ways to render current step.

1. Do nothing will just render current step container and pass MagicalBag + FormikBag to it 🙃

import React from 'react';
import { WizardForm } from '@panenco/formik-wizard-form';

import * as HarryPotterAnd from './steps';

const App = () => {
  return (
    <WizardForm
      initialValues={{
        firstName: '',
        lastName: '',
        faculty: null,
        email: '',
      }}
      steps={[
        HarryPotterAnd.PhilosophersStone,
        HarryPotterAnd.TheChamberOfSecrets,
        HarryPotterAnd.ThePrisonerOfAzkaban,
      ]}
    />
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

2. Use function in children prop that receives 'ready to render' argument.

children: (current: { step: React.ReactNode } & WizardStepMeta) => React.ReactNode

import React from 'react';
import { WizardForm } from '@panenco/formik-wizard-form';

import * as HarryPotterAnd from './steps';

const App = () => {
  return (
    <WizardForm
      initialValues={{
        firstName: '',
        lastName: '',
        faculty: null,
        email: '',
      }}
      steps={[
        HarryPotterAnd.PhilosophersStone,
        HarryPotterAnd.TheChamberOfSecrets,
        HarryPotterAnd.ThePrisonerOfAzkaban,
      ]}
    >
      {({ step }) => (
        <div>
          <h1>My Wizard Form</h1>
          <div>{step}</div>
        </div>
      )}
    </WizardForm>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

3. Use component prop with same signature.

import React from 'react';
import { WizardForm, useWizardContext } from '@panenco/formik-wizard-form';
import { SecondaryButton } from '@panenco/pui';

import * as HarryPotterAnd from './steps';

const Layout = ({ step }) => {
  const { toFirstStep } = useWizardContext();

  const handleClick = () => toFirstStep();

  return (
    <div>
      <h1>My Wizard Form</h1>
      <div>{step}</div>
      <div>
        <SecondaryButton type="button" onClick={handleClick}>
          Go to start
        </SecondaryButton>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <WizardForm
      initialValues={{
        firstName: '',
        lastName: '',
        faculty: null,
        email: '',
      }}
      steps={[
        HarryPotterAnd.PhilosophersStone,
        HarryPotterAnd.TheChamberOfSecrets,
        HarryPotterAnd.ThePrisonerOfAzkaban,
      ]}
      component={Layout}
    />
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

Usage with connect from react-redux

When you are connecting your redux store to one of the step containers, then form's ref won't be forwarded by connect HOC. To make it work again you need to add { forwardRef: true } option as the 4-th argument of connect.

connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(StepContainer);

Steps Track

If you need to do a steps 'trackline' this can be customly done using Wizard's MagicalContext.

import React from 'react';
import { WizardForm, useWizardContext } from '@panenco/formik-wizard-form';

import * as HarryPotterAnd from './steps';

const WizardTrack = () => {
  const { currentStepIndex, stepsMeta, toStep } = useWizardContext();

  const handleStepClick = step => () => toStep(step);

  return (
    <div style={{ display: 'flex' }}>
      {stepsMeta.map((step, index) => {
        return (
          <div style={{ display: 'flex', alignItems: 'center' }}>
            {Boolean(index) && <div style={{ color: index <= currentStepIndex ? 'black' : 'grey' }}> - </div>}
            <button
              type="button"
              onClick={handleStepClick(index)}
              style={{ color: index <= currentStepIndex ? 'black' : 'grey' }}
            >
              [{step.title || `Step ${index}`}]
            </button>
          </div>
        );
      })}
    </div>
  );
};

const App = () => {
  return (
    <WizardForm
      initialValues={{
        firstName: '',
        lastName: '',
        faculty: null,
        email: '',
      }}
      steps={[
        HarryPotterAnd.PhilosophersStone,
        HarryPotterAnd.TheChamberOfSecrets,
        HarryPotterAnd.ThePrisonerOfAzkaban,
      ]}
    >
      {({ step }) => (
        <div>
          <h1>My Wizard Form</h1>
          <WizardTrack />
          <div>{step}</div>
        </div>
      )}
    </WizardForm>
  );
};

To do List:

  • React Native (with React-Navigation) usage
  • Validation on 'fast travel' with toStep
  • Fiels error to step return
  • Add isValid to stepsMeta TBD