Typescript generic parameter typing for curried function

248 views Asked by At

Trying to type a curried function which takes a functor first and then an object

type TestType = {
  x: number;
  y: number;
  hello: string;
};

type CordinateType = {
  data: TestType;
  id: string;
};

const transform = <T extends object, K extends keyof T>(
  fntor: (val: T[K]) => any
) => (obj: T) =>
  Object.entries(obj).reduce((acc, [k, v]) => ({ ...acc, [k]: fntor(v) }), {});

const testData: CordinateType = {
  id: "uuid-222-uuid",
  data: {
    x: -3,
    y: 2,
    hello: "test"
  }
};

const positive = (x) => (Number.isInteger(x) ? Math.abs(x) : x);

const result = { ...testData, data: transform(positive)(testData) };
console.log("result", result);

Transform is simple function that takes functor and maps over an object.

The main issue is that type of result.data is {}, instead it should be TestType

Edit lucid-mopsa-pqudl

This is sample react app, the setState hook function is not able to deduce proper type

Edit lucid-mopsa-pqudl

1

There are 1 answers

5
Ramesh Reddy On

Update

You can pass one of the generic parameters (T). Note that a default value needs to be used for the second parameter because we either let typescript figure out the parameters or pass them explicitly.

import "./styles.css";
import React, { useState, useCallback } from "react";

type PointType = {
  x: number;
  y: number;
  comment: string;
};

type CordinateType = {
  data: PointType;
  id: string;
};

const transform = <T extends object, K extends keyof T = keyof T>(
  fntor: (val: T[K]) => any
) => (obj: T) =>
  Object.entries(obj).reduce(
    (acc, [k, v]) => ({ ...acc, [k]: fntor(v) }),
    {}
  ) as T;
const positive = (x: any) => (Number.isInteger(x) ? Math.abs(x) : x);

export default function App() {
  const [cordinate, setCordinate] = useState<CordinateType>({
    id: "uuid-xxx-uuid",
    data: {
      x: 1,
      y: -2,
      comment: "initial-point"
    }
  });

  const updateCoord = useCallback(() => {
    // setCordinate((old) => ({ ...old, data: old.data })); // this works type
    setCordinate((old) => ({
      ...old,
      data: transform<PointType>(positive)(old.data)
    }));
  }, []);

  return (
    <div className="App">
      <h1>Hello Cordinate</h1>

      <div>
        <pre>{JSON.stringify(cordinate.data)}</pre>
      </div>

      <button onClick={updateCoord}>Update</button>
    </div>
  );
}

Edit elastic-bash-s6m6h


fntor: (val: K) => K

The type of fntor is incorrect because val isn't a key of the object, it's one of the values.

Here's the fixed code:

Edit lucid-mopsa-pqudl

const transform = <T extends object, K extends keyof T>(
  fntor: (val: T[K]) => any
) => (obj: T) =>
  Object.entries(obj).reduce((acc, [k, v]) => ({ ...acc, [k]: fntor(v) }), {});

const testData: Record<string, any> = {
  x: -3,
  y: 2,
  hello: "test"
};

const positive = (x: any) => (Number.isInteger(x) ? Math.abs(x) : x);

const result = transform(positive)(testData);
console.log("result", result);