Skip to content

Instantly share code, notes, and snippets.

@joshsalverda
Last active May 22, 2023 09:49
Show Gist options
  • Save joshsalverda/d808d92f46a7085be062b2cbde978ae6 to your computer and use it in GitHub Desktop.
Save joshsalverda/d808d92f46a7085be062b2cbde978ae6 to your computer and use it in GitHub Desktop.
Custom useFieldArray hook for formik using immutability-helper
import {useCallback, useRef, useEffect} from 'react'
import {useField, useFormikContext} from 'formik'
import update from 'immutability-helper'
const useFieldArray = props => {
const [field, meta] = useField(props)
const fieldArray = useRef(field.value)
const {setFieldValue} = useFormikContext()
useEffect(() => {
fieldArray.current = field.value
}, [field.value])
const push = useCallback(value => {
fieldArray.current = update(fieldArray.current, {
$push: [value]
})
setFieldValue(field.name, fieldArray.current)
}, [field.name, setFieldValue])
const swap = useCallback((indexA, indexB) => {
const swapA = fieldArray.current[indexA]
const swapB = fieldArray.current[indexB]
fieldArray.current = update(fieldArray.current, {
$splice: [[indexA, 1, swapB], [indexB, 1, swapA]]
})
setFieldValue(field.name, fieldArray.current)
}, [field.name, setFieldValue])
const move = useCallback((from, to) => {
const toMove = fieldArray.current[from]
fieldArray.current = update(fieldArray.current, {
$splice: [[from, 1], [to, 0, toMove]]
})
setFieldValue(field.name, fieldArray.current)
}, [field.name, setFieldValue])
const insert = useCallback((index, value) => {
fieldArray.current = update(fieldArray.current, {
$splice: [[index, 0, value]]
})
setFieldValue(field.name, fieldArray.current)
}, [field.name, setFieldValue])
const unshift = useCallback(value => {
fieldArray.current = update(fieldArray.current, {
$unshift: [value]
})
setFieldValue(field.name, fieldArray.current)
return fieldArray.current.length
}, [field.name, setFieldValue])
const remove = useCallback(index => {
const removedItem = fieldArray.current[index]
fieldArray.current = update(fieldArray.current, {
$splice: [[index, 1]]
})
setFieldValue(field.name, fieldArray.current)
return removedItem
}, [field.name, setFieldValue])
const pop = useCallback(() => {
const lastIndex = fieldArray.current.length - 1
const poppedItem = fieldArray.current[lastIndex]
fieldArray.current = update(fieldArray.current, {
$splice: [[lastIndex, 1]]
})
setFieldValue(field.name, fieldArray.current)
return poppedItem
}, [field.name, setFieldValue])
const replace = useCallback((index, value) => {
fieldArray.current = update(fieldArray.current, {
$splice: [[index, 1, value]]
})
setFieldValue(field.name, fieldArray.current)
}, [field.name, setFieldValue])
return [
field,
meta,
{
push,
swap,
move,
insert,
unshift,
remove,
pop,
replace
}
]
}
export {useFieldArray}
@joshsalverda
Copy link
Author

There was a sync issue bug but throwing a useEffect in there (lines 10-12) to keep the ref in sync with field.value seems to fix it. Hopefully doesn't cause other weird issues 😅

@natac13
Copy link

natac13 commented Mar 2, 2020

Oh that must be what was causing my issue today. Ill add that and see if I can remove my addition

  const reset = useCallback(
    (v) => {
      fieldArray.current = v
      setFieldValue(field.name, fieldArray.current)
    },
    [field.name, setFieldValue]
  )

Plus I exported the helpers from useField

@thadeu
Copy link

thadeu commented Apr 17, 2020

how to update this to useFastArrayHelper, in order to update local state?

@joshsalverda
Copy link
Author

@thadeu Not 100% sure what you mean, but guessing you want to return the helpers from useField? If so you should just be able to change line 6 to const [field, meta, helpers] = useField(props) and then just add helpers to the returned array. I haven't tried using those methods with this hook personally, but it should just work I think.

@Bulletninja
Copy link

Can you use it in an example? I keep getting: "update(): expected target of $push to be an array; got undefined."

@joshsalverda
Copy link
Author

@Bulletninja here is the "friend list" example taken from the formik docs and modified to work with useFieldArray: https://codesandbox.io/s/eloquent-leakey-v70t1?file=/src/App.js

@Bulletninja
Copy link

Thanks a lot!

@joshsalverda
Copy link
Author

joshsalverda commented Sep 16, 2020

I was having weird sync issues again recently (not sure what changed 🤔)

Haven't looked into it too closely, but as a quick fix changing from useEffect to useLayoutEffect seems to work. It can delay the rendering if you are pushing in lots of items, but at least no data gets lost...

@abranhe
Copy link

abranhe commented Jun 3, 2021

We need this as a part of formik.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment