Input state management for React. It comes with useful common validations that are applied if you enabled them.
It supports custom validation asynchronous or not, with dynamic error messages.
npm install aio-inputs
import {useInputs} from "aio-inputs";
There are five different ways to use it.
- Passing a string
- Passing an array of string.
- Passing a mix of array of string and object.
- Passing an array of objects.
- Passing an object.
This is useful if you want a single input that doesn't require validation.
const [input, setInput] = useInputs("name")
This gives you access to an input object with the following properties
input.touched
input.valid
input.value
input.placeholder
and many other ready-to-use properties listed here INPUT PROPERTIES.
Give you access to an array of inputs with ready-to-use properties listed here INPUT PROPERTIES.
const [input, setInput] = useInputs(["name", "firstname"])
Useful when you want to keep validation on certain entries. For those who don't need validation, simply pass a
string.
For example, name doesn't require validation, when another input does.
const [input, setInput] = useInputs(["name", {validation: {minLength: 3}}])
You can also name your inputs with the name
property
const [input, setInput] = useInputs(["name", {name: "myInputName",validation: {minLength: 3}}])
For available properties, JUMP here INPUT PROPERTIES.
const [input, setInput] = useInputs([{}, {}])
Object that doesn't require validation.
const [input, setInput] = useInputs({name: {}, contact: {}})
Object with validation
const [input, setInput] = useInputs(
{
name: {validation: {minLength: 3}},
contact: {validation: {required: true}},
}
)
JUMP here for all properties INPUT PROPERTIES.
Now you are ready to bind some htmlInputElements to your input state. For example
const [inputState, setInputState] = useInputs("name");
<input value={inputState.value}/>
More example with some validation rules. When you add validation don't forget to provide a general error message.
An Error message, specific or not can be set as a string or map of string / object for internationalization support
- General error message as a string
const [inputState, setInputState] = useInputs({
name: {
errorMessage: "my error message",
validation: {
minLength: 3
}
}
});
- General error message as object
const language = getLanguageFromSomewhere()
const [inputState, setInputState] = useInputs({
name: {
errorMessage: {
fr: "Some fr error",
en: "Some en error"
},
validation: {
minLength: 3
}
}
});
<span>{inputState.name.errorMessage[language]}</span>
Error message can also be specific to each validation.
Note
Specific error message has a priority on general error message.
If we found a specific error message, it will be used otherwise, we fall back to the general error message if it
exists.
// As a string
const [inputState, setInputState] = useInputs({
name: {
validation: {
required: {
message: "This field is required"
},
minLength: {
value: 3,
message: "At least 3 characters"
}
}
}
});
// As an object
const [inputState, setInputState] = useInputs({
name: {
validation: {
required: {
message: {
fr: "Cette valeur est requise",
en: "This field is required"
}
},
minLength: {
value: 3,
message: {
fr: "Au moins 3 caractères",
en: "At least 3 characters"
}
}
}
}
});
You can share validation rules across your inputs with the copy
keyword.
- With object as entry
const [inputState, setInputState] = useInputs({
homePhone: {
validation: {
required: true,
minLength: 10,
startsWith: "+"
}
},
officePhone: {
validation: {
copy: "homePhone",
//... add those intended only for officePhone if you wish
}
}
});
- With array as entry
You need to provide id
for the input you want to copy. Make sure it is unique otherwise, Same id on inputs merge those
inputs into one Input.
ID need to be unique in the current component not in your app.
Component1 inputs => id => "test
✅
Component2 inputs => id => "test
✅
Component1 inputs => id => "test
❌
Component1 inputs => id => "test
❌
const [inputState, setInputState] = useInputs([
{
// id here
id: "homePhone",
validation: {
required: true,
minLength: 10,
startsWith: "+"
}
},
{
validation: {
// We copy
copy: "homePhone",
//... add those intended only for officePhone if you wish
}
}
]);
There is no limit when you copy validation. But you must follow one rule.
Important
He who is copied must not copy himself or one of those who copy him**
This is an example that leads to an infinite copy
const [inputState, setInputState] = useInputs({
homePhone: {
validation: {
required: true,
minLength: 10,
startsWith: "+",
copy: "officePhone"
}
},
officePhone: {
validation: {
regex: /..../,
copy: "homePhone"
}
}
});
Here officePhone
copy homePhone
which copy officePhone
and so on leading to an infinite copy.
If you want homePhone
to copy regex
validation from officePhone
while officePhone
copy validation
from homePhone
,
you can do it that way because at the end, they will share same validation rules.
const [inputState, setInputState] = useInputs({
homePhone: {
validation: {
required: true,
minLength: 10,
startsWith: "+",
// Move regex here
regex: /.../,
}
},
officePhone: {
validation: {
// we copy here
copy: "homePhone"
}
}
});
You can copy as many as you want as long as you comply with the above rule. Here for example, username
copy firstname
who copy name
, when grandPa
copy firstname
and so on . The order doesn't matter
const [inputState, setInputState] = useInputs({
name: {
validation: {
required: true,
minLength: 5,
maxLength: 100
}
},
firstname: {
validation: {
copy: "username"
}
},
username: {
validation: {
copy: "name", // or copy name
regex: /..../ // only for username
}
},
grandPa: {
validation: {
copy: "firstname", // or copy name, it doesn't matter
}
}
});
match
key works the same in a little different way. Instead of only copy validation, it makes sure that inputs share
the same validation and the same value.
It is very useful for example password validation.
const [inputState, setInputState] = useInputs({
password: {
validation: {
required: true,
minLength: 8,
}
},
confirmPassword: {
validation: {
match: "password"
}
}
});
You can match as many as you want, as long as you comply with the above rule.
If we apply it for match, we get this rule
Important
He who is matched must not match himself or one of those who matched him.
You cannot copy or match an input that doesn't exist. If you do so, you will get this error.
TypeError: Cannot read properties of undefined reading 'validation'
const [inputState, setInputState] = useInputs({
password: {
validation: {
required: true,
minLength: 8,
}
},
confirmPassword: {
validation: {
// ❌ We match something that doesn't exist
match: "sdhslhdlhsdl"
}
}
});
Note
copy
and match
are basically the same. After the copy or the match, they always keep their own validation and
error message (specific or general) if present.
match
differs from copy
by checking also if all matched inputs share the same value.
It is important to understand the difference between them and use them accordingly
Common use case example with a custom error message for both inputs.
const [inputState, setInputState] = useInputs({
password: {
errorMessage: "Differ from password confirmation",
validation: {
required: true,
minLength: 8,
}
},
confirmPassword: {
// We keep this message for error on confirmPassword and share validation with password
errorMessage: "Differ from password",
validation: {
match: "password"
}
}
});
- String update
If you pass a string, handle change like this, and we will take care of all other stuff
const [inputState, setInputState] = useInputs("name");
<input value={inputState.value} onChange={(e) => setInputState(e.target.value)}/>
Just pass the value and you are done.
- Array of string update
If you pass an array of string, handle change like this, and we will take care of all other stuff
const [inputState, setInputState] = useInputs(["name", "firstname"]);
<div>
{inputState.map((input) => {
return <input key={input.key} value={input.value} onChange={e => setInputState(input, e.target.value)}/>
})}
</div>
Just pass the input and the value, and you are done.
- Object update
If you pass an object, handle change like this, and we will take care of all other stuff
const [inputState, setInputState] = useInputs({
contact: {},
description: {
validation: {
minLength: 10,
}
},
extra: {},
});
const {description} = inputState;
<input value={description.value} onChange={e => setInputState(description, e.target.value)}/>
Just pass the input you are updating and the value, and you are done.
If you are using object and do not want validation, Generate your inputs like this
const [inputState, setInputState] = useInputs({
name: {},
optional: {},
tag: {}
})
But if you really do not need validation, we recommend you to use the array instead. It is shorter.
useInputs(["name", "optional", "tag"])
Validation properties are listed here INPUT PROPERTIES.
But let's talk about a special one, YOURS. You can add a custom synchronous or asynchronous validation.
And to do so, you need to follow one rule.
Important
Your custom validation must return a boolean, true
or false
. Nothing else
- Synchronous custom validation
const [myInputs, setMyInputs] = useInputs({
name: {
validation: {
// set is optionnal. But you can use it to update the error message
custom: (value, set) => {
// we will give you the value entered by the user and a function to update the error message if you want
// validate it like you want but at the end tell us if it is valid or not
// After doing your validation
return true
}
}
}
})
- Asynchronous custom validation
For performance purpose, you need to passasync
property totrue
. Do not worry about multiple calls to the server. We make sure to call the server only when user stops typing.
const [myInputs, setMyInputs] = useInputs({
name: {
validation: {
async: true,
// set is optionnal. But you can use it to update the error message
custom: async (value, set) => {
const r = await someting;
!r.valid && set("Not available")
return r.valid
}
}
}
})
What happen if I didn't set async
property with custom asynchronous validation ?
Well, your custom asynchronous validation will not be triggered. Or if triggered, will be treated as a synchronous
function
So, do not forget to set async
to true when using asynchronous validation.
Note
Remove async property if your custom validation is not asynchronous, otherwise, you will notice a delay when validation occurs
- Asynchronous custom validation with Promise
const [myInputs, setMyInputs] = useInputs({
name: {
validation: {
async: true,
custom: (value) => new Promise(resolve => {
resolve(true)
})
}
}
})
- Asynchronous indicator
You can show some loader when doing asynchronous validation.
For example, we want username to be unique, so we call our server to check if user choice is available or already taken.
You can use thevalidating
property for that
const [myInputs, setMyInputs, form] = useInputs({
username: {
validation: {
async: true,
custom: async (value) => {
// some stuff
return false
}
}
}
})
// Later
myInputs.username.validating && <span>Validating your username ....</span>
In the form
object, you have access to a reset
method and a isValid
property.
reset
let you reset a form when you successfully submitisValid
tell you if the whole form is valid, if all your inputs are valid
const [myInputs, setMyInputs, form] = useInputs(...)
// Reset your form
form.reset()
const [myInputs, setMyInputs, form] = useInputs(...)
const submit = () => {
if (form.isValid) {
// Submit
}
}
<button className={form.isValid ? validClass : invalidClass} onClick={submit}>Submit</button>
These are automatically added to your state when your call useInputs
. You can override their value by passing them.
Except two internal used keys
__
, ___
. Those keys are used internally. You can't override them
id
input id.<-- overridable
only if you deal with object as entrykey
A crypto-based key for your input.<-- not overridable
name
Input name.<-- overridable
type
Html input element type.<-- overridable
label
Input label.<-- overridable
value
Input value.<-- overridable but will change on user input
resetValue
The value to put when you reset the input.<-- overridable
valid
Tell you if input is valid or not.<-- overridable but can change on user input based on validation
touched
Tell you if input is touched or not.<-- overridable but will change on user input
placeholder
Input placeholder.<-- overridable
errorMessage
General error message when input is invalid.<-- overridable
validating
Tell you if an asynchronous validation is in processing state<-- overridable but will change when processing an asynchronous validation
validation
Validation options. See the validation properties HERE<-- overridable
__
Internal key.<-- not overridable
___
Internal key.<-- not overridable
required
Make the value required.<-- boolean
async
Enable asynchronous validation.<-- boolean
email
Treat the value as email<-- boolean
number
Treat the value as a number.<-- boolean
min
The minimum acceptable value.<-- number
max
The maximum acceptable value.<-- number
minLength
The minimum length of the value.<-- number
minLengthWithoutSpace
The minimum length of trimmed value with no space at all.<-- number
maxLength
The maximum length of the value.<-- number
maxLengthWithoutSpace
The maximum length of trimmed value with no space at all.<-- number
match
The matched input name.<-- string
copy
The copied input name.<-- string
startsWith
The input will start with that value.<-- string
endsWith
The input will end with that value.<-- string
equalsTo
The input will be strictly equal to that value.<-- any
regex
Your regex validation.<-- regex
custom
A function that return a boolean.<-- (value, set) => boolean or Promise<boolean>
.
required
Make the value required.<-- {value: boolean, message: string or object }
email
Treat the value as email<-- {value: boolean, message: string or object }
number
Treat the value as a number.<-- {value: number, message: string or object }
min
The minimum acceptable value.<-- {value: number, message: string or object }
max
The maximum acceptable value.<-- {value: number, message: string or object }
minLength
The minimum length of the value.<-- {value: number, message: string or object }
minLengthWithoutSpace
The minimum length of trimmed value with no space at all.<-- {value: number, message: string or object }
maxLength
The maximum length of the value.<-- {value: number, message: string or object }
maxLengthWithoutSpace
The maximum length of trimmed value with no space at all.<-- {value: number, message: string or object }
startsWith
The input will start with that value.<-- {value: string, message: string or object }
endsWith
The input will end with that value.<-- {value: string, message: string or object }
equalsTo
The input will be strictly equal to that value.<-- {value: any, message: string or object }
regex
Your regex validation.<-- {value: regex, message: string or object }
For example
validation: {
required: {
message: "It is required"
},
email: {
message: "Not a valid email"
}
}
validation: {
regex: {
value: /my regex/
message: "my error message based on regex"
},
endsWith: {
value: "end"
message: "Must en with end"
}
}