Alwaysblue
Alwaysblue

Reputation: 11900

How do you use spread operator to overwrite properties in typescript

Consider this example

interface additonalProperties {
  backgroundColor: string,
  color: string,
  [key: string]: string
}

class StyleObjectMaker implements StyleObjectMaker {
  constructor(className: string, additonalProperties: additonalProperties = {}) {
    this.key = `button`
    this.className = `button${className ? '-' + className : ''}`
    this.property = {
      backgroundColor: colors[className] || colors.default,
      color: colors.default,
      ...additonalProperties
    }
  }
}

In the above ts is complaining about

'backgroundColor' is specified more than once, so this usage will be overwritten

And same for

  color

Any idea how I can fix it? i.e how can I allow to overwrite

This is how my tsconfig looks

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "dist/ts-out",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "resolveJsonModule": true,
    "typeRoots": ["./node_modules/@types"]
  },
  "compileOnSave": true,
  "include": ["src", "index.ts"]
}

Upvotes: 13

Views: 17029

Answers (4)

Simon_Weaver
Simon_Weaver

Reputation: 146140

I had some defaults set like this that weren't a problem until Typescript 4.4 (possibly an earlier version):

Fails

    const formsValue = { 

        stateOrProvince: null, 
        stateCd: null, 
        address3: null, 
        phone: null, 

        ...addressFields 
    };

Shows error: Error TS2783: 'stateOrProvince' is specified more than once, so this usage will be overwritten. which makes sense.

The destination for this value is an Angular form which requires empty fields to be explicitly set. While normally addressFields would contain a value for every field (as specified by its type) I had some older customers who may have a cached value in their local storage without all these fields hence these defaults.

Fortunately the following seems to get around the error, and I think looks nicer anyway as it groups things together.

Works

    const formsValue = { 

        ...{
            stateOrProvince: null, 
            stateCd: null, 
            address3: null, 
            phone: null, 
        },

        ...addressFields 
    };

Not 100% sure why the compiler lets this through but it does :-)

Upvotes: 4

Seth Bro
Seth Bro

Reputation: 2555

Typescript's Partial utility type seems the best way to make this happen.

In your example, the constructor would be changed to:

constructor(className: string, additonalProperties: Partial<additonalProperties> = {})

Upvotes: 3

Federico Alecci
Federico Alecci

Reputation: 914

I don't know if there is a configuration for this. However, you could destructure additionalProperties to exclude color and backgroundColor:

class StyleObjectMaker implements StyleObjectMaker {
  constructor(className: string, additonalProperties: additonalProperties = {}) {
    const { backgroundColor, color, ...rest } = additionalProperties;
    this.key = `button`
    this.className = `button${className ? '-' + className : ''}`
    this.property = {
      backgroundColor: colors[className] || colors.default,
      color: colors.default,
      ...rest
    }
  }
}

If you want those props to be just default values, you can make use of Object.assign.

class StyleObjectMaker implements StyleObjectMaker {
  constructor(className: string, additonalProperties: additonalProperties = {}) {
    const finalProperties = Object.assign({}, {
        backgroundColor: colors[className] || colors.default,
        color: colors.default,
    }, additionalProperties);
    this.key = `button`
    this.className = `button${className ? '-' + className : ''}`
    this.property = finalProperties;
  }
}

Upvotes: 1

Nicholas Tower
Nicholas Tower

Reputation: 85122

Typescript is pointing out that these two lines are useless:

  backgroundColor: colors[className] || colors.default,
  color: colors.default,

You're setting these properties manually, but then you immediately spread over them, wiping them out. This is probably not what you meant to do. If you want these two values to trump what's found in additionalProperties, then switch the order:

this.property = {
  ...additonalProperties
  backgroundColor: colors[className] || colors.default,
  color: colors.default,
}

If the order is correct, but additionalProperties won't always have color and backgroundColor, then mark those as optional:

interface additonalProperties {
  backgroundColor?: string,
  color?: string,
  [key: string]: string
}

Or if it's behaving the way you want (ie, the properties are always getting overwritten, and that's intentional), then delete the useless lines of code.

Upvotes: 16

Related Questions