Search code examples
typescriptclonereadonly

How can I let Typescript know, that my function will clone a passed Readonly protected argument?


I'd like to know how I can make Typescript understand, that update can be mutated by the function which is passed as the second parameter to modifyState because update will be cloned.

// Basic example
import { clone } from 'lodash';

interface IState {
  foo: string;
}

const state: Readonly<IState> = {
  foo: "bar"
};

// Start situation
function modifyState<S>(
  prevState: S,
  updateFunc: (update: S) => void
): S {
  const newState = clone(prevState);
  updateFunc(newState);
  return newState;
}

modifyState(
  state,
  update => {
    update.foo = "another string"; // 'Cannot assign to 'foo' because it is a constant or a read-only property.'
  }
);

Solution

  • You could infer the generic type from the generic type parameter of the received Readonly<T> value instead:

    function modifyState<S>(
        prevState: Readonly<S>, // <- this one
        updateFunc: (update: S) => void
    ): Readonly<S> {
        ...
    }
    

    I believe the code is self-explanatory. If you need to support both readonly and non-readonly versions, you could use an overload signature, or a union type, such as:

    prevState: Readonly<S> | S
    

    In this case, the TypeScript compiler will prefer Readonly<S> over S, when applicable.