Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

typescript + redux: exclude redux props in parent component

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.

  1. 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.

  2. Set the param in IProps of MyChildComponent 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.

like image 781
Felix Hagspiel Avatar asked Apr 19 '17 17:04

Felix Hagspiel


1 Answers

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 from mapStateToProps - 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 connects 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).

like image 192
Tyler Sebastian Avatar answered Sep 20 '22 14:09

Tyler Sebastian