Comments (11)
No problem, I have also added an answer in the SO question for other ppl searching for help.
Cheers!
from react-redux-typescript-guide.
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.
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.
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.
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.
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.
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.
Ok, thanks will download the project and will try compile locally using tsc.....
from react-redux-typescript-guide.
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.
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.
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.
- here are correct Injected Props:
type InjectedProps = {
filteredErrors: any;
history: any;
location: any;
match: any;
};
- Remove injected props from restProps
const {
history,
location,
match,
filteredErrors,
...restProps
} = this.props;
- 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)
- VSCode Intellisense broken by RootState HOT 1
- Suggestion: Examples for new redux hooks and react-router hooks HOT 2
- Playground fails to compile - import order HOT 1
- Regarding 'Typing connected component with redux-thunk integration' section HOT 1
- Q: how to avoid exposing actionCreators when using redux-thunk
- `bindActionCreators` example is outdated
- src/hoc/connected-with-count.tsx type cast is incorrect
- MapDispatchToProps shorthand HOT 5
- Add new example for non-HOC component that uses connect HOT 2
- Cannot run up
- why the WithConnectedCountUsage component cannot increment the counter?
- 'fc-counter-connected-bind-action-creators.usage' component cannot work HOT 2
- property does not exist on type 'IntrinsicAttributes & Pick<any, symbol>'
- Start playground dir has a error
- Why not use immutable
- Slow typescript compile time HOT 1
- I got parsing error
- Link to missing anchor in the guide HOT 2
- Refactor RenderProps components
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-redux-typescript-guide.