Can union types be utilized in destructuring with useSelector?

Recently, I have been reevaluating the state structure used in my application and encountered some challenges on how to proceed.

Initially, I had set up the following state structure for redux:

type State = {
  loading: boolean
  loaded: boolean
  items: Array<Item>
  categories: Array<Category>
}

const initialState: State = {
  loading: false,
  loaded: false,
  items: [],
  categories: [],
}

This setup was straightforward and convenient for use with selectors. The unchanging types of items and categories eliminated concerns about undefined values or type casting. However, when I needed to introduce additional properties that were only occasionally required, I decided to create separate types for each redux action, leading me to the current structure:

type RequestItems = {
  category: Category;
  items: Array<Item>;
  totalCountFromServer: number;
}

export type RequestItem = {
  item: Item;
}

export type RequestCategories = {
  categories: Array<Category>
}

type RequestContent =
  | RequestItem
  | RequestItems
  | RequestCategories

type State = {
  loading: boolean;
  loaded: boolean;
  content: RequestContent | undefined
}

While this revised structure is less redundant, I am uncertain if useSelector can accurately determine the exact type. Is there a more effective way of utilizing it than what I currently have?

I feel that using state hooks here may be somewhat redundant, but by checking for 'undefined' content slides, I could potentially return a '404'.

const { content, loading, loaded } = useSelector(state => ({
    content: state.suply.content,
    loading: state.suply.loading,
    loaded: state.suply.loaded,
  }))

  const [items, setItems] = useState<Items[]>([])
  const [category, setCategory] = useState<Category>(Category.emptyObject)
  const [totalCountFromServer, setTotalCountFromServer] = useState<number>()

  useEffect(() => {
    if (content) {
      setItems((content as RequestItems).items)
      setCategory((content as RequestItems).category)
      setTotalCountFromServer((content as RequestItems).totalCountFromServer)
    }
  }, [content])

Alternatively, I encounter a TypeError when attempting this approach, as the selector struggles to read properties of 'undefined' (such as 'items', 'category', 'totalCountFromServer').

  const { content, loading, loaded } = useSelector(state => ({
    items: (state.suply.content as RequestItems).items,
    category: (state.suply.content as RequestItems).category,
    totalCountFromServer: (state.suply.content as RequestItems).totalCountFromServer,
    loading: state.suply.loading,
    loaded: state.suply.loaded,
  }))

Or I could just stick with destructuring like this, which functions correctly. Although, I came across an article suggesting that using destructuring with selectors might not be the ideal method.

  const { content, loading, loaded } = useSelector(state => ({
    content: state.suply.content,
    loading: state.suply.loading,
    loaded: state.suply.loaded,
  }))

  const {items, category, totalCountFromServer } = (content as RequestItems);

Hence, I wonder if there exists a superior approach to handling state with Union Type content, or if reverting to my original structure is the optimal choice?

Answer №1

Make sure to refer to the Define Typed Hooks documentation for guidance on creating typed versions of the useDispatch and useSelector hooks.

Additionally, explore the concept of Discriminating Unions

import { useSelector } from "react-redux";
import { useEffect } from "react";
import type { TypedUseSelectorHook } from "react-redux";

type Category = any;
type Item = any;

type RequestItems = {
  kind: "RequestItems";
  category: Category;
  items: Array<Item>;
  totalCountFromServer: number;
};

type RequestItem = {
  kind: "RequestItem";
  item: Item;
};

type RequestCategories = {
  kind: "RequestCategories";
  categories: Array<Category>;
};

type RequestContent = RequestItem | RequestItems | RequestCategories;

type State = {
  loading: boolean;
  loaded: boolean;
  content: RequestContent | undefined;
};

const useAppSelector: TypedUseSelectorHook<State> = useSelector;

const useCustomHook = () => {
  const { content, loading, loaded } = useAppSelector((state) => ({
    content: state.content,
    loading: state.loading,
    loaded: state.loaded
  }));

  useEffect(() => {
    if (content?.kind === "RequestItems") {
      // Handle logic for content being RequestItems
    }
  }, [content]);
};

Check out the example in the codesandbox link

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

Searching in Vue based on the selected option is only possible by the selected criteria and not by id, regardless of the

#1 Even if chosen, cannot search by id. The 'name' condition in the loop works well but I am unable to correctly search by id (returns nothing). #2 When selecting an option from the dropdown menu, it only displays until I start typing. I would l ...

Tips for maintaining a healthy balance of tasks in libuv during IO operations

Utilizing Typescript and libuv for IO operations is crucial. In my current situation, I am generating a fingerprint hash of a particular file. Let's say the input file size is approximately 1TB. To obtain the file's fingerprint, one method involv ...

Facing difficulty in accessing mongoose schema static method in TypeScript

I am currently facing an issue where I have a method called "findByToken()" defined in the model and implemented as a static method in the userSchema. However, in another part of my application, I am unable to access the method using: User.findByToken(tok ...

Is there a RxJS equivalent of tap that disregards notification type?

Typically, a tap pipe is used for side effects like logging. In this scenario, the goal is simply to set the isLoading property to false. However, it's important that this action occurs regardless of whether the notification type is next or error. Thi ...

'The object of type '{}' does not support indexing with a 'string'

I am currently working on a React component that dynamically generates an HTML Table based on an array of objects. The columns to be displayed are specified through the property called tableColumns. While iterating through the items and trying to display ...

During the compilation process, Angular could not locate the exported enum

In the file models.ts, I have defined the following enum: export enum REPORTTYPE { CUSTOMER, EMPLOYEE, PROJECT } After defining it, I use this enum inside another class like so: console.log(REPORTTYPE.CUSTOMER); When I save the file, the IDE automati ...

Leveraging Carbon for date localisation in a React/Laravel application

Working on implementing Carbon for localization in my project. After thorough testing, it seems that Carbon is functioning correctly. In order to integrate Carbon into my Model file, I added the following code block: use DateTimeInterface; protected funct ...

What is the best way to manage classNames dynamically in React with Material-UI?

I am wondering how to dynamically add and remove classes from an img tag. My goal is to change the image automatically every 2 seconds, similar to Instagram's signup page. I am struggling to achieve this using the material-ui approach. Below is a snip ...

How do I add a new item to an object using Ionic 2?

example item: this.advData = { 'title': this.addAdvS2.value.title , 'breadcrumb': this.suggestData.breadcrumb, 'price': this.addAdvS2.value.price ...

React: Issue with Rendering Custom Component Inside Double Mapping Loop

I am currently facing an issue in React as I work on a modal Form using Material-UI Field components. Each Field type such as Select, Text, and Shrink has its own Component. To handle more complex events like displaying more fields depending on a field&apo ...

React Component rendering twice, initial props are appearing as 'undefined' on first render

My React child component (FinalReport.js) seems to be rendering twice. However, on the first render, one of the props is mysteriously undefined, causing an error. I could handle this error, but it doesn't seem like the best approach. The Parent Compo ...

Typescript: Convert Generics to a String Format

Is there a way for me to return a generic type as a string in this function? const actionName = <P extends string>(path: P) => <A extends string>(action: A): string => `${path}/${action}`; const path = actionName('filter'); con ...

In the production build, the RegEx validation is lacking and fails to accept certain characters like 0, 2, 7, a, c, u, x, and occasionally z

Incorporating Angular 15.2.10 and Typescript 4.9.5, the RegEx utilized in one of my libraries and exposed via a service is outlined as follows: private readonly _DISALLOWED_CHARS_REGEX_GENERAL = new RegExp(/^[^\\/\?\!\&\: ...

Ionic causing delay in updating ngModel value in Angular 2

My ion-searchbar is set up like this: <ion-searchbar [(ngModel)]="searchQuery" (keyup.enter)="search();"></ion-searchbar> In the typescript file, the searchQuery variable is defined as follows: export class SearchPage { searchQuery: string ...

Issue with FullCalendar-vue and Typescript: the property 'getApi' is not recognized

Struggling to integrate FullCalendar-vue with Typescript, I encountered a problem when trying to access its API. This is how my calendar is set up: <FullCalendar ref="fullCalendar" :options="calendarOptions" style="width: 100%& ...

Expanding Classes through Index signatories

My attempt at creating an abstract class is not going as smoothly as I hoped. I suspect my limited knowledge of TypeScript is the primary issue, even though this seems like a common scenario. The abstract class I'm working on is called Program. It co ...

Determine the data type of the property in an object that needs to be provided as an argument to a

I am currently in the process of creating a function that can take in an object with a specific data type, as well as a function that acts on that data type as its argument. const analyze = < Info extends object, F extends {initialize: Info; display ...

Unable to break out of loop within TypeScript Protractor code

Currently I am delving into the realm of protractor and typescript while engaged in creating an automation test suite utilizing these technologies. I formulated a method as: private allElements = $$('.some-list li'); public async getDateElem ...

waiting for the import statement in a React/NextJS/Typescript project to resolve

While working on my node.js development server, I encountered a problem with the following code: import { useRouter } from 'next/router' import nextBase64 from 'next-base64'; const Load = () => { const router = useRouter() co ...

Clearing out all records from a Firestore collection

I attempted to implement the following method: deleteMessages(){ this.firestore.collection("MESSAGES") .get() .then(res => {res.forEach(element => {element.ref.delete();}); }); } However, I encountered the following err ...