Attempting to create a TypeScript + React component that can accept multiple types of props, but running into the issue where only the common prop is accessible

I am looking to create a component named Foo that can accept two different sets of props:

  1. Foo({a: 'a'})
  2. Foo({a: 'a', b: 'b', c:'c'})

The prop {a: 'a'} is mandatory.

These scenarios should be considered invalid:

Foo({a: 'a', b: 'b'}) // ❌
Foo({a: 'a', c: 'c'}) // ❌

Below is the approach I have tried.


type BaseProps = {
  a: "a";
};

type VariantPrps = {
  b: "b";
  c: "c";
} & BaseProps;

function Foo(props: BaseProps): React.ReactNode;
function Foo(props: VariantPrps): React.ReactNode;

function Foo(props: BaseProps | VariantPrps) {
  // Only able to access `props.a` here
  return <span>{props.a}</span>;
}



// sample usage
Foo({a: 'a'}) // ✅
Foo({a: 'a', b: 'b', c:'c'}) // ✅

Foo({a: 'a', b: 'b'}) // ❌
Foo({a: 'a', c: 'c'}) // ❌

While it partially works, inside the Foo component, I can only access the prop a, not b and c. Ideally, I would like to be able to access b and c within the component and determine if they exist or not before using them. Is there a way to achieve this?

Answer №1

Here is an interesting scenario to consider:

import React from 'react'

// credit: https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
    T extends any
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

type BaseProps = {
    a: "a";
};

type VariantPrps = {
    b: "b";
    c: "c";
} & BaseProps;

function Foo(props: BaseProps): React.ReactNode;
function Foo(props: VariantPrps): React.ReactNode;

function Foo(props: StrictUnion<BaseProps | VariantPrps>) {
    props.a // "a"
    props.b // "b" | undefined
    props.c // "c" | undefined
    // Can only access `props.a` within this function
    return <span>{props.a}</span>;
}



// usage
Foo({ a: 'a' }) // ✅
Foo({ a: 'a', b: 'b', c: 'c' }) // ✅

Foo({ a: 'a', b: 'b' }) // ❌
Foo({ a: 'a', c: 'c' }) // ❌

Try it on Playground

In this example, only the common property a can be used because it exists in both types. For more information, refer to the documentation.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I am encountering an issue where my TSX import is being declared but not read when I attempt to pass it to the Jest render method. Can anyone explain

My Jest test file includes a simple import of a TSX component in my nextjs 13 project. However, when I try to use the component as a TSX tag, an error occurs: "'Properties' refers to a value, but is being used as a type here. Did you mean ...

Obtaining response object when encountering 401 error in AngularJS

I am currently working with Angular 1.6.4, Express 4.15.2, and express-session. My goal is to identify whether a user is unauthorized to access a specific route by checking for the existence of the req.session.user parameter. If the user is not authorized, ...

Setting a default value automatically - Prisma & Next.js version 13.4

Is there a way to set the default category for a new movie added through a form to 'all-films' with ID? Any suggestions on how to achieve this would be greatly appreciated. Below are snippets of the relevant code: new-film.tsx --> const form ...

The Foundation 6 Zurb Template is not compatible for offline use

After successfully installing Foundation 6 Zurb Template via the cli, I encountered no issues. I then added the missing babel install and everything worked fine online. However, BrowserSync does not seem to work offline. Upon initiating watch, I receive a ...

Enhancing webpage styles with AngularJS

Just starting out with AngularJS and eager to get the best approach to tackle this challenge. Describing my dilemma: I have an anchor element that, when clicked, needs to toggle between classes "show-all" and "hide-all" while also updating the styling of ...

Error: The variable "Set" cannot be found in the react.js code, specifically in Safari browser

An issue has been identified in Safari where a ReferenceError is thrown: "Can't find variable: Set" when using react.js. This error occurs only in Safari, while all other browsers work perfectly fine. The error message points to the main.js file which ...

Is it possible to switch the summernote editor between airmode and toolbar mode?

Currently, I am working on creating a report editor that displays only one toolbar when multiple summernote WYSIWYG editor sections are used. My solution involves having the first section as a full editor and the other section in airmode. Below is the HTM ...

Transform input string containing newline characters into separate paragraphs

I utilize Contentful CMS for content management and fetch the content through their API. When the content is fetched, it comes in as a JSON object. One of the keys within this object pertains to the main text block for the entry I am retrieving. This stri ...

Exploring Reactive Programming with RxJS and organizing data into individual streams

As I delve deeper into working reactively with Angular 15 and RxJS observables for a UI component, my focus lies on subscribing to data solely within the component template (html). The data is fetched from an external system through a service. However, a c ...

I'm a beginner with Angularjs and I attempted to populate multiple JSON values, but unfortunately, it didn't work as expected

<div ng-controller="studentController" ng-repeat = "student in students | unique = 'RollNo' " > <table class="profile-info"> <tr> <th>Enrollment Number</th> <td> ...

Compiling TypeScript files with an incorrect path when importing, appending "index" at the end of the @angular/material library

I'm currently working on creating a library to collect and distribute a series of Angular components across various projects, with a dependency on angular/material2. My objective is to eventually publish it on npm. However, I've encountered an i ...

Leverage socket.io in various routes within a node.js application

In my Node.js application, I have various routes defined in the router.js file. Now, I want to implement socket.io in every route to enable real-time communication between my Node.js and React.js applications. However, the structure of my Node.js applicati ...

AngularJS variable assignment with HTTP GET operation

The angular filter I have set up is functioning perfectly: categorieFilter = angular.module("categorieFilter", []) categorieFilter.controller("catFilter", ["$scope", "store", function($scope, store){ $scope.search = ""; $scope.products = []; $ ...

Playing with Data in AG-Grid using Javascript

I am working on implementing data display using AG Grid with an AJAX call, but I am facing an issue where no data is being shown in the grid. Even though my AJAX call seems to be functioning correctly and returning the desired object List, the grid itsel ...

Issue - The 'defaultValue' is failing to load the state value, and the 'value' is not being updated when changed

My current setup involves an input field in my MovieInput.tsx file: <input id="inputMovieTitle" type="text" onChange={ e => titleHandleChange(e) } value={ getTitle() }> </input> This is how the titleHandleChange function ...

Error occurred during npm build with Browserify: Module not found

When I run npm build with the following command: "build": "browserify -t [ babelify --presets [ es2015 react ] ] app/assets/app.jsx -o public/javascripts/app.js" I encounter the error message below: Error: Cannot find module 'components/maininput.j ...

Tips for displaying a div near the cursor's location when hovering in React JS

Looking for a way to show a hidden div at cursor position when hovering on Text item1 or item2. Check out the sample GIF animation in this Link My attempt using Jquery code inside React resulted in an error: $(".text-item").mouseenter(function ( ...

Firefox is mistakenly interpreting a pasted image from the clipboard as a string instead of a file, causing

I am facing an issue where I am attempting to extract images from a contenteditable div using the paste event. The code works perfectly in Chrome but does not function as expected in Firefox. I have implemented the following code: $(window).on("paste& ...

Efficiently loading Ionic 3 components within a tab with lazy-loading functionality

Need help with adding a new tab to your project using lazy-loading? You can utilize the @IonicPage decorator for setting up a page as the root of a tab. To implement this, create a new page: // module import { NgModule } from '@angular/core'; ...

When using Inertia.js with Typescript, an issue arises where the argument types {} and InertiaFormProps{} are not compatible with the parameter type Partial<VisitOptions> or undefined

I set up a brand new Laravel project and integrated Laravel Breeze along with Typescript support. After creating a form (using useForm()) and utilizing the .post() method with one of the options selected (such as onFinish: () =>), I encountered the fol ...