zaguiini / formik-wizard

A multi-step form component powered by formik and react-albus
MIT License
86 stars 26 forks source link

How to go to next step in react native? #22

Closed zifahm closed 4 years ago

zifahm commented 4 years ago

@zaguiini react native has no type="submit" , so how do i got to next step? after clicking the button. Unable to find any albus examples too for submitting.

zifahm commented 4 years ago

Maybe thats not it, im missing something? this is how my wizard look

<FormikWizard
      steps={steps}
      onSubmit={handleSubmit}
      render={FormWrapper}
      Form={({ children }: any) => children}
    />
zifahm commented 4 years ago

is there something i should do to make things work in React Native?

zifahm commented 4 years ago

Hmm i looked into the code and got, wizard.next from the FormWrapper prop, But this is not fun without the handleSubmit for checking with yup.

zifahm commented 4 years ago

Wrote a whole new prop for triggering handleSubmit inside Formwrapper

zifahm commented 4 years ago

Ohh I see now, I could have used the context and just use handle submit or submitform from there.

ataravati commented 4 years ago

Ohh I see now, I could have used the context and just use handle submit or submitform from there.

@zifahm Could you please share what you have done?

zifahm commented 4 years ago

@ataravati

const { submitForm } = useFormikContext<MyFormikValues>();

  return (
    <View>
      {children}
      <ButtonContainer canGoBack={canGoBack}>
        {canGoBack && (
          <Button onPress={goToPreviousStep} title="Back" color="#404040" />
        )}

        <Button
          onPress={submitForm}
          title={isLastStep ? "Submit" : "Next"}
          color="#595959"
        />
      </ButtonContainer>
    </View>
  );
ataravati commented 4 years ago

@ataravati

const { submitForm } = useFormikContext<MyFormikValues>();

  return (
    <View>
      {children}
      <ButtonContainer canGoBack={canGoBack}>
        {canGoBack && (
          <Button onPress={goToPreviousStep} title="Back" color="#404040" />
        )}

        <Button
          onPress={submitForm}
          title={isLastStep ? "Submit" : "Next"}
          color="#595959"
        />
      </ButtonContainer>
    </View>
  );

Thank you!

adrrian17 commented 4 years ago

Maybe thats not it, im missing something? this is how my wizard look

<FormikWizard
      steps={steps}
      onSubmit={handleSubmit}
      render={FormWrapper}
      Form={({ children }: any) => children}
    />

@zifahm could you show me an example of how to implement this on React Native, please? I can't fully understand how I'm supposed to use it 😞

zifahm commented 4 years ago

I actually did a few tweaks to the formike wizard @adrrian17 by removing Form prop in the formik wizard. also added triggerSubmit as a prop so I could avoid using context api take a look

import { Formik, FormikErrors, FormikProps } from "formik";
import produce from "immer";
import React from "react";
import {
  Step as AlbusStep,
  Steps as AlbusSteps,
  Wizard as AlbusWizard,
  WizardContext,
  WizardProps
} from "react-albus";
import { View } from "react-native";
import { withNextInputAutoFocusForm } from "react-native-formik";
import { Schema } from "yup";
export type FormikWizardBaseValues = any;

const Form = withNextInputAutoFocusForm(View);

export interface FormikWizardContextValue<V = any, S = any> {
  status: S;
  setStatus: React.Dispatch<React.SetStateAction<S>>;
  values: V;
  setValues: React.Dispatch<React.SetStateAction<V>>;
}

export interface FormikWizardStepType {
  id: string;
  component: React.SFC<{}>;
  validationSchema?: Schema<any>;
  validate?: (values: any) => void | object | Promise<FormikErrors<any>>;
  initialValues?: FormikWizardBaseValues;
  actionLabel?: string;
  onAction?: (
    sectionValues: FormikWizardBaseValues,
    formValues: FormikWizardBaseValues
  ) => Promise<any>;
  keepValuesOnPrevious?: boolean;
}

export interface FormikWizardWrapperProps<Values, Status = any>
  extends FormikWizardContextValue<Values, Status> {
  canGoBack: boolean;
  goToPreviousStep: () => void;
  triggerSubmit: () => void;
  currentStep: string;
  actionLabel?: string;
  isLastStep: boolean;
  steps: string[];
  wizard: WizardContext;
  children: React.ReactNode;
  isSubmitting: boolean;
}

export interface FormikWizardProps<Values, Status = any> {
  steps: FormikWizardStepType[];
  render: React.SFC<FormikWizardWrapperProps<Values, Status>>;
  onSubmit: (values: Values) => void | Promise<void>;
  formikProps?: Partial<FormikProps<Values>>;
  albusProps?: Partial<WizardProps>;
}
function getInitialValues(steps: FormikWizardStepType[]) {
  return steps.reduce<FormikWizardBaseValues>((curr, next) => {
    curr[next.id] = next.initialValues;
    return curr;
  }, {});
}

const FormikWizardContext = React.createContext<FormikWizardContextValue | null>(
  null
);

interface FormikWizardStepProps
  extends FormikWizardContextValue<FormikWizardBaseValues, any> {
  step: FormikWizardStepType;
  Form?: any;
  steps: string[];
  FormWrapper: React.SFC<FormikWizardWrapperProps<any>>;
  wizard: WizardContext;
  formikProps?: Partial<FormikProps<any>>;
  onSubmit: FormikWizardProps<any>["onSubmit"];
}

function FormikWizardStep({
  step,
  FormWrapper,
  steps,
  wizard,
  formikProps,
  onSubmit,
  setStatus,
  status,
  values,
  setValues
}: FormikWizardStepProps) {
  const info = React.useMemo(() => {
    return {
      canGoBack: steps[0] !== step.id,
      currentStep: step.id,
      isLastStep: steps[steps.length - 1] === step.id
    };
  }, [steps, step]);

  const handleSubmit = React.useCallback(
    async sectionValues => {
      setStatus(undefined);

      let status;

      try {
        if (info.isLastStep) {
          const newValues = produce(values, (draft: any) => {
            draft[info.currentStep] = sectionValues;
          });

          status = await onSubmit(newValues);
          setValues(newValues);
        } else {
          status = step.onAction
            ? await step.onAction(sectionValues, values)
            : undefined;

          setValues((values: any) => {
            return produce(values, (draft: any) => {
              draft[info.currentStep] = sectionValues;
            });
          });

          setImmediate(wizard.next);
        }
      } catch (e) {
        status = e;
      }

      setStatus(status);
    },
    [
      info.currentStep,
      info.isLastStep,
      onSubmit,
      setStatus,
      setValues,
      step,
      values,
      wizard.next
    ]
  );

  return (
    <Formik
      {...formikProps}
      enableReinitialize
      initialValues={step.initialValues}
      validationSchema={step.validationSchema}
      validate={step.validate}
      onSubmit={handleSubmit}
    >
      {props => (
        <Form>
          <FormWrapper
            {...info}
            steps={steps}
            wizard={wizard}
            actionLabel={step.actionLabel}
            isSubmitting={props.isSubmitting}
            goToPreviousStep={() => {
              setStatus(undefined);

              if (step.keepValuesOnPrevious) {
                setValues((values: any) =>
                  produce(values, (draft: any) => {
                    draft[step.id] = props.values;
                  })
                );
              }

              wizard.previous();
            }}
            status={status}
            values={values}
            setStatus={setStatus}
            setValues={setValues}
            triggerSubmit={() => props.handleSubmit()}
          >
            {React.createElement(step.component)}
          </FormWrapper>
        </Form>
      )}
    </Formik>
  );
}

export function FormikWizard<T>({
  formikProps,
  albusProps,
  onSubmit,
  steps,
  render
}: FormikWizardProps<T>) {
  const [status, setStatus] = React.useState(undefined);
  const [values, setValues] = React.useState(() => getInitialValues(steps));

  React.useEffect(() => {
    setValues(getInitialValues(steps));
    setStatus(undefined);
  }, [steps]);

  const stepIds = React.useMemo(() => steps.map(step => step.id), [steps]);

  return (
    <AlbusWizard {...albusProps}>
      <FormikWizardContext.Provider
        value={{
          status,
          setStatus,
          values,
          setValues
        }}
      >
        <AlbusSteps>
          {steps.map(step => (
            <AlbusStep
              key={step.id}
              id={step.id}
              render={wizard => (
                <FormikWizardStep
                  wizard={wizard}
                  formikProps={formikProps}
                  onSubmit={onSubmit}
                  steps={stepIds}
                  status={status}
                  values={values}
                  setValues={setValues}
                  setStatus={setStatus}
                  step={{
                    ...step,
                    initialValues: values[step.id] || {}
                  }}
                  FormWrapper={render}
                />
              )}
            />
          ))}
        </AlbusSteps>
      </FormikWizardContext.Provider>
    </AlbusWizard>
  );
}

export default FormikWizard;

export function useFormikWizard<T>() {
  return React.useContext(FormikWizardContext) as FormikWizardContextValue<T>;
}
adrrian17 commented 4 years ago

@zifahm wow, thanks a lot :D