Home > front end >  Using conditional typescript props with useContext
Using conditional typescript props with useContext

Time:08-06

Alright so on the application I am working on, I have a component called InputElement, where different HTML inputs (input, textarea, select, etc) can all use this component and share the same styles and properties (I use them with react-hook-form).

type Variant =
  | {
      variant: 'select'
      list: string[]
    }
  | {
      variant: 'input'
      list?: never
    }
  | {
      variant: 'textarea'
      list?: never
    }

These are the variant props. Input and textarea are the same, but when we give InputElement a prop of select, it needs to have a list of strings to render just like the html select tag would have option tags inside of it (will transform to option later). So far, this works well.

type Props = Variant & {
  /** Header of the InputElement */
  label: string

  /** Custom label to give the InputElement for form hook */
  registerLabel: string

  /** Input type of the field */
  type?: HTMLInputTypeAttribute
}

And these are the regular props. Pretty self explanatory there.

So I have tested this, and it works, but I'm trying to use it with React.useContext hook so I don't have to prop-drill all of these props.

createContext:

const InputContext = createContext<Props>({
  label: 'Default Label',
  registerLabel: 'example',
  type: 'text',
  variant: 'input'
})

InputElement component:

const InputElement = ({ label, registerLabel, type, ...props }: Props) => {
  const { register } = useFormContext()
  const { list, variant } = { ...props }
  return (
    <InputContext.Provider value={{ label, registerLabel, type, variant, list}}>
      <div className={styles.container} {...props}>
        <label>{label}</label>
        {getVariant(variant)}
      </div>
    </InputContext.Provider>
  )
}

(getVariant just returns the variant (input, textarea, select) that the component was passed). But typescript throws an error over the react context initial value key with this long log that ends with Type '"select"' is not assignable to type '"textarea"' ???

I have never used conditional types before, and I love how I can apply it here, but this is killing me. Any thoughts? Your help would be greatly appreciated. Thank you in advance.

CodePudding user response:

Most likely you don't want/need the list?: never. Try your union with the list missing for variants that dont need it, then have a function which returns the right context shape for a given variant:

import { createContext, HTMLInputTypeAttribute } from "react";

type Variant =
  | {
      variant: "select";
      list: string[];
    }
  | {
      variant: "input";
    }
  | {
      variant: "textarea";
    };

type Props = Variant & {
  /** Header of the InputElement */
  label: string;

  /** Custom label to give the InputElement for form hook */
  registerLabel: string;

  /** Input type of the field */
  type?: HTMLInputTypeAttribute;
};

const InputContext = createContext<Props>({
  label: "Default Label",
  registerLabel: "example",
  type: "text",
  variant: "input"
});

const getInputContext = (props: Props): Props => {
  switch (props.variant) {
    case "select":
      return { ...props, variant: "select", list: props.list };
    default:
      return { ...props, variant: props.variant };
  }
};

const InputElement = (props: Props) => {
  const { register } = useFormContext();
  const { label, registerLabel, type, ...otherProps } = props;
  return (
    <InputContext.Provider value={getInputContext(props)}>
      <div className={styles.container} {...props}>
        <label>{label}</label>
        {getVariant(variant)}
      </div>
    </InputContext.Provider>
  );
};

I'll stress what you are trying to achieve is a little odd. Condensing down JSX into configurable properties that just say what JSX to render, is potentially an anti pattern. What you would normally do is pass down the component into the core one, if you want some commonality. I think the abstraction you are introducing is potentially unnecessary indirection and is encouraging branching over composition (which is preferred).

  • Related