From 785e7d48a2894a7c0b4ba6e3a5bd56f99534c75c Mon Sep 17 00:00:00 2001 From: Keven van Zuijlen Date: Wed, 12 Apr 2017 21:28:23 +0200 Subject: [PATCH] Implemented react-router v4 to ReactRedux template --- .../ReactReduxSpa/ClientApp/boot-client.tsx | 12 ++-- .../ReactReduxSpa/ClientApp/boot-server.tsx | 64 +++++++++---------- .../ClientApp/components/Counter.tsx | 4 +- .../ClientApp/components/FetchData.tsx | 14 ++-- .../ClientApp/components/Layout.tsx | 4 +- .../ClientApp/components/NavMenu.tsx | 14 ++-- .../ReactReduxSpa/ClientApp/configureStore.ts | 8 ++- templates/ReactReduxSpa/ClientApp/routes.tsx | 14 ++-- templates/ReactReduxSpa/package.json | 2 +- 9 files changed, 67 insertions(+), 69 deletions(-) diff --git a/templates/ReactReduxSpa/ClientApp/boot-client.tsx b/templates/ReactReduxSpa/ClientApp/boot-client.tsx index 4dc12af..58d85a3 100644 --- a/templates/ReactReduxSpa/ClientApp/boot-client.tsx +++ b/templates/ReactReduxSpa/ClientApp/boot-client.tsx @@ -2,23 +2,25 @@ import './css/site.css'; import 'bootstrap'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { browserHistory, Router } from 'react-router'; import { Provider } from 'react-redux'; -import { syncHistoryWithStore } from 'react-router-redux'; +import { ConnectedRouter } from 'react-router-redux'; +import { createBrowserHistory } from 'history'; import routes from './routes'; import configureStore from './configureStore'; import { ApplicationState } from './store'; +// Create browser history to use in the Redux store +const history = createBrowserHistory(); + // Get the application-wide store instance, prepopulating with state from the server where available. const initialState = (window as any).initialReduxState as ApplicationState; -const store = configureStore(initialState); -const history = syncHistoryWithStore(browserHistory, store); +const store = configureStore(history, initialState); // This code starts up the React app when it runs in a browser. It sets up the routing configuration // and injects the app into a DOM element. ReactDOM.render( - + , document.getElementById('react-app') ); diff --git a/templates/ReactReduxSpa/ClientApp/boot-server.tsx b/templates/ReactReduxSpa/ClientApp/boot-server.tsx index 4d43376..93d818f 100644 --- a/templates/ReactReduxSpa/ClientApp/boot-server.tsx +++ b/templates/ReactReduxSpa/ClientApp/boot-server.tsx @@ -1,50 +1,46 @@ import * as React from 'react'; import { Provider } from 'react-redux'; import { renderToString } from 'react-dom/server'; -import { match, RouterContext } from 'react-router'; -import createMemoryHistory from 'history/lib/createMemoryHistory'; +import { StaticRouter } from 'react-router-dom'; +import { replace } from "react-router-redux"; +import { createMemoryHistory } from 'history'; import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; import routes from './routes'; import configureStore from './configureStore'; export default createServerRenderer(params => { return new Promise((resolve, reject) => { - // Match the incoming request against the list of client-side routes - const store = configureStore(); - match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => { - if (error) { - throw error; - } + // Create memory history to use in the Redux store + const history = createMemoryHistory(); + const store = configureStore(history); - // If there's a redirection, just send this information back to the host application - if (redirectLocation) { - resolve({ redirectUrl: redirectLocation.pathname }); - return; - } + // Dispatch the current location so that the router knows where to go + store.dispatch(replace(params.location)); - // If it didn't match any route, renderProps will be undefined - if (!renderProps) { - throw new Error(`The location '${ params.url }' doesn't match any route configured in react-router.`); - } + const context : any = {}; - // Build an instance of the application - const app = ( - - - - ); + const app = ( + + + + ); - // Perform an initial render that will cause any async tasks (e.g., data access) to begin - renderToString(app); + // Perform an initial render that will cause any async tasks (e.g., data access) to begin + renderToString(app); - // Once the tasks are done, we can perform the final render - // We also send the redux store state, so the client can continue execution where the server left off - params.domainTasks.then(() => { - resolve({ - html: renderToString(app), - globals: { initialReduxState: store.getState() } - }); - }, reject); // Also propagate any errors back into the host application - }); + // If there's a redirection, just send this information back to the host application (Maybe improve this?) + if (context.url) { + resolve({ redirectUrl: context.url }); + return; + } + + // Once the tasks are done, we can perform the final render + // We also send the redux store state, so the client can continue execution where the server left off + params.domainTasks.then(() => { + resolve({ + html: renderToString(app), + globals: { initialReduxState: store.getState() } + }); + }, reject); // Also propagate any errors back into the host application }); }); diff --git a/templates/ReactReduxSpa/ClientApp/components/Counter.tsx b/templates/ReactReduxSpa/ClientApp/components/Counter.tsx index 1037247..f56cb4b 100644 --- a/templates/ReactReduxSpa/ClientApp/components/Counter.tsx +++ b/templates/ReactReduxSpa/ClientApp/components/Counter.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Link } from 'react-router'; +import { Link, RouteComponentProps } from 'react-router-dom'; import { connect } from 'react-redux'; import { ApplicationState } from '../store'; import * as CounterStore from '../store/Counter'; @@ -22,7 +22,7 @@ class Counter extends React.Component { } // Wire up the React component to the Redux store -export default connect( +export default connect>( (state: ApplicationState) => state.counter, // Selects which state properties are merged into the component's props CounterStore.actionCreators // Selects which action creators are merged into the component's props )(Counter); diff --git a/templates/ReactReduxSpa/ClientApp/components/FetchData.tsx b/templates/ReactReduxSpa/ClientApp/components/FetchData.tsx index 8a79cb8..e6380b2 100644 --- a/templates/ReactReduxSpa/ClientApp/components/FetchData.tsx +++ b/templates/ReactReduxSpa/ClientApp/components/FetchData.tsx @@ -1,25 +1,25 @@ import * as React from 'react'; -import { Link } from 'react-router'; +import { Link, RouteComponentProps } from 'react-router-dom'; import { connect } from 'react-redux'; import { ApplicationState } from '../store'; import * as WeatherForecastsState from '../store/WeatherForecasts'; // At runtime, Redux will merge together... type WeatherForecastProps = - WeatherForecastsState.WeatherForecastsState // ... state we've requested from the Redux store - & typeof WeatherForecastsState.actionCreators // ... plus action creators we've requested - & { params: { startDateIndex: string } }; // ... plus incoming routing parameters + WeatherForecastsState.WeatherForecastsState // ... state we've requested from the Redux store + & typeof WeatherForecastsState.actionCreators // ... plus action creators we've requested + & RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters class FetchData extends React.Component { componentWillMount() { // This method runs when the component is first added to the page - let startDateIndex = parseInt(this.props.params.startDateIndex) || 0; + let startDateIndex = parseInt(this.props.match.params.startDateIndex) || 0; this.props.requestWeatherForecasts(startDateIndex); } componentWillReceiveProps(nextProps: WeatherForecastProps) { // This method runs when incoming props (e.g., route params) change - let startDateIndex = parseInt(nextProps.params.startDateIndex) || 0; + let startDateIndex = parseInt(nextProps.match.params.startDateIndex) || 0; this.props.requestWeatherForecasts(startDateIndex); } @@ -67,7 +67,7 @@ class FetchData extends React.Component { } } -export default connect( +export default connect( (state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props WeatherForecastsState.actionCreators // Selects which action creators are merged into the component's props )(FetchData); diff --git a/templates/ReactReduxSpa/ClientApp/components/Layout.tsx b/templates/ReactReduxSpa/ClientApp/components/Layout.tsx index b87c731..18179ff 100644 --- a/templates/ReactReduxSpa/ClientApp/components/Layout.tsx +++ b/templates/ReactReduxSpa/ClientApp/components/Layout.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { NavMenu } from './NavMenu'; export interface LayoutProps { - body: React.ReactElement; + children?: React.ReactElement; } export class Layout extends React.Component { @@ -13,7 +13,7 @@ export class Layout extends React.Component {
- { this.props.body } + { this.props.children }
; diff --git a/templates/ReactReduxSpa/ClientApp/components/NavMenu.tsx b/templates/ReactReduxSpa/ClientApp/components/NavMenu.tsx index feef09d..1747148 100644 --- a/templates/ReactReduxSpa/ClientApp/components/NavMenu.tsx +++ b/templates/ReactReduxSpa/ClientApp/components/NavMenu.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Link } from 'react-router'; +import { NavLink, Link } from 'react-router-dom'; export class NavMenu extends React.Component<{}, {}> { public render() { @@ -18,19 +18,19 @@ export class NavMenu extends React.Component<{}, {}> {
  • - + Home - +
  • - + Counter - +
  • - + Fetch data - +
diff --git a/templates/ReactReduxSpa/ClientApp/configureStore.ts b/templates/ReactReduxSpa/ClientApp/configureStore.ts index 5b38388..ed1f551 100644 --- a/templates/ReactReduxSpa/ClientApp/configureStore.ts +++ b/templates/ReactReduxSpa/ClientApp/configureStore.ts @@ -1,15 +1,17 @@ import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer } from 'redux'; import thunk from 'redux-thunk'; -import { routerReducer } from 'react-router-redux'; +import { routerReducer, routerMiddleware } from 'react-router-redux'; import * as Store from './store'; +import { History } from 'history'; -export default function configureStore(initialState?: Store.ApplicationState) { +export default function configureStore(history: History, initialState?: Store.ApplicationState) { // Build middleware. These are functions that can process the actions before they reach the store. const windowIfDefined = typeof window === 'undefined' ? null : window as any; // If devTools is installed, connect to it const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension as () => GenericStoreEnhancer; + const middlewares = [thunk, routerMiddleware(history)]; const createStoreWithMiddleware = compose( - applyMiddleware(thunk), + applyMiddleware(...middlewares), devToolsExtension ? devToolsExtension() : f => f )(createStore); diff --git a/templates/ReactReduxSpa/ClientApp/routes.tsx b/templates/ReactReduxSpa/ClientApp/routes.tsx index 6845bfb..8e3465a 100644 --- a/templates/ReactReduxSpa/ClientApp/routes.tsx +++ b/templates/ReactReduxSpa/ClientApp/routes.tsx @@ -1,17 +1,15 @@ import * as React from 'react'; -import { Router, Route, HistoryBase } from 'react-router'; +import { Route } from 'react-router-dom'; import { Layout } from './components/Layout'; import Home from './components/Home'; import FetchData from './components/FetchData'; import Counter from './components/Counter'; -export default - - - - { /* Optional route segment that does not affect NavMenu highlighting */ } - -; +export default + + + +; // Enable Hot Module Replacement (HMR) if (module.hot) { diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index 1929cbb..d4e4052 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -6,7 +6,7 @@ "@types/react": "^0.14.29", "@types/react-dom": "^0.14.14", "@types/react-redux": "^4.4.29", - "@types/react-router": "^4.0.4", + "@types/react-router-dom": "^4.0.3", "@types/react-router-redux": "^5.0.0", "@types/redux": "3.5.27", "@types/webpack": "^2.2.0",