Create a generic context with a state from parent component using TypeScript

Viewed 412

I set a state in my page component formData and I would like to create a context in my form component to use it in each form child component. Without TS everything works perfectly. But with TS, it tell me on setFormData, username and email does not exist on type IFormValues.

I would like to find a tips to say "hey Form component data type is ..." and stay generic in my Form component.

My page

const RegisterPage: NextPage = () => {
    const [formData, setFormData] = useState({
        username: '',
        email: '',
    })
    return (
        <Form state={[formData, setFormData]} name="mySuperForm">...</Form>
    )
}

My component

interface IFormValues {
    [key: string]: string | number
}

interface IFormProps extends FormHTMLAttributes<Element> {
    state: [IFormValues, Dispatch<SetStateAction<IFormValues>>]
    name: string
}

interface IFormContext {
    values: IFormValues
    setValues: Dispatch<SetStateAction<IFormValues>>
    name: string
}

const FormContext = createContext<Partial<IFormContext>>({})

const Form: FC<IFormProps> = (props) => {
    const cleanProps = {...props, state: undefined}
    return (
        <FormContext.Provider value={{
            values: props.state[0], 
            setValues: props.state[1],
            name: props.name
        }}>
            <form {...cleanProps} />
        </FormContext.Provider>
    )
}

If I use that:

interface IFormValues {
    username: string
    email: string
}

everything works

1 Answers

You have to properly type the useState in the RegisterPage component the same way you do for the Form props.

import type { IFormValues } from "../components/Form";

const RegisterPage: NextPage = () => {
    const [formData, setFormData] = useState<IFormValues>({
        username: '',
        email: '',
    })
    return (
        <Form state={[formData, setFormData]} name="some-name">...</Form>
    )
}

Note that you also have to pass a name prop to Form as that's a required prop as well. TypeScript will complain otherwise.

Related