2

I want a function to take partial input. However, it contains a variable named style whose values can only be outline or fill like:

export type TrafficSignalStyle = 'outline' | 'fill'
let style: TrafficSignalStyle

But the problem is when I do Partial<TrafficSignalStyle>, it converts TrafficSignalStyle to 'outline' | 'fill' | undefined which is not what I want.

I want a single function that takes 3 inputs namely style, colors & position & changes only whatever input is passed. So if I pass style like updateTrafficSignal({ style: e.target.value as TrafficSignalStyle }) then it keeps colors & position the same as it were before.

Here's what the related files look like:

types.ts

export type TrafficSignalButtons = {
    close: string
    minimize: string
    maximize: string
}

export type TrafficSignalPosition = {
    close: Point
    minimize: Point
    maximize: Point
}

export type TrafficSignalStyle = 'outline' | 'fill'

export type TrafficSignal = {
    style: TrafficSignalStyle
    colors: Array<TrafficSignalButtons>
    position: TrafficSignalPosition
}

export interface IFrameItStore {
    trafficSignal: TrafficSignal
    updateTrafficSignal(trafficSignal: Partial<TrafficSignal>): void
}

FrameItStore.ts

import { action, makeObservable, observable } from 'mobx'

import type { IFrameItStore, TrafficSignal } from './types'

export class FrameItStore implements IFrameItStore {
  trafficSignal: TrafficSignal = {
    style: 'outline',
    colors: [
      {
        close: '#EF4444',
        minimize: '#FBBE25',
        maximize: '#49DE80',
      },
      {
        close: 'black',
        minimize: 'blue',
        maximize: 'orange',
      },
    ],
    position: {
      close: { x: 100 + 20, y: 20 },
      minimize: { x: 100 + 2 * 20, y: 20 },
      maximize: { x: 100 + 3 * 20, y: 20 },
    },
  }

  constructor() {
    makeObservable(this, {
      trafficSignal: observable,
      updateTrafficSignal: action.bound,
    })
  }

  updateTrafficSignal(trafficSignal: Partial<TrafficSignal>) {
    if (trafficSignal.style) this.trafficSignal.style = trafficSignal.style
    if (trafficSignal.colors) this.trafficSignal.colors = trafficSignal.colors
    if (trafficSignal.position) this.trafficSignal.position = trafficSignal.position
  }
}

Options.tsx

import * as React from 'react'
import { observer } from 'mobx-react'

import { useFrameItStore } from './store'
import type { Padding, TrafficSignal, TrafficSignalStyle } from './types'

interface IProps {
    className?: string
}

export const Options = observer((props: IProps) => {
    const frameItStore = useFrameItStore()

    const trafficSignal: TrafficSignal = frameItStore.trafficSignal
    const padding: Padding = frameItStore.padding

    const updateTrafficSignal: (trafficSignal: Partial<TrafficSignal>) => void =
        frameItStore.updateTrafficSignal
    const updatePadding: (padding: Padding) => void = frameItStore.updatePadding

    return (
        <div>
            <h2>Traffic Signal Style:</h2>
            <select
                value={trafficSignal.style}
                onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
                    const style = e.target.value as TrafficSignalStyle
                    updateTrafficSignal({ style })
                }}
            >
                <option value="outline">Outline</option>
                <option value="fill">Fill</option>
            </select>
        </div>
    )
})

However, I get an error saying

  TS2322: Type '(trafficSignal: TrafficSignal) => void' is not assignable to type '(trafficSignal: Partial<TrafficSignal>) => void'.

Types of parameters 'trafficSignal' and 'trafficSignal' are incompatible. Type 'Partial' is not assignable to type 'TrafficSignal'. Types of property 'style' are incompatible. Type '"outline" | "fill" | undefined' is not assignable to type 'TrafficSignalStyle'. Type 'undefined' is not assignable to type 'TrafficSignalStyle'.

Which makes sense but how do I pass in only 1 parameter & have that change only that parmeter & not the others.

If I remove Partial then I get

Argument of type '{ style: TrafficSignalStyle; }' is not assignable to parameter of type 'TrafficSignal'. Type '{ style: TrafficSignalStyle; }' is missing the following properties from type 'TrafficSignal': colors, position

Which also makes sense. But still doesn't solve my problem.

I want a single function to change all 3 params & only 1 will be passed at a time.

How do I do it?

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163
  • From the code you have provided, I can't find exactly where the static typechecking error would occur. Please double check your type declarations. If you could find which line of code the error is called, you should be able to solve the problem quite easily. Either way, your approach isn't too bad, it should achieve what you want. – 98sean98 Dec 30 '20 at 06:33
  • @98sean98 i already found the answer & was typing it before you typed the comment. anyways its below :) – deadcoder0904 Dec 30 '20 at 06:34
  • Btw, I don't think it's necessary to re-declare the same types when you use `frameItStore` in a component. Typescript should be able to infer them. `const updateTrafficSignal: (trafficSignal: Partial) => void = frameItStore.updateTrafficSignal` is not needed, just do `const updateTrafficSignal = frameItStore.updateTrafficSignal`. – 98sean98 Dec 30 '20 at 06:35
  • Ok great. Have fun with typescript! – 98sean98 Dec 30 '20 at 06:35
  • @98sean98 cool that's a great idea. i thought i'd be explicit but the lesser the code, the better. i was currently doing `IFrameItStore["updateTrafficSignal"]` but I guess I shouldn't need that either :) – deadcoder0904 Dec 30 '20 at 06:37
  • 1
    Typescript is great, and most of the time very smart. So declaring types once, and reusing it through inference is Typescript's greatest advantage. Being too verbose just takes away the fun. – 98sean98 Dec 30 '20 at 06:38

1 Answers1

0

As stated here, there is no such thing available in TypeScript so I passed the params without destructuring & made them optional & it worked.

Here are the changes I made:

types.ts

export interface IFrameItStore {
    updateTrafficSignal(
        style?: TrafficSignalStyle,
        colors?: Array<TrafficSignalButtons>,
        position?: TrafficSignalPosition
    ): void
}

FrameItStore.ts

import { action, makeObservable, observable } from 'mobx'
import type {
  IFrameItStore,
  TrafficSignal,
  TrafficSignalButtons,
  TrafficSignalPosition,
  TrafficSignalStyle,
} from './types'

export class FrameItStore implements IFrameItStore {
  trafficSignal: TrafficSignal = {
    style: 'outline',
    colors: [
      {
        close: '#EF4444',
        minimize: '#FBBE25',
        maximize: '#49DE80',
      },
      {
        close: 'black',
        minimize: 'blue',
        maximize: 'orange',
      },
    ],
    position: {
      close: { x: 100 + 20, y: 20 },
      minimize: { x: 100 + 2 * 20, y: 20 },
      maximize: { x: 100 + 3 * 20, y: 20 },
    },
  }

  constructor() {
    makeObservable(this, {
      trafficSignal: observable,
      updateTrafficSignal: action.bound,
    })
  }
  updateTrafficSignal(
    style?: TrafficSignalStyle,
    colors?: Array<TrafficSignalButtons>,
    position?: TrafficSignalPosition
  ) {
    if (style) this.trafficSignal.style = style
    if (colors) this.trafficSignal.colors = colors
    if (position) this.trafficSignal.position = position
  }
}

Options.tsx

import * as React from 'react'
import { observer } from 'mobx-react'

import { useFrameItStore } from './store'
import type { TrafficSignal, TrafficSignalStyle, IFrameItStore } from './types'

interface IProps {
  className?: string
}

export const Options = observer((props: IProps) => {
  const frameItStore = useFrameItStore()

  const trafficSignal: TrafficSignal = frameItStore.trafficSignal

  const updateTrafficSignal: IFrameItStore['updateTrafficSignal'] =
    frameItStore.updateTrafficSignal

  return (
    <div>
      <h2>Traffic Signal Style:</h2>
      <select
        value={trafficSignal.style}
        onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
          const style = e.target.value as TrafficSignalStyle
          updateTrafficSignal(style)
        }}
      >
        <option value="outline">Outline</option>
        <option value="fill">Fill</option>
      </select>
    </div>
  )
})
deadcoder0904
  • 7,232
  • 12
  • 66
  • 163