2

I have recently started using react with typescript and it seems like something odd is happening when I destructure props with optional / default values. For example take this function to generate a random id:

interface GenKeyIdProps {
  length?: number;
}

/**
 * Generate a random id (mainly for use with rendering lists of components.
 * genKeyId convert Math.random to base 36 (numbers + letters), and grab the first ${lenght} characters after the decimal.
 * @param length (optional) specify how many characters the id should be. default is 5.
 * @returns a string with number and letters of specified length.
 */
export const genKeyId = ({ length = 5 }: GenKeyIdProps) => Math.random().toString(36).substr(2, length);

The problem is when I try to call it:

import { genKeyId } from '../../../helpers';

interface TextWrapperProps {
  textLines: string[];
}

const TextWrapper = ({ textLines }: TextWrapperProps) => {

  return textLines.map(line => (<div key={genKeyId()}>{line}</div>));

};

export default TextWrapper;

if I do not pass at least an empty object to genKeyId I get the following error: Expected 1 arguments, but got 0.ts(2554).

Why do I need to pass an empty object? Is my GenKeyIdProps interface specifying the props need to be part of an object? I am a bit confused about this.

1 Answer 1

2

Is my GenKeyIdProps interface specifying the props need to be part of an object?

Yes, you've defined GenKeyIdProps as an object. All interface definitions are objects. Your GenKeyIdProps is roughly equivalent to:

type GenKeyIdProps = {
  length?: number;
};

That is, an object with an optional length property of type number.

There's no need for genKeyId to accept an object, though; it's not a component function, it's just a utility function. You could define it to accept length directly by removing the destructuring:

export const genKeyId = (length = 5) => Math.random().toString(36).substr(2, length);

Alternatively, if you do want it to accept an object but you want it to be able to be called without one, you can default the parameter you're destructuring:

export const genKeyId = ({ length = 5 }: GenKeyIdProps = {}) => Math.random().toString(36).substr(2, length);
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^

Now you can call genKeyId A) with an object that has a length; B) with an object that doesn't have a length; and C) with no argument at all.

It's up to you whether you want getKeyId to accept a number or an object with a length property.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks T.J. I want it to accept length as a number but I still want to type the arguments with an interface. Is that possible?
@cbutler - No, because interfaces define object types, and a number isn't an object type. That seems like an X/Y problem. Why do you want to define the parameters with an interface? (For completeness: You can do it indirectly, by defining a type for genKeyId [the function] using interface and using that when declaring the constant, like this, but that would be fairly unusual if getKeyId is the only function you need to have this interface, and even then you'd usually use type for it.)
Thanks for X/Y reference, very interesting :) Your explanation makes more sense when I think about how to use the function API. For my use case I think I will use your last suggestion (= {}) as I want to use the default in most cases.
@cbutler - Just for completeness, the version not using GenKeyIdProps at all (the one under "There's no need for genKeyId to accept an object..." above) also can be called with no arguments, because length is defaulted. So genKeyId() and genKeyId(5) do the same thing.

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.