jokul
jokul

Reputation: 1339

Cannot pass props to component through connect()

I am using React / Redux with TypeScript transpilation. I want my navigation menu to read from state and to maintain its own mini-state.

NavMenu.tsx:

import * as React from 'react';
import { NavLink, Link, RouteComponentProps } from 'react-router-dom';
import { connect } from 'react-redux';
import { ApplicationState } from '../store';
import * as NavigationStore from '../store/Navigation';

type NavigationProps =
  NavigationStore.NavigationState
  & typeof NavigationStore.actionCreators;

class NavMenu extends React.Component<NavigationProps, {}> {
  public render() {
    return (
      <nav className='main-nav'>
        <ul className={`nav-standard`}>
            <li>
                <NavLink exact to={'/'} activeClassName='active'>
                    Home
                </NavLink>
            </li>
            <li>
                <NavLink to={'/learn'} activeClassName='active'>
                    Learn
                </NavLink>
            </li>
            <li>
                <NavLink to={'/blog'} activeClassName='active'>
                    Blog
                </NavLink>
            </li>
        </ul>
        <div className='nav-small'>
          <button type='button' className='navbar-toggle' onClick={() => { this.props.toggle() } }>
              <span className='screen-reader-content'>Toggle Navigation</span>
              <i className='fa fa-bars'></i>
          </button>
        </div>
      </nav>
    );
  }
}

export default connect(
    (state: ApplicationState) => state.navigation,
    NavigationStore.actionCreators
)(NavMenu) as typeof NavMenu;

Here is how I am trying to render the navbar, Layout.tsx:

import * as React from 'react';
import NavMenu from './NavMenu';

export class Layout extends React.Component<{}, {}> {
  public render() {
    return <div>
      <NavMenu />
      { this.props.children }
    </div>;
  }
}

I'm getting a typescript transpilation error:

TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<NavMenu> & Readonly<{ children?: ReactNode; }> & R...'. Type '{}' is not assignable to type 'Readonly<NavigationProps>'. Property 'expanded' is missing in type '{}'.

Isn't this the purpose of the connect() function? To automatically map state and actions to a component? This is normally the error I get when not passing props down to a component (without using connect()).

EDIT It does work when I exhaustively pass down all the properties expected for a NavigationProps type:

import * as React from 'react';
import NavMenu from './NavMenu';

export class Layout extends React.Component<{}, {}> {
  public render() {
    return <div>
      <NavMenu expanded={false} expand={Navigation.actionCreators.expand} constrict={Navigation.actionCreators.constrict} toggle={Navigation.actionCreators.toggle} />
      { this.props.children }
    </div>;
  }
}

Navigation.ts for posterity (new import):

import { Action, Reducer } from 'redux';

export interface NavigationState {
  expanded: boolean;
};

interface ExpandNavigationAction { type: 'EXPAND_NAVIGATION' }
interface ConstrictNavigationAction { type: 'CONSTRICT_NAVIGATION' }
interface ToggleNavigationAction { type: 'TOGGLE_NAVIGATION' }

type KnownAction = ExpandNavigationAction
  | ConstrictNavigationAction
  | ToggleNavigationAction;

export const actionCreators = {
  expand: () => <ExpandNavigationAction>{ type: 'EXPAND_NAVIGATION' },
  constrict: () => <ConstrictNavigationAction>{ type: 'CONSTRICT_NAVIGATION' 
},
  toggle: () => <ToggleNavigationAction>{ type: 'TOGGLE_NAVIGATION' }
};

export const reducer: Reducer<NavigationState> = (state: NavigationState, 
action: KnownAction) => {
  switch (action.type) {
    case 'EXPAND_NAVIGATION':
      return { expanded: true };
    case 'CONSTRICT_NAVIGATION':
        return { expanded: false };
    case 'TOGGLE_NAVIGATION':
        return { expanded: !state.expanded };
    default:
        const exhaustiveCheck: never = action;
  }
  return state || { expanded: false };
}

Even though this transpiles successfully, my goal when doing this was to avoid writing out such verbose markup. I was under the impression the whole point of the connect() method was to simply and easily pass the correct properties from state down to the child component via mapper methods.

Upvotes: 0

Views: 528

Answers (1)

stone
stone

Reputation: 8662

Don't cast the result of connect() to typeof NavMenu. When you do that you're telling TS that the component expects a NavigationProps object to be passed in as a property. So it makes sense that you get an error when you don't pass in any properties.

The wrapped component created by connect() has no required properties.

Upvotes: 1

Related Questions