Johnathan3
Johnathan3

Reputation: 43

React Typescript - dynamic types

Is it possible to have dynamic type? I have a json like this

{
  "fieldName": "Some text",
  "type": String,
  "inputType": "text"
},
{
  "fieldName": "Some bool",
  "type": Boolean,
  "inputType": "checkbox
}

And based on that json I would like to render field components like this one

const Field: React.FC<FieldInterface> = ({ name, type, handler }) => {
  const [value, setValue] = useState<type>()

  const handleOnChane = (e: ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value)
    handler(name, e.target.value)
  }

  return (
    <div>
      <label htmlFor={name}>{name}</label>
      <input
        type={type}
        id={name}
        onChange={handleOnChane}
        value={value}
      ></input>
    </div>
  )
}
export default Field

here are my interfaces

export interface FormInterface {
  fields: FieldPropInterface[]
  submitAction: Function
}

export interface FieldPropInterface {
  name: string
  inputType: string
  type: <Here I would like to have something but don't know what>
}

export interface FieldInterface {
  name: string
  type: string
  handler: Function
}

You see, I need that type to set type of useState hook variable. Is it possible to do that?

Repo link: https://github.com/johnathan-codes/react-form-from-json

Upvotes: 4

Views: 8271

Answers (3)

Linda Paiste
Linda Paiste

Reputation: 42298

The other responses will work, but they aren't making use of the full power of typescript. What you want is to establish a relationship between the fields inputType and type such that fields with {inputType: "checkbox"} must always be boolean, {inputType: "text"} must always be string, and so on.

Here's how you would do that with a Union Type (you could also make use of a map or a conditional, but I won't get in to that).

type FieldPropInterface = {
    fieldName: string;
} & ({
    type: "string";
    inputType: "text";
} | {
    type: "boolean";
    inputType: "checkbox";
})

You need to read up a bit more on the HTML input element and its props because you want to treat a checkbox differently than a text input. Checkboxes set their value through a boolean property called checked rather than value which is a string. There are also loads of packages on npm that can make dealing with forms a lot easier.

If you want useState to be generic then your component Field should be generic.

You've also said that your FieldInterface.handler can be any type of Function, but you need to be specific about what that function is.

{ handler: (e: ChangeEvent<HTMLInputElement>) => void; }

or maybe it can become a function of value rather than event with a different setup.

Are you sure that you know what you're doing when using the capitalized name String? The capitalized name means that the value is the String constructor, whereas the lowercase means that the value is a string. From the docs:

Don't ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.

If you are using this as a flag, just to say "this value is a string" or "this value is a boolean" you might consider using the literal string "string" and "boolean" rather than the object constructors, but I don't know where and how this is actually being used in your code.

Upvotes: 3

Piyush Rana
Piyush Rana

Reputation: 667

You can also do something like this:

export interface FieldInterface<T> {
  name: string
  type: T
  handler: Function
}

const Field: React.FC<FieldInterface<typeof type>> = ({ name, type, handler }) => {
  const [value, setValue] = useState<type>()

  const handleOnChane = (e: ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value)
    handler(name, e.target.value)
  }

  return (
    <div>
      <label htmlFor={name}>{name}</label>
      <input
        type={type}
        id={name}
        onChange={handleOnChane}
        value={value}
      ></input>
    </div>
  )
}
export default Field

Upvotes: -1

kind user
kind user

Reputation: 41913

Use alternative types.

export interface FieldPropInterface {
  name: string;
  inputType: string;
  type: Boolean | String;
}

Upvotes: 2

Related Questions