Giter Site home page Giter Site logo

Experiencing a type compilation error when using react-redux connect function for a component that does not inject additional properties into the wrapped base component about react-redux-typescript-guide HOT 11 CLOSED

dcs3spp avatar dcs3spp commented on May 29, 2024
Experiencing a type compilation error when using react-redux connect function for a component that does not inject additional properties into the wrapped base component

from react-redux-typescript-guide.

Comments (11)

piotrwitek avatar piotrwitek commented on May 29, 2024 1

No problem, I have also added an answer in the SO question for other ppl searching for help.
Cheers!

from react-redux-typescript-guide.

piotrwitek avatar piotrwitek commented on May 29, 2024

Hey @dcs3spp
Have you read the guide on Nested HOC?

Because I can clearly see you're doing it slightly different than in the HOC guide example and I'm pretty sure that's the problem.
So I'm wondering why you're not strictly following the guide?

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Hey @piotrwitek

Yes, agreed it is slightly different. I started with the Nested HOC on this site, since I find the site helpful and comprehensive, covering a wide range of guidelines for Typescript react redux developers. I tweaked my nested HOC due to the nature of project requirements.....

The behaviour of my nested HOC is as follows. Instead of injecting properties into a wrapped base component the nested HOC renders the wrapped component conditionally based upon the redux state it is connected to. For this reason I did not include Diff and InjectedProps in the HOC wrapper class. However, the base properties are still passed on to the wrapped component.

I made another forked codesandbox yesterday for my HoC. Currently developing using this. I have managed to get the HoC compiling, but only if I cast the nested HOC as any when I pass it to the connect function.

Not sure if there is something strange happening with react-redux. This stackoverflow connects to the store in the same way but has to cast the HOC passed to the connect function as any, otherwise compile errors are received...

from react-redux-typescript-guide.

piotrwitek avatar piotrwitek commented on May 29, 2024

Casting to any is turning off the type-safety, so it is never a solution but a workaround.

Moreover, you are injecting Props with connect so you have to subtract these props from BaseProps using Diff and if you want to use them in Hoc component you have to pass them as Component type argument. React.Component<InjectedProps>. This is a reason for your errors.

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Ahhhhhh, thanks @piotrwitek. Yes πŸ’― The lightbulb has been switched on now lol...

Ok, I think that I understand now. Gradually I am getting there. Before I was thinking that the need of Diff etc. was related to injecting properties into the base component. Yes, I see now that the HocProps from Diff operation is only used in the connect function. That explains the error I am receiving when trying to use it here.

Thanks again @piotrwitek!!!! You are the only person that has answered and isolated the precise issue that caused the compilation errorπŸ₯‡It is this key knowledge that I was misunderstanding. I also asked question on stack overflow and reactiflux #typescript but no responses.

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Hey @piotrwitek,

Have updated the codesandbox which is now providing a fully typed connected HoC :) How do I pass down the Base Component's own properties at line 176 of HoC class since compiler complains of no overlap between InjectedProps and BaseProps. If I change Hoc ErrorListener class to be

class ErrorListener extends React.Component<InjectedProps & BaseProps, never> {...}
then revert back to original typed connect function problem....

from react-redux-typescript-guide.

piotrwitek avatar piotrwitek commented on May 29, 2024

You don't pass BaseProps to Hoc, it shouldn't be there. The same as in the example.

Also don't lean on codesandbox for type errors, from my personal experience it can show false positives, always check using tsc command on your local dev env.

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Ok, thanks will download the project and will try compile locally using tsc.....

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Just downloaded the project from codesandbox. I had to slightly tweak the tsconfig by removing extends portion and also the incremental build compiler option and.....

There is a compile error regarding uniqueId property missing from ```PostsListWithErrorListener`` when it is created in App component. However, line 176 is not reported as a build error. Yes, sure enough you are right @piotrwitek , the highlighted line - 176 of HoC class - was not reported as a compile error when using a local tsc build.

Is the false positive something to do with eslint rules? I opened the project in Visual Studio Code and it highlights the same line as in codesandbox.

Anyway, some time saved there....otherwise I would have kept going round and round and round .........and round in one big circle lol....

Thanks again for your patience and help @piotrwitek πŸ₯‡

from react-redux-typescript-guide.

dcs3spp avatar dcs3spp commented on May 29, 2024

Hey @piotrwitek,

After fixing compile error mentioned in previous comment, I get another compile error with the same error as reported in codesandbox for a local tsc compile. Uploaded the same project here on github.

The compile error is....

Conversion of type '{ filteredErrors: FailureNotify[]; clearError: (fromAction: string, fromComponent: string, history?: History<any> | undefined, navigateTo?: string | 
undefined) => PayloadAction<constants.CLEAR_ERROR, ClearError>; ... 4 more ...; children?: ReactNode; }' to type 'BaseProps' may be a mistake because neither type sufficiently overl
aps with the other. If this was intentional, convert the expression to 'unknown' first.  TS2352
/**
   * ErrorListener component class
   * This should:
   * - Pass on base component properties when rendering base component
   * - Contain redux state and dispatch properties....these are not passed down into base component
   */
  class ErrorListener extends React.Component<InjectedProps, never> {
    static displayName = `withErrorListener(${BaseComponent.name})`;
    static readonly WrappedComponent = BaseComponent;

    /**
     * Render error if there is one to display, otherwise render the base component
     * @returns Rendered error if error occurred. Rendered base component if no error occurred. Base Component is rendered with it's own props only
     */
    render() {
      const { ...restProps } = this.props;
      console.log(
        `withErrorListener [error_count=${this.props.filteredErrors.length}]`
      );

      if (this.props.filteredErrors.length > 0) {
        return (
          <ErrorInfoConnected
            info={this.props.filteredErrors[0]}
            {...restProps}
          />
        );
      } else {
        return <BaseComponent {...restProps as BaseProps} />;
      }
    }
  }

I think what is happening is that it cannot perform the cast to BaseProps because these properties have been removed from InjectedProps.

If the code is updated to be InjectedProps & BaseProps in the class generic, as listed below, then revert back to the original compile error with connect function and have to cast to any....

/**
   * ErrorListener component class
   * This should:
   * - Pass on base component properties when rendering base component
   * - Contain redux state and dispatch properties....these are not passed down into base component
   */
  class ErrorListener extends React.Component<InjectedProps & BaseProps, never> {
    static displayName = `withErrorListener(${BaseComponent.name})`;
    static readonly WrappedComponent = BaseComponent;

    /**
     * Render error if there is one to display, otherwise render the base component
     * @returns Rendered error if error occurred. Rendered base component if no error occurred. Base Component is rendered with it's own props only
     */
    render() {
      const { ...restProps } = this.props;
      console.log(
        `withErrorListener [error_count=${this.props.filteredErrors.length}]`
      );

      if (this.props.filteredErrors.length > 0) {
        return (
          <ErrorInfoConnected
            info={this.props.filteredErrors[0]}
            {...restProps}
          />
        );
      } else {
        // this line causes a compile error
        return <BaseComponent {...restProps as BaseProps} />;
      }
    }
  }

How is it possible to pass on base component's own properties using nested HOC without having to cast connect component argument as any?

from react-redux-typescript-guide.

piotrwitek avatar piotrwitek commented on May 29, 2024

Ok, I checked your project, and I think here are some of the problems with HOC typings, although you have much more general type problems and unnecessary complexity that is causing a lot of problems with understanding the code.

  1. here are correct Injected Props:
type InjectedProps = {
    filteredErrors: any;
    history: any;
    location: any;
    match: any;
  };
  1. Remove injected props from restProps
const {
        history,
        location,
        match,
        filteredErrors,
        ...restProps
      } = this.props;
  1. Pass only the necessary props to ErrorInfoConnected, not all of props
<ErrorInfoConnected
            info={this.props.filteredErrors[0]}
            history={history}
            location={location}
            match={match}
          />

Now there are no errors, although I can be wrong about the above because I don't know your code and I have only briefly looked at this single file.

Here is the full code:

import * as React from "react";

import { connect, MapStateToProps } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom";

import { clearErrorAction } from "../features/errors/actions";
import { Diff } from "utility-types";
import { FailureNotify } from "../features/errors/types";
import { filterErrors } from "../features/errors/selectors";

import { RootState } from "typesafe-actions";
import { withId } from "./componentId/withId";

/**
 * Type declarations
 */
export type RequiredProps = { uniqueId: string };

/**
 * Internal components
 */

/** ================================= ErrorInfo ======================================== */

/**
 * Redux types
 */
const mapErrorInfoDispatchToProps = {
  clearError: clearErrorAction
};

type ErrorInfoReduxProps = typeof mapErrorInfoDispatchToProps;
type ErrorInfoProps = { info: FailureNotify } & RouteComponentProps &
  ErrorInfoReduxProps;

/**
 * Functional component to display error info and dispatch CLEAR_ERROR action when unmounted.
 * @param param0  properties for display error info and dispatching CLEAR_ERROR action
 */
const ErrorInfoBase = ({
  info,
  clearError,
  history
}: ErrorInfoProps): JSX.Element => {
  function goHome(): void {
    console.log("Go home button has been clicked");
    clearError(info.fromAction, info.fromComponent, history, "/");
  }
  return (
    <React.Fragment>
      <p>Error {info.message}</p>
      <p>Received from action {info.fromAction}</p>
      <p>Received for component id {info.fromComponent}</p>
      <button onClick={goHome}>Home</button>
    </React.Fragment>
  );
};

/** Connect the ErrorInfoBase component to the redux store */
const ErrorInfoConnected = connect<
  {},
  typeof mapErrorInfoDispatchToProps,
  {},
  RootState
>(
  null,
  mapErrorInfoDispatchToProps
)(ErrorInfoBase);

/** ================================== End ErrorInfo =================================== */

/* ===================================== HoC =========================================== */

/**
 * withErrorListener
 * HoC that renders errors on the redux store raised for a component.
 * The base component must remain unchanged with no new properties.
 * The withErrorListener enhances the behaviour of the base component by listening on the redux store for errors
 * notified.
 *
 * If no errors are found then render base component with it's own properties.
 * @param BaseComponent  The component to wrap. This must have a uniqueId property rendered by withId HoC.
 */
export const withErrorListener = <BaseProps extends RequiredProps>(
  BaseComponent: React.ComponentType<BaseProps>
) => {
  /**
   * Injected properties for use inside the HOC component class only
   * These are excluded from react-redux connect, i.e. they are not injected into connect function's ownProps argument
   * They are also not injected into the base component.
   * The only propertis that should be possed down when rendering BaseComponent is BaseProps.
   */
  type InjectedProps = {
    filteredErrors: any;
    history: any;
    location: any;
    match: any;
  }; //this solves typed connect issue, how to pass on base component's own props at line 174....
  // type InjectedProps = TReduxProps & RouteComponentProps & BaseProps; // including base props here breaks the typed connect, do I need this to pass on in line 177...

  /**
   * Remove the following injected properties injected from Base props:
   * - redux state and dispatch
   * - react router
   */
  type HocProps = Diff<BaseProps, InjectedProps>;

  /** == Redux properties ================================================================ **/

  /**
   * The object type returned by mapState function.
   * I need to declare it, as opposed to using TState, otherwise a circular reference is created.
   */
  type StateProps = {
    filteredErrors: FailureNotify[];
  };

  /**
   * Function to return subset of store state that filters errors for the wrapped component via uniqueId property
   * @param state  The root state
   * @param ownProps  uniqueId property is required to fullfil filter operation.
   * @returns  StateProps type that contains a list of filtered errors of type FailureNotify.
   */
  const mapState: MapStateToProps<StateProps, RequiredProps, RootState> = (
    state: RootState,
    ownProps: RequiredProps
  ): StateProps => {
    // uniqueId property is always undefined here???
    console.log(`withErrorListener mapStateToProps => ${ownProps.uniqueId}`);
    return {
      filteredErrors: [] // filterErrors(state.errors, ownProps)
    } as StateProps;
  };

  /**
   * Dispatch object. Each object key is mapped to an action.
   */
  const dispatchProps = {
    clearError: clearErrorAction
  };

  type TDispatchProps = typeof dispatchProps;
  type TStateProps = ReturnType<typeof mapState>;
  type TReduxProps = TStateProps & TDispatchProps;

  /** =============================== ErrorListener Component Class ==================== */

  /**
   * ErrorListener component class
   * This should accept:
   * - Base component properties
   * - Redux state and dispatch properties
   */
  class ErrorListener extends React.Component<InjectedProps, never> {
    static displayName = `withErrorListener(${BaseComponent.name})`;
    static readonly WrappedComponent = BaseComponent;

    /**
     * Render error if there is one to display, otherwise render the base component
     * @returns Rendered error if error occurred. Rendered base component if no error occurred. Base Component is rendered with it's own props only
     */
    render() {
      const {
        history,
        location,
        match,
        filteredErrors,
        ...restProps
      } = this.props;
      console.log(
        `withErrorListener [error_count=${this.props.filteredErrors.length}]`
      );

      if (this.props.filteredErrors.length > 0) {
        return (
          <ErrorInfoConnected
            info={this.props.filteredErrors[0]}
            history={history}
            location={location}
            match={match}
          />
        );
      } else {
        // false positive linter error, tsc compiles
        return <BaseComponent {...(restProps as BaseProps)} />;
      }
    }
  }

  // connect to redux store
  const ConnectedHoc = connect<
    TStateProps,
    TDispatchProps,
    HocProps, // this merges properties with ownProps, exclude redux and router props
    RootState
  >(
    mapState,
    dispatchProps
  )(ErrorListener);
  // connect to router

  // inject uniqueId
  const RoutedHoc = withRouter(ConnectedHoc);

  return withId(RoutedHoc);
};

from react-redux-typescript-guide.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.