0

I want to be able to generate types for a dynamic key (that will be of type number). I have two very similar types:

type UseCalculatePayments = () => {
  totalPayments: number;
  aggregate: number;
  condition: boolean;
};
type UseCalculateCommissions = () => {
  totalCommissions: number;
  aggregate: number;
  condition: boolean;
};

The only difference is the first key-name (totalPayments vs totalCommissions). So I wanted to create a type for them both:

type UseDynamicTransactions = (type: string) => {
  [key: string]: number; // I want this type key to dynamically be called `total${type}`, so it would accept, for example, totalPayments, totalCommissions or totalSales, all of type number
  aggregate: number;
  condition: boolean;
};

But now the condition has a type error of Property 'condition' of type 'boolean' is not assignable to 'string' index type 'number' as it presumably expects all the keys to be a number.

So my question is: how can the dynamic [key: string]: number type be corrected? I tried to use a Record type too, but this failed.

To give you some better context, see:

The stackblitz demo: https://stackblitz.com/edit/react-starter-typescript-h3ckn4?file=App.tsx

The code:

export const App = () => {
  // const { totalPayments, aggregate, condition } = useCalculatePayments();
  const { totalPayments, aggregate, condition } =
    useDynamicTransactions('Payments');
  const {
    totalCommissions,
    aggregate: aggregateCommissions,
    condition: commissionCondition,
    // } = useCalculateCommissions();
  } = useDynamicTransactions('Commissions');
  const {
    totalSales,
    aggregate: aggregateSales,
    condition: saleCondition,
  } = useDynamicTransactions('Sales');

  return (
    <Fragment>
      <div>
        <h2>Payments</h2>
        <p>Aggregate: {aggregate}</p>
        <p>Total: {totalPayments}</p>
        <p>Condition: {condition.toString()}</p>
      </div>
      <div>
        <h2>Commissions</h2>
        <p>Aggregate: {aggregateCommissions}</p>
        <p>Total: {totalCommissions}</p>
        <p>Condition: {commissionCondition.toString()}</p>
      </div>
      <div>
        <h2>Sales</h2>
        <p>Aggregate: {aggregateSales}</p>
        <p>Total: {totalSales}</p>
        <p>Condition: {saleCondition.toString()}</p>
      </div>
    </Fragment>
  );
};

type UseDynamicTransactions = (type: string) => {
  [key: string]: number; // I want this type key to dynamically be called `total${type}`, so it would accept, for example, totalPayments, totalCommissions or totalSales, all of type number
  aggregate: number;
  condition: boolean;
};
const useDynamicTransactions: UseDynamicTransactions = (type) => {
  // does some calcs etc...
  return { [`total${type}`]: 6, aggregate: 40, condition: false };
};

type UseCalculatePayments = () => {
  totalPayments: number;
  aggregate: number;
  condition: boolean;
};
type UseCalculateCommissions = () => {
  totalCommissions: number;
  aggregate: number;
  condition: boolean;
};

const useCalculatePayments = () => {
  return { totalPayments: 5, aggregate: 100, condition: false };
};
const useCalculateCommissions = () => {
  return { totalCommissions: 3, aggregate: 20, condition: false };
};

Also, is an interface or a type better here? Thanks for any help here.

2
  • Does this approach meet your needs? If so, I can write up an answer explaining; if not, what am I missing? Commented Oct 14, 2022 at 20:26
  • Looks like a use case for typescript generics Commented Oct 14, 2022 at 21:01

1 Answer 1

1

You can make use of mapped types and template literal types to create an object with the key you want.

Then combine it with the additional properties you want.

type TotalKey<Str extends string> = {
  [Prop in keyof "total" as `total${Str}`]: number;
};

type UseCalculate<T extends string> = () => {
    aggregate: number;
    condition: boolean;
} & TotalKey<T>;

type UseCalculateCommissions = UseCalculate<"Commissions">;
type UseCalculatePayments = UseCalculate<"Payments">;

const foo: UseCalculateCommissions = () => ({
    aggregate: 5,
    condition: true,
    totalCommissions: 5,
})

const bar: UseCalculatePayments = () => ({
    aggregate: 5,
    condition: true,
    totalPayments: 5,
})

To use it for your useDynamicTransaction you just need to specify the return type using this generic type.

function useDynamicTransactions<T extends string>(name: T): UseCalculate<T> {
    return {
        aggregate: 5,
        condition: true,
        [`total${name}`]: 5,
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Could you show how to pass in the type?
Maybe try to actually explain what you mean. Pass in what type where?
Sorry, I meant inside the function signature, e.g. useDynamicTransactions('Commissions') or useDynamicTransactions('Sales') as per the demo

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.