DEV Community

Cover image for Passing Props to Child Components in React using TypeScript
Francisco Mendes
Francisco Mendes

Posted on

Passing Props to Child Components in React using TypeScript

I believe that if you are reading this article, you already have an idea of component hireraquia and the normal flow is to pass props from the parent component to the child component.

I believe we all had some friction trying to convert our JavaScript knowledge to TypeScript, even though it was the same the code became more verbose and suddenly you started questioning everything.

I continue with several challenges on a daily basis, however today I am fully aware of the advantages that TypeScript brings to my application development experience in React.

Exactly today I'm going to give a brief example of how we can pass props between components using TypeScript.

Let's code

Pretend that the main page of your application is as follows:

// @src/App.tsx

import React, { useState } from "react";

import Form from "./components/Form";

const App = () => {
  const [state, setState] = useState("");
  const handleOnSubmit = (e) => {
    e.preventDefault();
    console.log({ state });
  };
  return (
    <Form
      state={state}
      setState={setState}
      handleOnSubmit={handleOnSubmit}
      placeholder="Type some letters"
    />
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

And the component of our form is as follows:

// @src/components/Form.tsx

import React from "react";

const Form = ({
  state,
  setState,
  handleOnSubmit,
  placeholder,
}) => {
  return (
    <form onSubmit={handleOnSubmit}>
      <input
        type="text"
        value={state}
        onChange={(e) => setState(e.target.value)}
        placeholder={placeholder}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

As you may have noticed both components, both are written the same way you would in JavaScript. And you may have noticed that we passed the following properties from the parent component to the child component:

  • state is a string;
  • setState is a function;
  • handleOnSubmit is a function;
  • placeholder is a string;

But before that we have to type our own function components. This way:

// @src/App.tsx

const App: React.FC = () => {
  // Hidden for simplicity
}


// @src/components/Form.tsx

const Form: React.FC = () => {
  // Hidden for simplicity
}
Enter fullscreen mode Exit fullscreen mode

So we can go to our Form.tsx and create a type called Props that will be used as a generic for our component.

// @src/components/Form.tsx

import React from "react";

type Props = {
  state: string;
  setState: (val: string) => void;
  handleOnSubmit: () => void;
  placeholder: string;
};

const Form: React.FC<Props> = ({
  state,
  setState,
  handleOnSubmit,
  placeholder,
}) => {
  return (
    // Hidden for simplicity
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

You may have noticed an inconsistency in the previous code, in App.tsx the handleOnSubmit function takes a single argument, which is an event.

While in our Props type of Form.tsx we don't have any arguments. For this we will use a React data type called FormEvent that will have a generic, which in this case will be the HTMLFormElement.

This way we will already have the ideal data type to "handle" the form event.Like this:

// @src/components/Form.tsx

import React, { FormEvent } from "react";

type SubmitEvent = FormEvent<HTMLFormElement>;

type Props = {
  state: string;
  setState: (val: string) => void;
  handleOnSubmit: (e: SubmitEvent) => void;
  placeholder: string;
};

const Form: React.FC<Props> = ({
  state,
  setState,
  handleOnSubmit,
  placeholder,
}) => {
  return (
    // Hidden for simplicity
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

This way, you must have also noticed that in the input element we have an attribute that is the onChange which is actually an event, so we have to type it.

In a very similar way to what we did before. First we will import a React data type called ChangeEvent, then we will assign a generic which in this case will be HTMLInputElement. This way:

// @src/components/Form.tsx

import React, { ChangeEvent, FormEvent } from "react";

type SubmitEvent = FormEvent<HTMLFormElement>;
type InputEvent = ChangeEvent<HTMLInputElement>;

// Hidden for simplicity

const Form: React.FC<Props> = ({
  // Hidden for simplicity
}) => {
  return (
    <form onSubmit={handleOnSubmit}>
      <input
        type="text"
        value={state}
        onChange={(e: InputEvent) => setState(e.target.value)}
        placeholder={placeholder}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

Now we can go back to our App.tsx. We just need to create a type in the handleOnSubmit function argument, which, as you might have guessed, is an event. Like this:

// @src/App.tsx

import React, { useState } from "react";

import Form from "./components/Form";

type FormEvent = React.FormEvent<HTMLFormElement>;

const App: React.FC = () => {
  const [state, setState] = useState("");
  const handleOnSubmit = (e: FormEvent) => {
    e.preventDefault();
    console.log({ state });
  };
  return (
    // Hidden for simplicity
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Finally we can add a generic to our useState(), which in this case is a string.

// @src/App.tsx

import React, { useState } from "react";

// Hidden for simplicity

const App: React.FC = () => {
  const [state, setState] = useState<string>("");
  // Hidden for simplicity
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

As always, I hope I was clear and that I helped you. If you notice any errors in this article, please mention them in the comments. ✏️

Hope you have a great day! 🙌 🤩

Discussion (5)

Collapse
lukeshiru profile image
LUKESHIRU • Edited

Nice! A few tips to add:

  • You can use the FormEventHandler and ChangeEventHandler types, that way you don't have to type the events again when using them (the same you'll get from the "onClick" for example on a native element).
  • You can import FC directly from react: import type { FC } from "react".
  • If your component will "extend a native element", you can do the following:
import { FC } from "react";

// Pay attention to this part in particular:
// We use `JSX.IntrinsicElements` to refer to a native element
// And then we augment it with the properties we want to add
export type AnchorProps = JSX.IntrinsicElements["a"] & {
    readonly aside?: boolean;
};

// Safe Anchor
export const Anchor: FC<AnchorProps> = ({ aside = false, ...props }) => (
    <a
        rel="noopener noreferrer"
        target={aside ? "_blank" : undefined}
        {...props}
    />
);

// Later when you use `Anchor`, it will have all the properties
// from an "a" element, like `href`, all the `aria` labels, and so on 
// with autocomplete, type checking, and all that :D
Enter fullscreen mode Exit fullscreen mode

The example is pretty minimalistic, because it should use forwardRef and maybe even memo, but is illustrative enough.

Hope they are useful! Cheers!

Collapse
gerreth profile image
Gerret Halberstadt

Hey, while in your example it makes sense to give the component an implicit children prop, in the article above it might be misleading. Someone could be using something like

<Form
  state={state}
  setState={setState}
  handleOnSubmit={handleOnSubmit}
  placeholder="Type some letters"
>
  Some info text
</Form>
Enter fullscreen mode Exit fullscreen mode

and wondering why the text is not rendering without TypeScript complaining. You could instead write it like

const Form = ({state, setState, handleOnSubmit, placeholder}: Props) => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

and do not lose something too useful. I stumbled upon github.com/typescript-cheatsheets/... (Why is React.FC discouraged?) a while ago and thought it could be useful to share here.

Collapse
lukeshiru profile image
LUKESHIRU

The good thing about FC or FunctionComponent is that from the TS side you have to return a valid ReactElement or null, while the function with the Props directly on the header can return anything. About children, is kinda easy to address:

const Form = ({ a, b, c, children, ...props }) => (
  <form {...props}>
    {children ?? (
       // Default content here, replaced by `children` if set
    )}
  </form>
);
Enter fullscreen mode Exit fullscreen mode

This approach is also ideal because it lets the dev change the default children if needed, or leave the default ones. Generally the flexibility of having components that "extend" html elements is great because anything expected from a native element can be expected of yours (className access, a11y, etc.), without having to add every single property to Props.

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks for sharing! Undoubtedly a good resource to study. 🤓

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks for your comment! It's a great addition to the knowledge shared in the article! 🤩