I am using redux and typescript for my current webapp.
What is the best practice to define the props of a component which receives redux-actions via @connect
, but also props from parent?
Example:
// mychild.tsx export namespace MyChildComponent { export interface IProps { propertyFromParent: string; propertyFromRedux: string; // !!!! -> This is the problem actionsPropertyFromRedux: typeof MyReduxActions; // !!!! -> And this } } @connect(mapStateToProps, mapDispatchToProps) export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> { ... react stuff } function mapStateToProps(state: RootState) { return { propertyFromRedux: state.propertyFromRedux }; } function mapDispatchToProps(dispatch) { return { actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch) }; } // myparent.tsx export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> { ... react stuff render(){ // typescript complains, because I am not passing `propertyFromRedux`! return <div><MyChildComponent propertyFromParent="yay" /></div>; } }
As I see it I got 2 solutions.
Just pass the actions & state down through my whole app. But that would mean that my whole app gets re-rendered even when just some small child component would have to change. Or is it the redux way to listen in my top level component on all store changes? Then I would have to write a lot of logic inside
shouldComponentUpdate
for props which are no flat objects.Set the param in
IProps
ofMyChildComponent
optional like this:
–
// mychild.tsx export namespace MyChildComponent { export interface IProps { propertyFromParent: string; propertyFromRedux?: typeof MyAction; // This is the problem } }
Is there another way? Both of the above ways seem too messy in my eyes.
Advertisement
Answer
You need to split up your props – you’ll need a DispatchProps
, StateProps
, and an OwnProps
type. You’ll also have to use TypeScript’s generics with connect
DispatchProps
are your action creators.StateProps
are your state props (duh) – these come frommapStateToProps
– the return type of that function should match this type.OwnProps
are props which are accepted (and perhaps expected) by your component. Optional props should be marked as optional in the interface.
The way I do it (without decorators, but i’m sure it applies here) is
interface ComponentDispatchProps { doSomeAction: typeof someAction; } interface ComponentStateProps { somethingFromState: any; } interface ComponentOwnProps { somethingWhichIsRequiredInProps: any; somethingWhichIsNotRequiredInProps?: any; } // not necessary to combine them into another type, but it cleans up the next line type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps; class Component extends React.Component<ComponentProps, {}> {...} function mapStateToProps(state, props) { return { somethingFromState }; } export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>( mapStateToProps, mapDispatchToProps )(Component);
I think you have to use @connect<StateProps, DispatchProps, OwnProps>
which will decorate and return a class which accepts OwnProps
.
If you look at connect
s implementation in TS
export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps> interface ComponentDecorator<TOriginalProps, TOwnProps> { (component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>; }
connect<...>
returns a ComponentDecorator
, which, when passed the component (in your case this is done transparently when transpiling the decorator out), regardless of StateProps
, and DispatchProps
returns a component which expects OwnProps
.
connect
(non-generic) returns InferableComponentDecorator
export interface InferableComponentDecorator { <P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct; }
which attempts to infer the props based on the props supplied to the component, which in your case is the combination of all props (OwnProps
becomes ComponentProps
from above).