Discriminated Unions in TypeScript provide a powerful mechanism for handling various types in a concise and type-safe manner. In the context of React, Discriminated Unions can be particularly useful when dealing with components that render different content based on the type of data they receive.
Discriminated unions shine when you need type-safe handling of different possibilities with distinct properties. Discriminated Unions in TypeScript are useful when you want to create complex type structures by combining two or more types into a single type based on a common property or key. It is a way of combining two or more types into a single type, based on a common property or key.
Table of Contents
Example of Discriminated Unions in React TypeScript
Here are a few examples of how you can use Discriminated Unions in React with TypeScript with useState with discriminated union typescript.
import React, { useState, useEffect } from 'react';
// Discriminated union for user data or error
type UserResponse = {
type: 'success';
user: {
name: string;
email: string;
};
} | {
type: 'error';
message: string;
};
function UserDetails() {
const [response, setResponse] = useState<UserResponse | null>(null);
useEffect(() => {
// Simulate fetching user data or handling an error
const fetchData = async () => {
try {
const userData = await fetchUserAPI(); // Replace with actual API call
setResponse({ type: 'success', user: userData });
} catch (error) {
setResponse({ type: 'error', message: error.message });
}
};
fetchData();
}, []);
return (
<div>
{response ? (
<div>
{response.type === 'success' ? (
<>
<h2>User Details</h2>
<p>Name: {response.user.name}</p>
<p>Email: {response.user.email}</p>
</>
) : (
<p>Error: {response.message}</p>
)}
</div>
) : (
<p>Loading user data...</p>
)}
</div>
);
}
In this example, we have two types representing a successful and failed API response. We combine them into a single type Response
using the pipe operator (|
). TypeScript ensures that properties like user.name
and message
are only accessed when their respective types are guaranteed, preventing potential errors. When type is success then we can access user object with its value name and email.
Note: We combine different types into a single type using the pipe operator (`|`)
Different examples of discriminated union
Here are some glimse of other discrcimianted union types
type SuccessMessage = {
type: 'success';
content: string;
};
type ErrorMessage = {
type: 'error';
content: string;
};
type WarningMessage = {
type: 'warning';
content: string;
... can have addition
};
type Message = SuccessMessage | ErrorMessage | WarningMessage;
Issues in Infobox we conditional require one of the two modes, we need severity attribute only when we have modeset to warning
Without using severity propreties we will get error. To fixed it we have two solution
It is a way of combining two or more types into a single type, based on a common property or key. Here are a few examples of how you can use Discriminated Unions in React with TypeScript:
Uses case of Discriminated union in funtion argument
Lets first used it in function
interface Success {
success: true;
data: string;
}
interface Failure {
success: false;
message: string;
}
type Response = Success | Failure;
function handleResponse(response: Response) {
if (response.success) {
console.log(response.data);
} else {
console.error(response.message);
}
}
const successResponse: Success = { success: true, data: "Hello, world!" };
const failureResponse: Failure = { success: false, message: "Something went wrong." };
handleResponse(successResponse); // Output: Hello, world!
handleResponse(failureResponse); // Output: Something went wrong.
Example 3: Discriminated union react conditional props
We can also used descriminated union to used conditional props in react functional component. In TypeScript, when using Discriminated Unions, it’s important to handle all possible types in the union. Here is an example
type ComponentPropsBase = {
commonProp: string;
};
type WithMessageProps = ComponentPropsBase & {
hasData: true;
message: string;
};
type WithoutMessageProps = ComponentPropsBase & {
hasData: false;
noDataProp: string; // Additional prop specific to the case where there is no data
};
type MyComponentProps = WithMessageProps | WithoutMessageProps;
const MyComponent: React.FC<MyComponentProps> = (props) => {
if (props.hasData) {
return (
<div>
<p>{props.commonProp}</p>
<p>{props.message}</p>
</div>
);
} else {
return (
<div>
<p>{props.commonProp}</p>
<p>{props.noDataProp}</p> {/* Render additional prop specific to the case where there is no data */}
</div>
);
}
};
We havenoDataProp
to the WithoutMessageProps
type to represent additional props specific to the case where there is no data. Then, in the MyComponent
function component
Example 4 Discriminated union react props
Here we have component to show message Alert or Cautious . The cautious props type have extra data called level, now we can called this component in parent component where we can have it props value mode, children are compulsary and level is optional. We need level in props only when we have mode is caution.
import { ReactNode } from "react";
type AlertBoxProps = {
mode: "alert";
children: ReactNode;
};
type CautionBoxProps = {
mode: "caution";
level: "low" | "medium" | "high";
children: ReactNode;
};
type MessageBoxProps = AlertBoxProps | CautionBoxProps;
//Now create third props type what is either one of this two
function MessageBox(props: MessageBoxProps) {
const { children, mode } = props;
//Otherwise error if we descructure the pros as level is not exist in some cases
if (mode === "alert") {
return (
<aside className="messagebox messagebox-alert">
<p>{children}</p>
</aside>
);
}
if (mode === "caution") {
const { level } = props as CautionBoxProps;
return (
<aside className={`messagebox messagebox-caution caution--${level}`}>
<h2>Caution</h2>
<p>{children}</p>
</aside>
);
}
throw new Error(`Unsupported mode: ${mode}`);
}
export default MessageBox;
Here are fews case, where we can use this in react project.
- Data Fetching: Handling different response types from APIs (success, error, loading).
- Form Validation: Representing valid or invalid form states with distinct properties.
- Component State: Modeling different component variations based on internal states or props.
- Routing: Defining different route configurations for different page types.