-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #84 from iway1/docs-overhaul
finish docs overhaul, adds codesandbox
- Loading branch information
Showing
28 changed files
with
399 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# CodeSandbox Starter | ||
|
||
Here's a tiny codesandbox starter to get you going: | ||
|
||
https://codesandbox.io/s/react-ts-form-example-6gkuyx?file=/src/App.tsx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"label": "Example Components", | ||
"link": { | ||
"type": "generated-index", | ||
"description": "Usage of the module" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# CheckBox | ||
|
||
```tsx | ||
function Checkbox({ name }: { name: string }) { | ||
const { | ||
field: { onChange, value }, | ||
} = useTsController<boolean>(); | ||
|
||
return ( | ||
<label> | ||
{name} | ||
<input | ||
onChange={(e) => onChange(e.target.checked)} | ||
checked={value ? value : false} | ||
type="checkbox" | ||
/> | ||
</label> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# MultiCheckBox | ||
|
||
```tsx | ||
function MultiCheckbox({ options }: { options: string[] }) { | ||
const { | ||
field: { onChange, value }, | ||
} = useTsController<string[]>(); | ||
|
||
function toggleField(option: string) { | ||
if (!value) onChange([option]); | ||
else { | ||
onChange( | ||
value.includes(option) | ||
? value.filter((e) => e !== option) | ||
: [...value, option] | ||
); | ||
} | ||
} | ||
|
||
return ( | ||
<> | ||
{options.map((optionValue) => ( | ||
<label | ||
htmlFor={optionValue} | ||
style={{ display: "flex", flexDirection: "row" }} | ||
key={optionValue} | ||
> | ||
{optionValue} | ||
<input | ||
name={optionValue} | ||
type="checkbox" | ||
onChange={() => toggleField(optionValue)} | ||
checked={value?.includes(optionValue) ? true : false} | ||
/> | ||
</label> | ||
))} | ||
</> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# NumberField | ||
|
||
```tsx | ||
function NumberField({ req }: { req: number }) { | ||
const { | ||
field: { onChange, value }, | ||
error, | ||
} = useTsController<number>(); | ||
return ( | ||
<> | ||
<span> | ||
<span>{`req is ${req}`}</span> | ||
<input | ||
value={value !== undefined ? value + "" : ""} | ||
onChange={(e) => { | ||
const value = parseInt(e.target.value); | ||
if (isNaN(value)) onChange(undefined); | ||
else onChange(value); | ||
}} | ||
/> | ||
{error && error.errorMessage} | ||
</span> | ||
</> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# SelectField | ||
|
||
```tsx | ||
function Select({ options }: { options: string[] }) { | ||
const { field, error } = useTsController<string>(); | ||
return ( | ||
<> | ||
<select | ||
value={field.value ? field.value : "none"} | ||
onChange={(e) => { | ||
field.onChange(e.target.value); | ||
}} | ||
> | ||
{!field.value && <option value="none">Please select...</option>} | ||
{options.map((e) => ( | ||
<option key={e} value={e}> | ||
{e} | ||
</option> | ||
))} | ||
</select> | ||
<span>{error?.errorMessage && error.errorMessage}</span> | ||
</> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# TextField | ||
|
||
```tsx | ||
function TextField() { | ||
const { | ||
field: { onChange, value }, | ||
error, | ||
} = useTsController<string>(); | ||
|
||
return ( | ||
<> | ||
<input | ||
onChange={(e) => onChange(e.target.value)} | ||
value={value ? value : ""} | ||
/> | ||
{error && error.errorMessage} | ||
</> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Recommended Setup Tips | ||
|
||
## Tips | ||
|
||
For most projects, the following approach will work well | ||
|
||
## Tip 1. Create a custom form component | ||
|
||
Creating a custom form container is recommended for DRYness. For example: | ||
|
||
```tsx | ||
function FormContainer({ | ||
onSubmit, | ||
children, | ||
loading, | ||
}: { | ||
onSubmit: () => void; | ||
children: ReactNode; | ||
loading?: boolean; | ||
}) { | ||
return ( | ||
<form className="space-y-4" onSubmit={onSubmit}> | ||
{children} | ||
<Button className="mt-4 w-full" type="submit" loading={loading}> | ||
Submit | ||
</Button> | ||
</form> | ||
); | ||
} | ||
|
||
// Make sure to pass it to `createTsForm` | ||
const Form = createTsForm(mapping, { FormComponent: FormContainer }); | ||
``` | ||
|
||
The above form component has a "loading" prop to show a loading spinner while the form is submitting to make it easy for us to code a great UX. Also notice the form component has a `space-y-4`, ensuring consistent spacing between form components. | ||
|
||
## 2. Consider creating custom components per text field type | ||
|
||
There are many different types of text fields we may want to implement. For example, a phone input might behave differently than a password input. If you're buildling custom behavior per input types, consider using a different Schema per input: | ||
|
||
```tsx | ||
const PhoneSchema = createUniqueFieldSchema( | ||
z.string().regex(/[0-9]{10}/), | ||
"phone" | ||
); | ||
const PasswordSchema = createUniqueFieldSchema( | ||
z.string().min(8, "Must be 8 characters in length"), | ||
"password" | ||
); | ||
|
||
const mapping = [ | ||
[z.string(), TextField], | ||
[PhoneSchema, PhoneField], | ||
[PasswordSchema, PasswordField], | ||
] as const; | ||
``` | ||
|
||
### Component Composition / Wrapper Components | ||
|
||
Since we probably want to share some styling and such across these similar types of fields, we can use a base controlled component as a child of the components passed to the mapping, and the components passed to the mapping would be "wrappers" for this inner base text field component: | ||
|
||
```tsx | ||
function TextFieldBase({ | ||
label, | ||
value, | ||
onChange, | ||
error, | ||
inputProps, | ||
}: { | ||
value: string; | ||
onChange: (value: string) => void; | ||
label?: string; | ||
error?: string; | ||
inputProps?: Omit<ComponentProps<"input">, "onChange" | "value">; | ||
}) { | ||
<div> | ||
<label>{label}</label> | ||
<input | ||
className={/* custom input styles*/} | ||
value={value} | ||
onChange={(e) => { | ||
onChange(e.target.value); | ||
}} | ||
{...inputProps} | ||
/> | ||
<span>{error}</span> | ||
</div>; | ||
} | ||
|
||
function TextField() { | ||
const { | ||
field: { onChange, value }, | ||
error, | ||
} = useTsController<string>(); | ||
const { label } = useDescription(); | ||
|
||
return ( | ||
<TextFieldBase | ||
value={value} | ||
onChange={onChange} | ||
label={label} | ||
error={error?.errorMessage} | ||
/> | ||
); | ||
} | ||
|
||
function PhoneField() { | ||
const { | ||
field: { onChange, value }, | ||
error, | ||
} = useTsController<string>(); | ||
const { label } = useDescription(); | ||
|
||
return ( | ||
<TextFieldBase | ||
value={maskPhone(value)} | ||
onChange={(value) => onChange(unmaskPhone)} | ||
label={label} | ||
error={error?.errorMessage} | ||
/> | ||
); | ||
} | ||
``` | ||
|
||
In simple cases this may not be necessary, and may be easier to just pass props to the same TextField component. But if the input components are very different from eachother, we can save passing a lot of props by creating these types of wrappers. If you're using a component library like MUI or Mantine, you likely won't need to define `TextFieldBase` yourself, and should instead use the TextField component from the component library |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Default values | ||
|
||
You can provide typesafe default values to your form like this: | ||
|
||
```tsx | ||
const Schema = z.object({ | ||
string: z.string(), | ||
num: z.number() | ||
}) | ||
//... | ||
<MyForm | ||
schema={Schema} | ||
defaultValues={{ | ||
string: 'default', | ||
num: 5 | ||
}} | ||
/> | ||
``` |
Oops, something went wrong.
6fbd5de
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
react-ts-form – ./
react-ts-form-iway1.vercel.app
react-ts-form.com
react-ts-form-git-main-iway1.vercel.app
react-ts-form.vercel.app