By leveraging TypeScript's type annotations, interfaces, and generics, you can establish strong type contracts within your React components.
Here's how to define types effectively in various scenarios:
- Props
type GreetProps = {
name: string;
messageCount?: number;
isLoggedIn: boolean;
};
export default function Greet(props: GreetProps) {
const { messageCount = 0, name, isLoggedIn } = props;
return (
<div>
<h1>
{isLoggedIn
? `Welcome ${name}, you have ${messageCount} unread message`
: `Welcome Guest`}
</h1>
</div>
);
}
- Union
type StatusProps = {
status: 'loading' | 'success' | 'error';
};
export default function Status({ status }: StatusProps) {
let message;
if (status === 'loading') {
message = 'Loading...';
} else if (status === 'success') {
message = 'Data fetched successfully';
} else if (status === 'error') {
message = 'Error fetching data';
}
return (
<div>
<h2>Status - {message}</h2>
</div>
);
}
- Children
type HeadingProps = {
children: string;
};
export default function Heading({ children }: HeadingProps) {
return <h1>{children}</h1>;
}
- Children (element)
type OscarProps = {
children: React.ReactNode;
};
export default function Oscar({ children }: OscarProps) {
return <div>{children}</div>;
}
- Event: onClick()
type ButtonProps = {
handleClick: (event: React.MouseEvent<HTMLButtonElement>, id: number) => void;
};
export default function Button(props: ButtonProps) {
return (
<button
onClick={(event) => props.handleClick(event, 1)}
className="btn-secondary"
>
Click
</button>
);
}
- Event: onChange()
type InputProps = {
value: string;
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
export default function Input({ value, handleChange }: InputProps) {
return <input type="text" value={value} onChange={handleChange} />;
}
- Styles
type ContainerProps = {
styles: React.CSSProperties;
};
export default function Container(props: ContainerProps) {
return <div style={props.styles}>Container</div>;
}
- State
import { useState } from 'react';
type AuthUser = {
name: string;
email: string;
};
export default function User() {
const [user, setUser] = useState<AuthUser>({} as AuthUser);
const handleLogin = () => {
setUser({
name: 'bayu',
email: 'bayu@gmail.com',
});
};
const handleLogout = () => {
setUser({} as AuthUser);
};
return (
<div>
<button className="btn-primary mr-3" onClick={handleLogin}>
Login
</button>
<button className="btn-info" onClick={handleLogout}>
Logout
</button>
<h3>User name is {user.name}</h3>
<h3>User email is {user.email}</h3>
</div>
);
}
- Reducer
import { useReducer } from 'react';
const initialState = {
count: 0,
};
type CounterState = {
count: number;
};
type CounterAction = {
type: string;
payload: number;
};
function reducer(state: CounterState, action: CounterAction) {
switch (action.type) {
case 'increment':
return { count: state.count + action.payload };
case 'decrement':
return { count: state.count - action.payload };
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({ type: 'increment', payload: 1 })}
className="btn-primary"
>
Increment
</button>
<button
onClick={() => dispatch({ type: 'decrement', payload: 1 })}
className="btn-secondary"
>
Decrement
</button>
</>
);
}
Incorporating TypeScript type definitions into your React projects revolutionizes the way you build and maintain user interfaces. By precisely annotating prop types, state types, context types, and even custom hooks, you're able to catch errors early, improve code documentation, and make collaboration smoother.
This blog post has illuminated the significance of type definitions in React TypeScript development and provided practical examples for different scenarios. With these techniques, you're well-equipped to create more predictable and robust React applications, ultimately delivering a better user experience and accelerating your development process.
Hope this helps!
Top comments (1)
Hi, kindly leave a like and comment if you got new insight! π₯