resir014 / react-redux-typescript-example

Demonstrating the common patterns when using React, Redux v4, and TypeScript.
https://resir014.xyz/posts/2018/07/06/redux-4-plus-typescript/
586 stars 150 forks source link

Migrating to latest versions - [ react-redux & connected-react-router ] #5

Closed IrvingArmenta closed 5 years ago

IrvingArmenta commented 5 years ago

Hello, I got to learn a lot by following this example, however if I try to use it with the latest:

"react-redux": "^6.0.0"
"connected-react-router": "^6.2.2"

I get errors everywhere and of course I've been looking for a proper way to migrate on their docs but the docs are not considering typescript and I am still learning.

I was just wondering if you could give some pointers to migrate it to the latest versions.

resir014 commented 5 years ago

@IrvingArmenta Hi, I'm still investigating this. Please be patient.

resir014 commented 5 years ago

Alright, I've done some tweaking around and I'm able to show a proper migration path for you.

The latest connected-react-router changed their store initialisation a bit, this is fairly a simple change since you'll have to include it in your combineReducers.

// ./src/store.index.ts

// ...
import { connectRouter, RouterState } from 'connected-react-router'

// ...
export interface ApplicationState {
  layout: LayoutState
  heroes: HeroesState
  teams: TeamsState
  router: RouterState
}

// ...
export const createRootReducer = (history: History) =>
  combineReducers({
    layout: layoutReducer,
    heroes: heroesReducer,
    teams: teamsReducer,
    router: connectRouter(history)
  })
// ./src/configureStore.tsx

// ...
import { ApplicationState, createRootReducer, rootSaga } from './store'

// ...
  const store = createStore(
    createRootReducer(history),
    initialState,
    composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
  )

However, upgrading react-redux is slightly more complicated, since it broke the typings for my "children-props-as-redux-container" approach I mentioned in my post, which is used in my LayoutContainer component. I would suggest against using this pattern nowadays, but if you still want to use it, here's a way to upgrade, using the newly-introduced ReactReduxContext:

// ./src/containers/LayoutContainer
import * as React from 'react'
import { ReactReduxContext } from 'react-redux'

import { ApplicationState } from '../store'
import { ThemeColors } from '../store/layout'
import * as layoutActions from '../store/layout/actions'

// Redux-specific props.
interface LayoutContainerProps {
  theme: ThemeColors
  setTheme: (theme: ThemeColors) => void
}

// Wrapper props for render/children callback.
interface LayoutContainerRenderProps {
  render?: (props: LayoutContainerProps) => React.ReactNode
  children?: (props: LayoutContainerProps) => React.ReactNode
}

// ...

const LayoutContainer: React.FC<LayoutContainerRenderProps> = ({ render, children }) => {
  // Here we do a bit of a hack. Since the latest react-redux typings broke the
  // "children-props-as-redux-container" approach on the previous version of this guide,
  // we use the newly-introduced `ReactReduxContext` consumer to get our state, and map the
  // `theme` state and the `setTheme` action call inside it.
  return (
    <ReactReduxContext.Consumer>
      {({ store }) => {
        // Use the standard `store.getState()` redux function to get the root state, and cast
        // it with our ApplicationState type.
        const state: ApplicationState = store.getState()

        // Obtain the `theme` state and the `setTheme` action.
        // Note that per Redux conventions actions MUST be wrapped inside `store.dispatch()`
        const theme = state.layout.theme
        const setTheme = (theme: ThemeColors) => store.dispatch(layoutActions.setTheme(theme))

        // Create a render/children props wrapper with the above variables set as a callback.
        if (render) {
          return render({ theme, setTheme })
        }

        if (children) {
          return children({ theme, setTheme })
        }

        return null
      }}
    </ReactReduxContext.Consumer>
  )
}

export default LayoutContainer

I'll push a PR to outline these steps shortly.