Skip to content

Instantly share code, notes, and snippets.

@yangshun
Last active January 30, 2019 21:45
Show Gist options
  • Save yangshun/719f5390c28c0b7f7cd1b4e031526fad to your computer and use it in GitHub Desktop.
Save yangshun/719f5390c28c0b7f7cd1b4e031526fad to your computer and use it in GitHub Desktop.
'use strict';
const {useCallback, useState} = require('React');
const nullthrows = require('nullthrows');
type InputValue<T> = {|
// Whether the input value has changed since it was initialized.
changed: boolean,
// A string key to uniquely identify the value.
key: ?string,
// Whether the input value is invalid.
invalid: boolean,
// The input value that's transformed for submission. Useful when working
// with an intermediate format that needs to be transformed before sending
// as a payload within a request.
submissionValue: mixed,
// Where the input value is valid.
valid: boolean,
// The raw input value.
value: T,
// Callback to update the input value.
onChange: (value: T) => void,
|};
function useInputValue<T>(
initialValue: T,
opts?: {|
// Predicate to determine if two values are different.
changePredicate?: (currentValue: T, initialValue: T) => boolean,
// Unique key of the form value. Will be the key of the object if used in formInputValues.
key?: string,
// Transforms a value before being submitted.
submissionValueTransform?: (currentValue: T) => mixed,
// Determines if a value is valid.
validate?: (value: T) => boolean,
|},
): InputValue<T> {
const [value, setValue] = useState(initialValue);
const onChange = useCallback(value => {
setValue(value);
}, []);
const options = Object.assign(
{},
{
// Defaults to reference equality.
changePredicate: (currentValue: T, initialValue: T) =>
currentValue !== initialValue,
key: null,
// Defaults to identity.
submissionValueTransform: (currentValue: T) => currentValue,
// Defaults to true.
validate: (_: T) => true,
},
opts,
);
const valid = options.validate(value);
return {
changed: options.changePredicate(value, initialValue),
key: options.key,
invalid: !valid,
submissionValue: options.submissionValueTransform(value),
value,
valid,
onChange,
};
}
type FormInputValues = {|
// An object of all values, where the key is the field key.
allValues: {[string]: mixed},
// An array of keys where values have changed.
changedKeys: Array<string>,
// Whether any value has changed.
hasChanged: boolean,
// Whether any value is invalid.
hasInvalid: boolean,
// An array of keys where their values are invalid.
invalidKeys: Array<string>,
// An object of values after mapping to a submission-friendly format.
submissionValues: {[string]: mixed},
|};
/**
* Receives an array of input values and determines the overall form validity state.
*/
function formInputValues(
values: Array<
$IntentionallyInexact<{
changed: boolean,
invalid: boolean,
key: ?string,
submissionValue: mixed,
}>,
>,
): FormInputValues {
const allValues = values
.filter(value => value.key)
.reduce((values, value) => {
values[nullthrows(value.key)] = value.submissionValue;
return values;
}, {});
const changedKeys: Array<string> = values
.filter(value => value.changed && value.key)
.map(value => nullthrows(value.key));
const invalidKeys: Array<string> = values
.filter(value => value.invalid)
.map(value => nullthrows(value.key));
const submissionValues = values
.filter(value => value.changed && value.key)
.reduce((values, value) => {
values[nullthrows(value.key)] = value.submissionValue;
return values;
}, {});
return {
allValues,
changedKeys,
hasChanged: changedKeys.length > 0,
hasInvalid: invalidKeys.length > 0,
invalidKeys,
submissionValues,
};
}
module.exports = {
useInputValue,
formInputValues,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment