I have a case when API returns 2 different types of history logs.
The interface for the operation was successful.
export interface IsyncSuccess {
deleted_count: number,
new_count: number
}
And interface for operation failed
export interface IsyncFailed {
error_message: string
}
Interface for entire log.
export interface logType {
type: "SYNC_SUCCESS" | "SYNC_FAILED" // for simplicity here are strings, normally its enum
data: IsyncSuccess | IsyncFailed
}
And in table row I have conditioning like that
const HistoryTableRow: React.FC<{ log: logType }> = React.memo(({ log }) => {
const getLog = () => {
if (log.type === "SYNC_SUCCESS") {
const data = log.data as IsyncSuccess
return <>
<Td>
{data.new_count}
</Td>
<Td>
{data.deleted_count}
</Td>
</>
} else {
const data = log.data as IsyncFailed
return <>
<Td colspan={2}>
{data.error_message}
</Td>
</>
}
}
return (
<TbodyTr>
{getLog()}
</TbodyTr>
)
}, (prevProps, nextProps) => (true))
It works. Is good practice conditioning interfaces? Is there more elegant solution for this case, when API returns different log types?
CodePudding user response:
A discriminated union type
may be more appropriate here as a way of combining your possible states in a way that will also give you type-safety/intellisense. For example:
export interface ISyncSuccess {
deleted_count: number,
new_count: number
}
export interface ISyncFailed {
error_message: string
}
interface ILogTypeSuccess {
type: "SYNC_SUCCESS"
data: ISyncSuccess
}
interface ILogTypeError {
type: "SYNC_ERROR"
data: ISyncFailed
}
type LogType = ILogTypeSuccess | ILogTypeError
You can then use the type
property to "discriminate" and infer what type the data
property will be (with the original approach data
would always be of type ISyncSuccess | ISyncFailed
regardless of the type of type
)
function checkLogType(logType: LogType) {
if (logType.type === "SYNC_SUCCESS") {
logType.data // ISyncSuccess
}
else {
logType.data // ISyncError
}
}
const validSuccess: LogType = {
type: "SYNC_SUCCESS",
data: {
deleted_count: 0,
new_count: 0,
}
}
const invalidSuccess: LogType = {
type: "SYNC_SUCCESS",
data: {
error_message: 'some message' // Error: Object literal may only specify known properties, and 'error_message' does not exist in type 'ISyncSuccess'.(2322)
}
}
It is also easy to add new states in without losing that benefit, e.g.
interface ISyncLoading {
loading_message: string;
}
interface ILogTypeLoading {
type: "SYNC_LOADING"
data: ISyncLoading
}
type LogType = ILogTypeSuccess | ILogTypeError | ILogTypeLoading
const validLoading: LogType = {
type: "SYNC_LOADING",
data: {
loading_message: 'some message'
}
}