Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
Manage array state through custom context
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixHenninger committed Aug 18, 2020
1 parent 735e94e commit 0b45f19
Showing 1 changed file with 113 additions and 19 deletions.
132 changes: 113 additions & 19 deletions packages/builder/src/components/Form/array.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,93 @@
import React, { Fragment } from 'react'
import React, { createContext, useContext, Fragment } from 'react'

import { FieldArray, useFormikContext, getIn } from 'formik'

// State management ------------------------------------------------------------

const ArrayContext = createContext({})

const ArrayContextProvider = ({ name, values,
arrayHelpers, setValues, setFieldValue, ...props
}) => {
const getRows = () => getIn(values, name) ?? []

const overwriteRows = (newValue) =>
setFieldValue(name, newValue)

// TODO: A fair amount of these helper functions are grid-specific.
// Maybe it might be worth investigating splitting them out
const overwriteAll = (rows, columns) => {
const baseName = name.replace('.rows', '')

setValues({
[baseName]: {
...getIn(values, baseName),
rows, columns,
}
})
}

const dispatch = (f) => {
const columnName = name.replace('.rows', '.columns')

overwriteAll(...f(
getIn(values, name) ?? [],
getIn(values, columnName) ?? [],
))
}

const addRow = () => arrayHelpers.push([])

const mapRows = (f) =>
overwriteRows(getRows().map(f))

const addColumn = (defaultCell, defaultColumn) =>
dispatch((rows, columns) => ([
rows.map(row => [...row, defaultCell]),
[...columns, defaultColumn],
]))

const deleteColumn = (index) =>
dispatch((rows, columns) => ([
rows.map(row => row.filter((_, i) => i !== index)),
columns.filter((_, i) => i !== index),
]))

const clearColumn = (index) =>
mapRows(row => {
const output = [...row]
output[index] = ''
return output
})

const fillColumn = (index) => {
// Gather cells with content
const availableCells = getRows()
.map(r => r[index])
.filter(r => r !== '')

return mapRows((r, rowIndex) => {
const output = [...r]
output[index] = output[index] ||
availableCells[rowIndex % availableCells.length]
return output
})
}

return (
<ArrayContext.Provider
value={{ setValues, setFieldValue, dispatch, overwriteAll,
addRow, addColumn, clearColumn, fillColumn, deleteColumn
}}
{ ...props }
/>
)
}

export const useArrayContext = () => useContext(ArrayContext)

// Basic form array ------------------------------------------------------------

export const FormArray = ({
name, item: Item,
header: Header, footer: Footer,
Expand All @@ -10,30 +96,38 @@ export const FormArray = ({
bodyWrapper: BodyWrapper=Fragment,
defaultItem={},
}) => {
const { values } = useFormikContext()
const { values, setFieldValue, setValues } = useFormikContext()
const rows = getIn(values, name)

return (
<FieldArray name={ name }>
{
arrayHelpers => <Wrapper { ...wrapperProps }>
{ Header && <Header name={ name } /> }
<BodyWrapper>
{
(rows || []).map(
(data, index) =>
<Item
key={ `${ name }[${ index }]` }
name={ `${ name }[${ index }]` }
index={ index }
isLastItem={ index === rows.length - 1 }
data={ data }
arrayHelpers={ arrayHelpers }
/>
)
}
</BodyWrapper>
{ Footer && <Footer addItem={ (item) => arrayHelpers.push(item || defaultItem) } /> }
<ArrayContextProvider
name={ name }
values={ values }
arrayHelpers={ arrayHelpers }
setValues={ setValues }
setFieldValue={ setFieldValue }
>
{ Header && <Header name={ name } /> }
<BodyWrapper>
{
(rows || []).map(
(data, index) =>
<Item
key={ `${ name }[${ index }]` }
name={ `${ name }[${ index }]` }
index={ index }
isLastItem={ index === rows.length - 1 }
data={ data }
arrayHelpers={ arrayHelpers }
/>
)
}
</BodyWrapper>
{ Footer && <Footer addItem={ (item) => arrayHelpers.push(item || defaultItem) } /> }
</ArrayContextProvider>
</Wrapper>
}
</FieldArray>
Expand Down

0 comments on commit 0b45f19

Please sign in to comment.