Skip to content

Commit

Permalink
Merge pull request #84 from iway1/docs-overhaul
Browse files Browse the repository at this point in the history
finish docs overhaul, adds codesandbox
  • Loading branch information
iway1 authored Mar 12, 2023
2 parents ce0a18e + 9fc8f55 commit 6fbd5de
Show file tree
Hide file tree
Showing 28 changed files with 399 additions and 95 deletions.
5 changes: 5 additions & 0 deletions www/docs/docs/codesandbox-starter.md
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
7 changes: 7 additions & 0 deletions www/docs/docs/example-inputs/_category_.json
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"
}
}
20 changes: 20 additions & 0 deletions www/docs/docs/example-inputs/checkbox.md
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>
);
}
```
40 changes: 40 additions & 0 deletions www/docs/docs/example-inputs/multi-checkbox.md
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>
))}
</>
);
}
```
26 changes: 26 additions & 0 deletions www/docs/docs/example-inputs/number-field.md
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>
</>
);
}
```
25 changes: 25 additions & 0 deletions www/docs/docs/example-inputs/select.md
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>
</>
);
}
```
20 changes: 20 additions & 0 deletions www/docs/docs/example-inputs/text-field.md
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}
</>
);
}
```
2 changes: 1 addition & 1 deletion www/docs/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sidebar_position: 1

# Installation

Make sure you have <code>"strict": true</code> in your `tsconfig.json` `compilerOptions` and make sure you set your editors [typescript version to v4.9](https://github.com/iway1/react-ts-form#typescript-versions) (or intellisense won't be as reliable).
Make sure you have <code>"strict": true</code> in your `tsconfig.json` `compilerOptions` and make sure you set your editors [typescript version to v4.9 or above](https://github.com/iway1/react-ts-form#typescript-versions) (or intellisense won't be as reliable).

Install package and dependencies with your preferred package manager:

Expand Down
26 changes: 0 additions & 26 deletions www/docs/docs/manual-form-submission.md

This file was deleted.

23 changes: 3 additions & 20 deletions www/docs/docs/react-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
sidebar_position: 9
---

# React Native Usage
# React Native

This library works the same in React Native as it does in React.

## React Native Usage

Expand Down Expand Up @@ -30,22 +32,3 @@ const mapping = [

const MyForm = createTsForm(mapping, { FormComponent: FormContainer });
```

## Default values

You can provide typesafe default values like this:

```tsx
const Schema = z.object({
string: z.string(),
num: z.number()
})
//...
<MyForm
schema={Schema}
defaultValues={{
string: 'default',
num: 5
}}
/>
```
125 changes: 125 additions & 0 deletions www/docs/docs/recommended-setup-tips.md
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
2 changes: 1 addition & 1 deletion www/docs/docs/troubleshooting/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ sidebar_position: 2
## Limitations

- Doesn't support class components
- `@ts-react/form` allows you to pass props to your components and render elements in between your components, which is good for almost all form designs out there. Some designs may not be easily achievable. For example, if you need a container around multiple sections of your form, this library doesn't allow splitting child components into containers at the moment. (Though if it's a common-enough use case and you'd like to see it added, open an issue!)
- `@ts-react/form` doesn't currently support dependent field props (but will in the future), so you won't be able to modify the behavior of input components based on form values (for example if you needed to hide a form field based on the value of another form field)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
sidebar_position: 6
---

# Include Non-Input Components
# Non-Input Components

## Adding non input components into your form

Expand All @@ -21,3 +21,5 @@ Some times you need to render components in between your fields (maybe a form se
}}
/>
```

Or you can use the [custom layouts feature](./custom-layouts.md)
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions www/docs/docs/usage/default-values.md
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
}}
/>
```
Loading

1 comment on commit 6fbd5de

@vercel
Copy link

@vercel vercel bot commented on 6fbd5de Mar 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.