Sending extra actions based on conditions from a redux-observable epic

To summarize, I am looking to create an epic that will make a request and dispatch an action based on whether the request is successful or not. Additionally, I want this epic to potentially dispatch more actions depending on the outcome and the current state.

The initial part is relatively simple

const fetchFooEpic: Epic<ActionAny, RootState> = (action$, store) =>
  action$.pipe(
    ofType<ActionAny, ReturnType<typeof actions.foo.fetch>>(actions.foo.types.FETCH_ALL),
    switchMap(action =>
      ajax({
        method: 'GET',
        url: `${path}/foo/${action.payload}`,
        headers: { Authorization: store.getState().user.token }
      }).pipe(
        map(({response}) => actions.foo.fetchFulfilled(response)),
        catchError(error => of(actions.foo.fetchRejected(error)))
      )
    )
  )

However, I've encountered difficulties when trying to incorporate another action or an empty response. I believe mergeMap and empty should be used when nothing needs to be dispatched, but I'm receiving type errors.

const fetchMissingRelations = (response: Foo[], state: RootState) => {
  const unknown: BarId[] = foo
    .map(foo => foo.barId)
    .filter(barId => !state.bar.entities[barId])
  return unknown.length
    ? actions.bar.fetch([...new Set(unknown)])
    : empty<never>()
}

const fetchFooEpic: Epic<ActionAny, RootState> = (action$, store) =>
  action$.pipe(
    ofType<ActionAny, ReturnType<typeof actions.foo.fetch>>(actions.foo.types.FETCH_ALL),
    switchMap(action =>
      ajax({
        method: 'GET',
        url: `${path}/foo/${action.payload}`,
        headers: { Authorization: store.getState().user.token }
      }).pipe(
        mergeMap(({response}) => of(
          actions.foo.fetchFulfilled(response),
          fetchMissingRelations(response, store.getState())
          // err: property 'type' is missing in {}
        )),
        catchError(error => of(actions.foo.fetchRejected(error)))
      )
    )
  )

This issue led me to refer to https://github.com/redux-observable/redux-observable/issues/339, but explicitly specifying the never type for empty did not resolve it for me.

This is the main question (you can skip here), but here is some additional background on why I am attempting this and if anyone could offer an alternative approach:

I have several sections of data in my state that have relationships coming from different API endpoints. Specifically, I am dealing with discussions involving internal and external participants.

When fetching discussions, I aim to promptly detect any references to participants that are not yet in the state and organize requests to acquire the missing information (to display names, avatars, etc., on the UI). In scenarios where all the necessary information is already stored locally, there should be no need for further requests.

Initially, I planned to rely on connected React components to check for missing referenced entities during lifecycle events (componentDidMount/componentWillReceiveProps) and trigger actions to update the data accordingly, allowing epics to focus on their specific tasks without concerning themselves with other updates.

However, this approach has become challenging as the state data is utilized in multiple areas requiring checks and actions. While I appreciate maintaining separate state domains, I believe having discussion-related epics handle updates for other areas would result in a simpler solution. This shift would enable connected components to solely focus on rendering or awaiting data, rather than managing updates for missing references. Nevertheless, I am open to superior suggestions.

Answer №1

empty gives back an observable, so based on the length of unknown, your fetchMissingRelations method will either return what seems like an action or an observable.

To ensure that it always returns an observable:

const fetchMissingRelations = (response: Foo[], state: RootState) => {
  const unknown: BarId[] = foo
    .map(foo => foo.barId)
    .filter(barId => !state.bar.entities[barId])
  return unknown.length
    ? of(actions.bar.fetch([...new Set(unknown)]))
    : empty<never>()
}

Additionally, make sure to modify your mergeMap accordingly:

...
mergeMap(({response}) => concat(
  of(actions.foo.fetchFulfilled(response)),
  fetchMissingRelations(response, store.getState())
)),

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

Retrieving the property of a union type comprising a void type and an unnamed type

Currently, I am working on a project involving GraphQL. In my code, I have encountered a GraphQLError object with a property named extensions. The type of this property is either void or { [key: string]: any; }. Whenever I try to access any property within ...

Is there a way to obtain asynchronous stack traces using Node.js and TypeScript?

When working with TypeScript, I encountered an issue with stack traces. It seems that only the bottommost function name is displayed. My setup includes Node.js v12.4.0 on Windows 10 (1803). Below is the code snippet: async function thrower() { throw new ...

What is the best way to refine object T types by supplying an array of exclusions (keyof T)[]?

What's the best way to create a type guard that can exclude keys from an object in TypeScript? Below is my getExcludedKeys function, which aims to filter out certain keys from an object. However, I'm encountering an issue where the type guard is ...

Creating a contravariant function member in TypeScript?

I am facing a challenge with a class that contains a member which is a function taking an instance of the same class: class Super { public member: (x: Super) => void = function(){} use() {const f = this.member; f(this)} } However, I need the me ...

What is the best way to determine the type of a static property in a TypeScript class?

I have a utility class containing various static methods: export default class MyHelper { private constructor() {} private static privateMethod() {} public static publicHelperMethod() {} } In my React component, I am using the publicHelperMet ...

Issue encountered while conducting tests with Jest and React Testing Library on a React component containing an SVG: the type is not recognized in React.jsx

In my Next.js 12.1.4 project, I am using Typescript, React Testing Library, and SVGR for importing icons like this: import ChevronLeftIcon from './chevron-left.svg' The issue arises when running a test on a component that includes an SVG import, ...

What is the best way to access the "document" in a vscode webview provider?

I am currently working on developing an extension for vscode that utilizes React for the interface. However, I have encountered an issue where I am unable to insert my react root component into the HTML of the webview. Despite trying various approaches, n ...

Can someone explain how to implement document.querySelector in TypeScript within the Angular framework?

I am tackling the task of creating a login/register form that allows users to switch between the two forms with the click of a button. The goal is to only display one form at a time. Initially, I implemented this functionality in a basic HTML file and it w ...

What Mac OSX command can you use in Typescript to input the quote character for multiline text?

Just starting out with Angular 2 and working through the official tutorial at https://angular.io/docs/ts/latest/tutorial/toh-pt1.html. I've realized that to use multi-line template strings (string interpolation), I have to use the ` mark. Any tips fo ...

Ensuring the presence of TypeScript variables

Take a look at this code snippet: let str: string | null; function print(msg: string) { console.log(msg); } print(str); When running this code, the typescript compiler correctly identifies the error, stating that Argument of type 'string | nu ...

Solving the issue of "localstorage is not defined" in NextJS Redux

Currently, I am working on implementing local storage in Next.js with Redux. In Next.js, components are initially rendered server-side, which means that localStorage or window objects are not available until the rendering occurs in a browser. This issue ...

Angular problem arises when attempting to map an array and selectively push objects into another array based on a specific condition

Setting up a cashier screen and needing an addToCart function seems pretty simple, right? However, I am encountering a strange logical error. When I click on an item to add it to the cart, my function checks if the item already exists in the array. If it d ...

Authentication for file uploads in Angular 2 using Dropzone and passportjs

I am currently working on implementing authentication for an admin user using Express, Passport, and MySQL in a specific page. The authentication process works fine, but I am facing an issue with verifying whether the user is logged in while uploading file ...

What is the best way to import a typescript file using a provided string?

I have a directory filled with JSON schemas, all coded in TypeScript. My goal is to import them collectively while preserving the typing, instead of having to write out numerous import statements. These schemas are utilized for validating JSON data being ...

Struggling to integrate D3.js with React using the useRef hook. Any suggestions on the proper approach?

I'm currently working on creating a line chart using d3.js and integrating it into React as a functional component with hooks. My approach involved utilizing useRef to initialize the elements as null and then setting them in the JSX. However, I encou ...

Tips on efficiently sorting through items using Angular Material's autocomplete feature

Struggling to implement a filter on an angular material autocomplete form input. The issue arises when attempting to filter values with an array of objects. After forking the example from the angular material documentation, I made adjustments to handle ob ...

The credentials in AWS S3Client are failing to load correctly

I encountered an issue with the S3 Client from aws sdk v3: When using the S3Client as outlined in the documentation and providing credentials via environment variables, I received an error message stating The AWS Access Key Id you provided does not exist ...

Property does not exist when dispatching in React Redux within componentDidMount

Currently, I am navigating my way through my initial project using React + Redux and have hit a few roadblocks while attempting to dispatch a function in the componentDidMount section. I tried to emulate the Reddit API example project from the Redux docume ...

Are there any solutions to refresh a page by clicking a button in next.js?

I am currently learning how to work with next.js and I'm facing an issue where I need to reload my page to make a new API request. As a beginner, I'm not sure how to achieve this. I've tried a few methods but none of them seem to work. Below ...

Tips for choosing a specific quantity and adjusting its value

Just starting out with Ionic 3 and looking for some help with the code. Can anyone assist me in understanding how to change the value of an item in a shopping cart and have the subtotal reflect that change? cart.ts private _values1 = [" 1 ", "2", " 3 "," ...