Exploring the Purpose of the CombinedState Type in Redux's TypeScript Definitions

When it comes to Redux, there are various types to take note of:

declare const $CombinedState: unique symbol

/**
 * The base state type for reducers that are created with `combineReducers()`.
 *
 * This particular type helps the `createStore()` method in inferring which levels of the
 * preloaded state can be partial.
 *
 * Due to TypeScript being very duck-typed, a type needs to possess some form of
 * identifying property to differentiate it from other types with similar
 * prototypes for type checking purposes. This is why this type includes the
 * `$CombinedState` symbol property. Without this property, the type would essentially
 * match any object. Despite the fact that the symbol may not physically exist because it's an internal
 * (i.e. not exported), and its value is never checked internally. As it's a
 * symbol property, it's not meant to be enumerable, and the value is
 * always typed as undefined, so it should never hold any meaningful
 * value anyway. It just serves the purpose of distinguishing this type from a plain `{}`.
 */
export type CombinedState<S> = { readonly [$CombinedState]?: undefined } & S

I find myself slightly puzzled regarding the use of this symbol and type.

One example where you might encounter it is in combineReducers

export default function combineReducers<S>(
  reducers: ReducersMapObject<S, any>
): Reducer<CombinedState<S>>
export default function combineReducers<S, A extends Action = AnyAction>(
  reducers: ReducersMapObject<S, A>
): Reducer<CombinedState<S>, A>
export default function combineReducers<M extends ReducersMapObject<any, any>>(
  reducers: M
): Reducer<
  CombinedState<StateFromReducersMapObject<M>>,
  ActionFromReducersMapObject<M>
>
export default function combineReducers(reducers: ReducersMapObject) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers: ReducersMapObject = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }

The comments suggest that its purpose is to distinguish it from {} or an empty type, yet there doesn't seem to be any part where it actually checks for this type. Additionally, the comments mention that internally the value is never checked, so why include it at all apart from potentially causing confusion for individuals like me?

Answer №1

According to the comment, TypeScript is considered duck typed, allowing seemingly unrelated types to be assigned to one another:

type A = { foo: string, baz?: string }
type B = { foo: string, goo?: number }

declare let a: A;
declare let b: B;

// Unrelated types, structurally compatible, so assignable
a = b;
b = a;

Play

This concept is known as structural typing, which differs from how other strongly typed languages like Java or C# function. Traditional languages utilize nominal typing, where structure does not play a significant role and without an inheritance relationship, A would not be able to be assigned to B.

To mimic nominal typing in TypeScript, private fields or unique symbols can be employed, as they exhibit nominal behavior. In this scenario, only symbols originating from the same definition are deemed compatible.

For instance, within CombinedState, it ensures that if a type

T extends { [$CombinedState]: undefined }
, it must have originated from combineReducers (or is an instance of CombinedState). This assurance stems from the fact that $CombinedState remains inaccessible to external entities and its utilization solely occurs within the library.

Importantly, consider the significance of PreloadedState:

/**
 * Recursively makes combined state objects partial. Only combined state _root
 * objects_ (i.e. the generated higher level object with keys mapping to
 * individual reducers) are partial.
 */
export type PreloadedState<S> = Required<S> extends {
  [$CombinedState]: undefined
}
  ? S extends CombinedState<infer S1>
    ? {
        [K in keyof S1]?: S1[K] extends object ? PreloadedState<S1[K]> : S1[K]
      }
    : never
  : {
      [K in keyof S]: S[K] extends object ? PreloadedState<S[K]> : S[K]
    }

In PreloadedState, the presence of S being a CombinedState triggers varied behavior in the conditional type.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a memory leak causing Node.js memory growth in the (system)?

I have come across a peculiar memory leak in our live environment, where the heap continues to grow due to (system) objects. Heap snapshot Here is a memory dump showing a spike in memory usage up to 800MB: https://i.sstatic.net/vvEpA.png It seems that t ...

Leverage tsconfig.json for TypeScript compilation in Vim using the Syntastic plugin

How can I configure the syntastic plugin in vim to provide live error checking for TypeScript files using tsc? Currently, even though I have tsc set up in vim, it doesn't seem to be using the closest parent's tsconfig.json file for configuration. ...

combine two separate typescript declaration files into a single package

Can anyone help me figure out how to merge two typescript definition packages, @types/package-a and @types/package-b, into one definition package? package-a.d.ts [export package-a {...}] package-b.d.ts [exports package-b {...}] package-mine.d.ts [ export ...

What are the steps to configure options in the ng2-date-picker component?

My current project involves using the ng2-date-picker Angular 2 date picker. I'm struggling with setting options such as minDate, maxDate, dateFormat, and others. Any assistance on how to configure these would be greatly appreciated. Sample code: &l ...

The readAsDataURL method generates an invalid Base64 string for PNG images, while it works properly for JPG/JPEG files

Dealing with a React Native app and attempting to address this particular problem. The base64 is sent to an API and saved in the database as a blob. I understand that it's not ideal practice, but given that this is just a simple student project and no ...

State data in React not yet available following redirection

I recently created a login/register system using react and node. Everything runs smoothly until I redirect after logging in. The data stored in the state is no longer accessible to me post-redirect. Strangely enough, without the redirection, everything wo ...

How do I send events from iOS (Swift) to JavaScript in React Native?

Currently, I am in the midst of a project where my goal is to seamlessly integrate React-Native into a native Swift application. To ensure that both sides are kept in sync with the state, I have implemented a 'message bus' - a mechanism designed ...

Unexpected JSON end causes issue with DELETE request in Next.js version 13

I am currently working on a web app using Next 13 and I have a route.ts file located in the api folder. This file contains two different methods, POST and DELETE. While both methods successfully receive the request, I am facing an issue with JSON parsing ...

Splitting large components into smaller components

I am currently in the process of splitting the code from index.tsx into two separate files: firstTab.tsx and secondTab.tsx. As of now, I have only separated the code related to the first tab into firstTab.tsx, which can be viewed in the code editor. The co ...

Why is NestJs having trouble resolving dependencies?

Recently delving into NestJs, I followed the configuration instructions outlined in https://docs.nestjs.com/techniques/database, but I am struggling to identify the issue within my code. Error: Nest cannot resolve dependencies of the AdminRepository ...

Using Required and Partial with an Array of Generic Types

I'm currently working with the following types: interface Color { color: string } type DarkerColor<T> = T & Color & { darker: string } type ColorInfo<T> = DarkerColor<T> & { hue: number luminance: number opacity ...

What is the best way to reset a form upon submission using Redux Form?

Currently, I am in the process of developing a basic portfolio website that includes a Contact form section. This section allows users to send me messages directly to my email using Formspree. To achieve this functionality, I have implemented Redux Forms. ...

Unloading a dynamically-loaded child component in Vue.js from the keep-alive cache

I have a question that is similar to the one mentioned here: Vue.js - Destroy a cached component from keep alive I am working on creating a Tab System using Vue router, and my code looks something like this: //My Tab component <template> <tab& ...

Obtaining API/JSON Data to Implement in a NextJS Application

Currently, I am working on developing a NextJs website that focuses on detecting crop diseases. The process involves sending photos and environmental data to a fastapi python server for processing. Subsequently, the processed data is supposed to be display ...

Convert the static method of a TypeScript class into a variable

Hey everyone, I've been working on a service for my custom application/library and this is the current approach I'm taking to create it. However, I'm thinking of converting this method into a variable to make it more user-friendly. import ...

Challenges in Displaying Components in React with Typescript

I'm currently facing an issue where the content I am trying to render on my screen is not appearing. Although the function correctly enters the if conditional statement, as confirmed by console logging. This is the section where I have implemented th ...

Prevent identical objects from being interchangeable in Typescript

I have a situation where I frequently use a StringToString interface: interface StringToString { [key: string]: string; } There are instances when I need to switch the keys and values in my objects. In this scenario, the keys become values and the val ...

Using Typescript to define the type for React's useState() setter function whenever

I'm working on setting up a React Context to handle parameters mode and setMode, which act as getter and setter for a React state. This is necessary in order to update the CSS mode (light / dark) from child components. I'm encountering a Typescr ...

Base class protected properties are absent following the exclusion of another property

In my implementation, I have a generic base service that handles CRUD operations while the subclasses specify a specific entity and its endpoint URL. The base class sets a protected API URL property that each subclass initializes in the constructor. Recen ...

Creating a call to action within another action in Redux using React

Exploring the world of Redux within React, with the help of redux-thunk. One specific action I am working with is defined as follows: export const login = (value, history) => dispatch => { Axios.post('/api/users/login', value) ...