Skip to content

Commit

Permalink
feat(unsetcompact): adds special [] syntax to unsetCompact
Browse files Browse the repository at this point in the history
Using an empty array syntax in a dot notation string allows recursively unsetting values by key in
all elements of an array
  • Loading branch information
aviemet committed Jun 9, 2024
1 parent 8f8c9b0 commit a06396a
Show file tree
Hide file tree
Showing 24 changed files with 333 additions and 307 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,12 @@ module.exports = {
}],
'comma-dangle': [2, 'always-multiline'],
},
overrides: [
{
files: ['*.test.ts*'],
rules: {
'no-unused-vars': 'off',
},
},
],
}
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.14.0
4 changes: 2 additions & 2 deletions src/Inputs/DynamicInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import NestedFields from '../NestedFields'
import useDynamicInputs from '../useDynamicInputs'
import NestedFields from './NestedFields'
import useDynamicInputs from './useDynamicInputs'

export interface DynamicInputsProps {
children: React.ReactNode
Expand Down
4 changes: 2 additions & 2 deletions src/NestedFields.tsx → src/Inputs/NestedFields.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react'
import { createContext } from './utils'
import { useFormMeta } from './Form/'
import { createContext } from '../utils'
import { useFormMeta } from '../Form'

export interface NestedFieldsProps {
children: React.ReactNode | React.ReactElement[]
Expand Down
3 changes: 3 additions & 0 deletions src/Inputs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { UseInertiaInputProps } from '../useInertiaInput'

export { default as Input } from './Input'
export { default as Submit } from './Submit'
export { default as NestedFields, type NestedFieldsProps } from './NestedFields'
export { default as DynamicInputs } from './DynamicInputs'
export { default as useDynamicInputs, type DynamicInputsProps } from './useDynamicInputs'

export type InputConflicts = 'name'|'onChange'|'onBlur'|'onFocus'|'value'|'defaultValue'
export interface BaseFormInputProps<T, TForm extends NestedObject = NestedObject>
Expand Down
2 changes: 1 addition & 1 deletion src/useDynamicInputs.ts → src/Inputs/useDynamicInputs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { useForm, useFormMeta } from './Form'
import { useForm, useFormMeta } from '../Form'
import { get, set } from 'lodash'
import { useNestedAttribute } from './NestedFields'

Expand Down
13 changes: 9 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
export { default as useInertiaForm, type UseInertiaFormProps, type NestedObject } from './useInertiaForm'
export { default as useInertiaInput, type UseInertiaInputProps } from './useInertiaInput'
export { default as useDynamicInputs } from './useDynamicInputs'
export {
Form,
useForm,
type HTTPVerb,
type UseFormProps,
type FormProps,
} from './Form'
export { default as NestedFields, type NestedFieldsProps } from './NestedFields'
export { default as DynamicInputs, type DynamicInputsProps } from './Inputs/DynamicInputs'
export { Input, Submit } from './Inputs'
export {
Input,
Submit,
NestedFields,
useDynamicInputs,
type NestedFieldsProps,
DynamicInputs,
type DynamicInputsProps,
} from './Inputs'
2 changes: 1 addition & 1 deletion src/useInertiaInput/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'
import { useForm } from '../Form'
import { useNestedAttribute } from '../NestedFields'
import { useNestedAttribute } from '../Inputs/NestedFields'
import inputStrategy, { type InputStrategy } from './inputStrategy'
import { type NestedObject } from '../useInertiaForm'

Expand Down
190 changes: 0 additions & 190 deletions src/utils.ts

This file was deleted.

21 changes: 21 additions & 0 deletions src/utils/createContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'

/**
* Creates context with simplified type notations
* Wraps useContext hook in an error check to enforce context context
*/
export const createContext = <CT extends unknown | null>() => {
const context = React.createContext<CT | undefined>(null)

const useContext = <T extends CT = CT>() => {
const c = React.useContext<T>(
(context as unknown) as React.Context<T>,
)
if(c === null) {
throw new Error('useContext must be inside a Provider with a value')
}
return c
}

return [useContext, context.Provider] as const
}
26 changes: 26 additions & 0 deletions src/utils/fillEmptyValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { isPlainObject } from 'lodash'

/**
* Replaces undefined or null values with empty values,
* either empty strings, empty arrays, or empty objects
* Allows using values from the server which may contain
* undefined or null values in a React controlled input
*/
export const fillEmptyValues = <TForm>(data: TForm) => {
const clone = structuredClone(data ?? {} as TForm)

for(const key in clone) {
if(isPlainObject(clone[key])) {
// @ts-ignore
clone[key] = fillEmptyValues(clone[key])
} else if(Array.isArray(clone[key])) {
// @ts-ignore
clone[key] = clone[key].map(el => fillEmptyValues(el))
} else if(clone[key] === undefined || clone[key] === null) {
// @ts-ignore
clone[key] = ''
}
}

return clone
}
43 changes: 43 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export { createContext } from './createContext'
export { fillEmptyValues } from './fillEmptyValues'
export { isUnset } from './isUnset'
export { renameObjectWithAttributes } from './renameObjectWithAttributes'
export { unsetCompact } from './unsetCompact'

/**
* Removes appended string (default of '_attributes') from dot notation
*/
export const stripAttributes = (str: string, attribute = '_attributes') =>
str.replace(new RegExp(`${attribute}\\.`), '.')

/**
* Ensures passed value is an array
*/
export const coerceArray = <T = unknown>(arg: T | T[]) => Array.isArray(arg) ? arg : [arg]

// Added recursion limit to path types to prevent the error:
// "Type instantiation is excessively deep and possibly infinite"
type Increment<A extends any[]> = [0, ...A];

type PathImpl<T, K extends keyof T, A extends any[] = []> =
A['length'] extends 5 ? never :
K extends string
? T[K] extends Record<string, any>
? T[K] extends ArrayLike<any>
? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>, Increment<A>>}`
: K | `${K}.${PathImpl<T[K], keyof T[K], Increment<A>>}`
: K
: never;

export type Path<T> = PathImpl<T, keyof T> | Extract<keyof T, string>;

export type PathValue<T, P extends Path<Required<T>>> =
P extends `${infer K}.${infer Rest}`
? K extends keyof Required<T>
? Rest extends Path<Required<T>[K]>
? PathValue<Required<T>[K], Rest>
: never
: never
: P extends keyof Required<T>
? Required<T>[P]
: never;
16 changes: 16 additions & 0 deletions src/utils/isUnset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { isEmpty } from 'lodash'

/**
* Returns whether a value should be considered empty in the context of a form input
*/
export const isUnset = (v: any) => {
if(typeof v === 'string') {
return v === ''
}

if(typeof v === 'number') {
return v === 0 ? false : !Boolean(v)
}

return isEmpty(v)
}
Loading

0 comments on commit a06396a

Please sign in to comment.