What is the best way to create a general getter function in Typescript that supports multiple variations?

My goal is to create a method that acts as a getter, with the option of taking a parameter. This getter should allow access to an object of type T, and return either the entire object or a specific property of that object.

The issue I am facing is defining the method in two different places - first in an interface and then in the actual implementation.

This is my proposed solution:


// The Getter interface defines multiple overloads
interface StoreGetter {
    <T>(): T;
    <T, K extends keyof T>(prop: K): T[K];
}

// The Store interface has a generic type 'T' and exposes the type along with its properties
interface Store<T> {
    get: StoreGetter;

    // Either of these work individually
    // get: <T>() => T;
    // get: <T, K extends keyof T>(prop: K) => T[K];
}

export function makeStore<T>(initial: T): Store<T> {
    let value: T = initial;

    // Overloads must be defined via function declaration
    // function get<T>(): T;
    // function get<T, K extends keyof T>(prop: K): T[K];
    function get(prop?: keyof T) {
        if (typeof prop !== 'undefined') {
            return value[prop];
        }
        return value;
    }

    return {
        get,
    };
}

const store = makeStore({
    text: '',
    items: [],
    num: 1
});

// Error: Argument of type '"text"' is not assignable to parameter of type 'never'.(2345)
store.get('text')

// Error: Object is of type 'unknown'.(2571)
store.get().

It appears that the conflicting definitions are causing issues.

I need help on how to define this method with overloads and have correct type inference for both calls.

Answer №1

After numerous unsuccessful tries, I finally stumbled upon a configuration that yields the desired conclusions:

interface StoreGetter<T> {
    (): T;
    <K extends keyof T>(props: K): T[K];
}

interface Store<T> {
    get: StoreGetter<T>;
    set: (val: any | T) => void;
}

export function createStore<T>(initial: T): Store<T> {
    let value: T = initial;
    let listeners: Function[] = [];

    function get(): T;
    function get<K extends keyof T>(prop: K): T[K];

    function get(prop?: keyof T): T | T[keyof T] {
        if (typeof prop !== 'undefined') {
            return value[prop];
        }
        return value;
    }

    return {
        get,
        set: (val: any) => {
            value = {
                ...value,
                ...val,
            };
            listeners.forEach(fn => fn(value));
        }
    };
}

const dataStore = createStore({
    text: '',
    items: [],
    num: 1
});

// Both methods work for type inference
dataStore.get('text').toUpperCase
dataStore.get().items

I am still eager to figure out a way to achieve this using an inline/anonymous function.

On a positive note, this method seamlessly integrates with a declarations file (e.g., store.d.ts), allowing for the utilization of a single declaration:

interface StoreGetter<T> {
  (): T;
  <K extends keyof T>(props: K): T[K];
}

interface Store<T> {
  get: StoreGetter<T>;
}

export function createStore<T>(initial: T): Store<T>;

export function useDataStore<T>(store: T, prop?: string): [T|any, (newState: T|any) => void];

Then, in a separate JS file:

const dataStore = createStore({
  keypresses: 0,
  text: '',
  arrows: [],
});

// Both are inferred:
dataStore.get('keypresses').toFixed
dataStore.get().arrows.push

This results in the expected annotations in VS code:

https://i.sstatic.net/73VFe.png

https://i.sstatic.net/Bk9UA.png

https://i.sstatic.net/YzLdy.png

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

What causes me to create components with incorrect paths?

Can someone assist me with creating a new component in the dynamic-print folder instead of it being created in the app? Thank you ...

Solving the issue of "_c is not defined" error in Vue functional component

I've been experimenting with creating functional components in Vue using the render method. Here's an example of how I attempted to do this: import Vue from "vue" const { render, staticRenderFns } = Vue.compile(`<div>Hello World</div&g ...

The getElementBy method is failing to pass the value in the document

Here is the javascript code snippet I am using: document.getElementById('district').value = dist.long_name "this line is passing the value" document.getElementById('city').value = route.long_name "this doesn&apos ...

What could be the reason for the jQuery not displaying JSON data in the console log?

When I put this code into a file named test.html: <html> <head> <title>Test</title> </head> <body> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script type="text/javascript"& ...

Utilize CSS to vertically align buttons

One of my current projects involves creating a panel with buttons organized in columns side by side, similar to the layout shown below: https://i.sstatic.net/ObAqw.png However, I am struggling to achieve this desired arrangement. Below is the code I hav ...

Encountering a problem with lazy loading of module routing paths. Issue arises when attempting to navigate to http://localhost:4200

AppLazyLoadingMoudle import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; const ROUTES : Routes = [ /* ProductModule (defined in the feature module) is loaded lazily when navigating ...

AJAX cannot be used with the current session

I am facing an issue with a directory containing files that are used only as modal. These PHP files have the following format: "modal.filename.php". Here is an example: "modal.user.php": <?php session_start(); $_SESSION['test'] = 1; echo & ...

Is it feasible to maintain a variable as a reference across views while utilizing ng-view?

I am facing a unique challenge: I have a webpage with two tabs that need to utilize ng-view from AngularJS. The twist is that both tabs must share the same variable, similar to referencing a variable in C# using the "ref" keyword. If you want to see an ex ...

What is the best way to combine objects that share the same id?

View Image: Displaying Image POST ID's https://i.stack.imgur.com/JO5OF.png I attempted to resolve this using jQuery each, but I am uncertain about the next steps. Check out my Code: jQuery(document).ready(function($) { var data, exampleData, , ...

Firefox having trouble loading ThreeJS

My current project involves showcasing 3D files online using ThreeJS. While it operates smoothly on Chrome and MS Edge, I encountered a hitch on Firefox: Uncaught TypeError: Error resolving module specifier “three”. Relative module specifiers must star ...

Typescript Syntax for Inferring Types based on kind

I'm struggling to write proper TypeScript syntax for strict type inference in the following scenarios: Ensuring that the compiler correctly reports any missing switch/case options Confirming that the returned value matches the input kind by type typ ...

What potential issue could result in a property length of null error while working with a Material Data Table?

I'm experiencing a similar issue as the one discussed in this post, but none of the suggestions there have resolved my problem, and my scenario has some differences. In my case, a parent component is assigning an array to a child component's inp ...

When using react-chartjs-2 with NextJS and typescript to create a bar chart, an error occurred: TypeError - Attempting to access properties of an undefined object (specifically 'map

Encountering a console error stating TypeError: Cannot read properties of undefined (reading 'map') . As I am new to NextJS & Typescript, I am attempting to build a simple bar chart. Error message: react-dom.development.js?03cb:22839 Uncaugh ...

There is an issue with transmitting data from an HTML page to the server and then retrieving data from the server

Upon running this code, I encountered an issue with sending and receiving data. I kindly request assistance in correcting my code. Highlighted below is the server-side code var http = require("http") ; var fs = require("fs") ; http.createServer(function( ...

The function initiates without delay upon meeting the specified condition

I am looking to trigger a function automatically upon certain dynamically changing conditions in my JavaScript code. I attempted using the document.body.onload method, but it did not work as expected. document.body.onload = myFunction() function myFunct ...

Implementing jQuery form validator post anti-SPAM verification?

I am facing what seems like a straightforward JavaScript issue, but my knowledge in this area is still limited. Following a successful implementation of basic anti-SPAM feature that asks the user to solve a simple math problem, how can I integrate jQuery& ...

Alert displayed on console during transition from MaterialUI lab

Whenever I try to run my MUI application, an error pops up in the console It seems you may have forgotten to include the ref parameter in your forwardRef render function. import { LoadingButton } from "@mui/lab"; const LoadData = ({loading,sig ...

Is it possible to create a combined header/declaration file in Golang within a single file?

My goal is to automatically generate Golang declaration files based on .json data. While with TypeScript I can consolidate types/declarations in one file using namespaces, it seems more complex to achieve the same with Golang packages and namespacing. In ...

What is causing this setInterval function to run repeatedly?

Below is the code snippet from my Vue application: mounted: function () { this.timer = setInterval(async () => { if (this.progress >= 1) { this.progress = 1 clearInterval(this.timer) } console.log('update& ...

What is the process for accessing a website using Java programming language?

Currently, I have a jar file up for sale that requires users to sign up on a particular website in order to download it. My issue lies in wanting to verify if purchasers have a valid login for the site when they run the jar file. Despite my attempts with h ...