question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Form Reducer / Middleware

See original GitHub issue

Feature

Allow users to hook into and reduce state changes.

Current Behavior

N/A

Desired Behavior


const MY_CUSTOM_FORMIK_ACTION = 'MY_CUSTOM_FORMIK_ACTION' 

<Formik 
  reducer={(prevState, nextState, action) =>{
      if (action.type === FormikActions.SET_VALUES) {
        // do something
         return nextState 
      }
      
     if (action.type === MY_CUSTOM_FORMIK_ACTION) {
        // since this action does not exist inside of formik's internal reducer,
        // nextState === prevState. 
        //  
         return {...prevState, warning: action.payload }
      }

      return prevState;
   })

  render=(({ dispatch, warning }) => 
    <Form>

      <button onClick={() => dispatch({ type: MY_CUSTOM_FORMIK_ACTION, payload: 'covfefe' }>
         Set a warning
      </button>
      {warning && warning}
       [/** .. */}
    </Form>
   }/>
 />

This would run on every state update. This approach would allow user’s to add custom reducers and middleware on top of formik. For example, here’s some pseudo code of a logger and quick and dirty formik-persist feature:


const logger = (prevState, nextState, action) => {
   console.log(`--- FORMIK ${action.type} ---`)
   console.log('previous form state', prevState)
   console.log('action', action.type, action)
   console.log('next form state', nextState)
   console.log(`-----------------------------`)
   return nextState;
}

const persist = (prevState, nextState) => {
   // would probably debounce this. but this is core idea.
   window.localStorage.setItem('state', JSON.stringify(nextState));
   return nextState;
}

// Usage
<Formik 
  onSubmit={...}
  initialValues={JSON.parse(window.localStorage.getItem('state')) || { ... }}
  reducer={compose(persist, logger)}
  render={props => ... }
 />

The reason this is better than just using regular react state, is that the reducers should be highly reusable across your forms. Furthermore, if you don’t like a specific feature of Formik, you can now safely extend or modify Formik.

Use cases:

  • Transforming values.
  • Adding state (like warnings, submit counts, soft submits, etc.)
  • Testing!

Suggested Solutions

We would need to rewrite Formik’s internals to use a “reducer” pattern. This is quite simple actually.

export const FormikActions = {
  SET_VALUES: 'SET_VALUES',
  SET_ERRORS: 'SET_ERRORS',
  SET_TOUCHED: 'SET_TOUCHED',
}

export class Formik extends React.Component {

  static defaultProps = {
    reducer: (_prevState, nextState) => nextState // default
  }

  reducer = (state, props, action) => {
    switch (action.type) {
      case FormikActions.SET_VALUES:
        return { ...state,
          values: action.payload
        }
      case FormikActions.SET_ERRORS:
        return { ...state,
          errors: action.payload
        }
      case FormikActions.SET_TOUCHED:
        return { ...state,
          touched: action.payload
        }
      default:
        return state
    }
  }

  dispatch = (action) => {
    this.setState((prevState, props) => this.props.reducer(prevState, this.reducer(prevState, props, action)))
  }

  setValues = (payload) => this.dispatch({
    type: FormikAction.SET_VALUES,
    payload
  })

  setErrors = (payload) => this.dispatch({
    type: FormikAction.SET_ERRORS,
    payload
  })

  setTouched = (payload) => this.dispatch({
    type: FormikAction.SET_TOUCHED,
    payload
  })
  
  // and on and on

  render() {
    // ... same same

  }
}

Discussion

  • Async. We would need a redux-thunk thing?
  • Naming conventions
  • Argument order
  • Action shape
  • binding to dispatch / action creators.

Regarding Action shape, my thoughts…

Sync Actions:

{
    type: string;
    payload: any;
 }

Async actions (inspired by redux-pack)

 {
    type: string;
    promise: Promise<any>,
    meta:  {
      onStart: (state: FormikState<Values>, props: Props) => void;
      onSuccess: (state: FormikState<Values>, props: Props) => void;
      onFailure: (state: FormikState<Values>, props: Props) => void;
      finally: (state: FormikState<Values>, props: Props) => void;
      always: (state: FormikState<Values>, props: Props) => void;
    }
 }

Why is this better than status and setStatus or the new (yet undocumented) setFormikState?

setFormikState is a code smell IMHO, which is why we didn’t ship it for so long. A reducer/middleware pattern lends keeps Formik uncontrolled from the user’s perspective, and yet allows Formik’s internals to be augmented.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:27
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

11reactions
jeffbskicommented, Jul 25, 2018

Where’s the back and forth discussion at? I don’t see examples being solved in this PR that would explain alternative ways of getting notified for change.

Here’s a concrete use case: See https://github.com/jaredpalmer/formik/issues/485#issuecomment-407590206

It certainly doesn’t seem unreasonable to have an onChange and to want to be able to react to that like with a filter form as mentioned in this comment linked above.

There were many issues that were depending on this being implemented. If it’s not going to be exposed to the public then we should re-open one of the others and at least have some simple onChange callback which provides all the current values.

If nothing else, we should at least have something like react-final-form where you can easily attach an onChange to an input that gets called after the supplied handleChange.

9reactions
jaredpalmercommented, Apr 11, 2018

Will hopefully have time this weekend to publish the work I’ve done. It’s a fully overhaul of the internals as you can imagine.

Read more comments on GitHub >

github_iconTop Results From Across the Web

redux-form Field pass class as component - Stack Overflow
I put the Rfc in the same folder with the MyForm. The complete codes are updated. Thanks! – wafflepie. Apr 3, 2018 at...
Read more >
Custom Form Validation in React with Redux Middleware
Actions are dispatched to the store, the store uses a reducer to change state and broadcast the new state to our components, and...
Read more >
Handling user authentication with Redux Toolkit
In this article, we'll learn how to use Redux Toolkit (RTK) to create a frontend authentication workflow in React.
Read more >
Idiomatic Redux: Redux Toolkit 1.0 - Mark's Dev Blog
Simplify common Redux tasks and code; Use opinionated defaults guiding towards "best practices"; Provide solutions to reduce or eliminate the " ...
Read more >
PHYSICAL RESIDUAL FUNCTIONAL CAPACITY ...
FORM APPR0VED. OMB NO. 0960-0431. RFC ASSESSMENT IS FOR: PHYSICAL RESIDUAL FUNCTIONAL CAPACITY ASSESSMENT. CLAIMANT: NUMBERHOLDER (IF CDB CLAIM):.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found