Interface vs Type

When you’re not sure which one to use, always go with interface until you have a reason to use type.

type isLoading = boolean
type Theme = "dark" | "light"
type Lang = "en" | "fr"

Function Components

interface AppProps {
  title: string
  children: ReactNode
}
 
// easiest way
const App = ({ title, children }: AppProps) => { ... }
 
// not recommended
const App: FC<AppProps> = ({ title, children }) => { ... }
 
// when there are a few props
const App = ({ message }: { message: string }) => { ... }

Custom HTML Components

type InputProps = ComponentProps<'input'>
 
const Input = forwardRef(
  (props: InputProps, ref: Ref<HTMLInputElement>) => {
    return <input ref={ref} {...props} className={...} />
  }
)

Use text value instead of children:

type TagProps = {
  variant?: "solid" | "outlined"
  text: string
} & Omit<React.ComponentProps<"span">, "children">

useState

// infer types
const [enabled, setEnabled] = useState(false)
 
const [title, setTitle] = useState<string | null>(null)
 
type Status = "idle" | "loading" | "success" | "error"
const [status, setStatus] = useState<Status>("idle")

useReducer

const initialTasks: TaskItem[] = []
 
enum ACTIONS {
  ADD = "added",
  CHANGE = "changed",
  DELETE = "deleted",
}
 
type ActionType =
  | { type: ACTIONS.ADD; id: string; text: string }
  | { type: ACTIONS.CHANGE; task: TaskItem }
  | { type: ACTIONS.DELETE; id: string }
 
function tasksReducer(tasks: typeof initialTasks, action: ActionType) {
  switch (action.type) {
    case ACTIONS.ADD: {
      return [...tasks, { id: action.id, text: action.text, done: false }]
    }
    case ACTIONS.CHANGE: {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task
        } else {
          return t
        }
      })
    }
    case ACTIONS.DELETE: {
      return tasks.filter((t) => t.id !== action.id)
    }
    default: {
      throw Error("Unknown Type")
    }
  }
}
 
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks)

useContext

const TasksContext = createContext<TaskItem[]>([])
const TasksDispatchContext = createContext<Dispatch<ActionType>>(() => {})
 
export function useTasks() {
  return useContext(TasksContext)
}
 
export function useTasksDispatch() {
  return useContext(TasksDispatchContext)
}
 
export function TaskProvider({ children }: { children: ReactNode }) {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks)
 
  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>{children}</TasksDispatchContext.Provider>
    </TasksContext.Provider>
  )
}

Thanks