Don't repeat the type when you pass functions as props, use their types.
Let's suppose, there is a component named 'SignUpForm'.
export interface SignUpFormProps {
onSubmit?: (values: {
username: string;
nickname: string;
password: string;
}) => void;
}
export const SignUpForm = ({ onSubmit }: SignUpFormProps) => {
const [values, setValues] = useState({
username: "",
nickname: "",
password: "",
});
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setValues((prevValues) => ({
...prevValues,
[e.target.name]: e.target.value,
}));
};
const handleSubmit: React.FormEventHandler = (e) => {
e.preventDefault();
onSubmit?.(values);
};
return (
<form
onSubmit={handleSubmit}
style={{ display: "flex", flexDirection: "column" }}
>
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
/>
<input
type="text"
name="nickname"
value={values.nickname}
onChange={handleChange}
/>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
};
The submit event passes the arguments 'username', 'nickname' and 'password' to the onSubmit
prop if it is there.
You can pass the function like below.
function App() {
const handleSubmit = (
values
) => {
console.log(values);
};
return (
<div>
<SignUpForm onSubmit={handleSubmit} />
</div>
);
}
If you don't have an option "noImplicitAny": false
, it occurs an error.
For avoiding this problem, you can repeat the type of the onSubmit
.
If you just repeated like this, you'll have to keep up-to-date depending on the onSubmit
prop.
It would bother you.
In this case, you can get the field type of the interface using brackets.
function App() {
const handleSubmit:SignUpFormProps['onSubmit'] = (
values
) => {
console.log(values);
};
return (
<div>
<SignUpForm onSubmit={handleSubmit} />
</div>
);
}
If there is no interface, use React.ComponentProps
.
function App() {
const handleSubmit: React.ComponentProps<typeof SignUpForm>["onSubmit"] = (
values
) => {
console.log(values);
};
return (
<div>
<SignUpForm onSubmit={handleSubmit} />
</div>
);
}
That's it. I hope it will be helpful for someone.
Happy Coding!
+
Thanks for reading it, all you guys. I'm not sure if that was an appropriate example though. The point was that you can get the field type, and you can use it with other packages. Thank you for your attention again, Good coding!
Top comments (10)
So I actually used to do this but now heir on the side of repeating myself and encourage the team to do the same here. One of the things that can start to happen to large typescript code bases like this is you can end up coupling lots of components together by way of their type.
By repeating the type, you may be violating DRY but you avoid making these two components dependent upon one another. It's definitely a delicate balance though.
Or, avoid both issues altogether and define the type in a separate file and import in both components
I think you end up in the same place where a bunch of components end up dependent on the same type. I do like the idea of using Omit and Partial to extend a single use case for different areas, but tread carefully with the coupling still.
Defining the type in a separate file is a good idea when it makes sense in your domain. In this case a Credentials type would be a good idea. If the type only matters in a specific component you'll want to avoid tight coupling, so I wouldn't export the SignupFormProps or even the onSubmit handler type.
Looks okay to me.
Nice one. I always go with this. Don't see the reason why people are making a big deal out of this. Keeping types in a separate file and importing them where necessary makes your application obeys DRY principle.
Yes, that's a good idea. Thanks for your opinion :) Defining types in a seperate file and components that need the types import them.
But if you care every types like even a type of one of props, you might end up need a lot of types.
And other packages that you use might not have all types you need.
Yes I agree with that. Why don't we declare the type in a new file instead?
Yes, you're right. I agree with you. thanks for the comment :). Let's say, you make a component and use a type of another component in the component like that, they must be dependent. When the one changes, another one would be affected. If you aren't considered it, that would be not good in a way. But in the examples, I think that is totally fine. That component already depends on the SignUpForm Component. And used the field type.
But the interface resides inside the component to be used,
How does react knows about this before hand.
If the component doesn't export the interface, you can use ComponentProps.