react-bfm
Version:
A basic field / form manager for React using hooks
88 lines (71 loc) • 3.29 kB
text/typescript
import { useContext, useMemo, useSyncExternalStore } from 'react'
import { BFMHooksContext } from '../context'
import { validateNamespace } from '../helpers'
import { FieldStateKeyType, FieldStateType, GetNamespaceType, NamespaceType } from '../common'
import {
FIELD_KEY_DIRTY,
FIELD_KEY_ERROR,
FIELD_KEY_FOCUS,
FIELD_KEY_TOUCHED,
FIELD_KEY_VALID,
FIELD_KEY_VALUE,
FIELD_KEY_VALUE_ON_FOCUS,
} from '../constants/field-keys'
import { NAMESPACE_STATE_DEFAULT } from '../constants/state-defaults'
const getServerSnapshot = () => undefined
export const useNamespaceState = (namespace: NamespaceType) => {
if (process.env.NODE_ENV !== 'production') {
if (!validateNamespace(namespace)) {
throw new Error('Expected string with a minimal length of 1 for `namespace`')
}
}
const { createSubscribeToNamespace, createGetSnapshotNamespaceState } = useContext(BFMHooksContext)
const subscribe = useMemo(() => createSubscribeToNamespace(namespace), [createSubscribeToNamespace, namespace])
const getSnapshot = useMemo(
() => createGetSnapshotNamespaceState(namespace),
[createGetSnapshotNamespaceState, namespace],
)
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
}
export const useNamespaceKeyValues = (
namespace: NamespaceType,
key: FieldStateKeyType,
): GetNamespaceType | undefined => {
const namespaceState = useNamespaceState(namespace)
return (
namespaceState &&
Object.entries<FieldStateType>(namespaceState).reduce((values: GetNamespaceType, [fieldName, fieldState]) => {
values[fieldName] = fieldState[key]
return values
}, {})
)
}
export const useNamespaceKeyIsEvery = (namespace: NamespaceType, key: FieldStateKeyType): boolean | undefined => {
const namespaceState = useNamespaceState(namespace)
return (
namespaceState &&
Object.values(namespaceState || NAMESPACE_STATE_DEFAULT).every(
(fieldState: FieldStateType) => fieldState[key] || false,
)
)
}
export const useNamespaceKeyIsSome = (namespace: NamespaceType, key: FieldStateKeyType): boolean | undefined => {
const namespaceState = useNamespaceState(namespace)
return (
namespaceState &&
Object.values(namespaceState || NAMESPACE_STATE_DEFAULT).some(
(fieldState: FieldStateType) => fieldState[key] || false,
)
)
}
/**
* Error values reflect the stored field error; falsy errors are normalized when mapped into field state.
*/
export const useNamespaceErrors = (namespace: NamespaceType) => useNamespaceKeyValues(namespace, FIELD_KEY_ERROR)
export const useNamespaceHasFocus = (namespace: NamespaceType) => useNamespaceKeyIsSome(namespace, FIELD_KEY_FOCUS)
export const useNamespaceIsDirty = (namespace: NamespaceType) => useNamespaceKeyIsSome(namespace, FIELD_KEY_DIRTY)
export const useNamespaceIsTouched = (namespace: NamespaceType) => useNamespaceKeyIsSome(namespace, FIELD_KEY_TOUCHED)
export const useNamespaceIsValid = (namespace: NamespaceType) => useNamespaceKeyIsEvery(namespace, FIELD_KEY_VALID)
export const useNamespaceValues = (namespace: NamespaceType) => useNamespaceKeyValues(namespace, FIELD_KEY_VALUE)
export const useNamespaceValuesOnFocus = (namespace: NamespaceType) =>
useNamespaceKeyValues(namespace, FIELD_KEY_VALUE_ON_FOCUS)