Hey,
If we use redux for state management in your react application, then react-redux-router plays vital role when we want routing in our application. It looks bit complex to configure react-redux-router but believe me routing will be very easy once it’s configured. Version is pretty important here for react-router-redux and here I am using 5.0.0-alpha.9 (maybe by the time you read this article, you will have better version)
I am using typescript so I need to add install @types in devDependencis
Once we have this packages installed, we need to configure in three places –
1. Reducer Function
2. Store Creation
3. Assign history to ConnectedRouter in parent tag
4. Push (whenever necessary)
1. Reducer Function
I have combineReducers in my app and all I need to do is, refer the routerReducer from react-router-redux package
RootReducer.ts
import { combineReducers } from "redux";
import { routerReducer } from "react-router-redux";
import IStore from "../store/IStore";
import { userReducer } from "./UserReducer";
import { appReducer } from "./AppReducer";
const rootReducer = combineReducers<IStore>({
routing: routerReducer,
user: userReducer,
app: appReducer
});
export default rootReducer;
export default rootReducer;
2. Store Creation
In store creation, I’ve created history from history package, I will be using hash navigation so I specifically take history from createHashHistory and add this to my story using routerMiddleware
configureStore.dev
import { applyMiddleware, createStore, Store } from "redux";
import promise from "redux-promise-middleware";
import thunkMiddleware from "redux-thunk";
import { routerMiddleware } from "react-router-redux";
import createHistory from "history/createHashHistory";
// For Dev
import logger from "redux-logger";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "../reducers/RootReducer";
import IStore from "./IStore";
import initialState from "./initialState";
export const history = createHistory();
export default function configureStore(
initialStateValue: IStore = initialState
): Store<IStore> {
return createStore(
rootReducer,
initialStateValue!,
composeWithDevTools(
applyMiddleware(
promise(),
routerMiddleware(history),
thunkMiddleware,
logger
)
)
);
}
3. Assign history to ConnectedRouter in parent tag
In our root tag, just below the <Provider> tag which we used for redux, add <ConnectedRouter> and assign all the components inside this tag, make sure to use history which we created in store.
index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { Fabric } from "office-ui-fabric-react";
import App from "./containers/App";
import "./styles/app.scss";
import configureStore from "./store/configureStore";
import { ConnectedRouter } from "react-router-redux";
import { history } from "../src/store/configureStore.dev";
const storeObj = configureStore();
ReactDOM.render(
<Fabric>
<Provider store={storeObj}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</Fabric>,
document.getElementById("root") as HTMLElement
);
I created a component called AppNavigation and it will take care of navigation between components and we are using CommandBar from Office UI Fabric for navigation bar. Important thing to notice here is this.props.onChangePath(item.key) this will push the values into router during onClick event
AppNavigation.tsx
import * as React from "react";
import { Dispatch } from "react-redux";
import { RouterState } from "react-router-redux";
import { CommandBar } from "office-ui-fabric-react";
export interface IAppNavigationProps {
onChangePath: (key: string) => Dispatch<RouterState>;
}
export default class AppNavigation extends React.Component<IAppNavigationProps, {}> {
constructor(props: IAppNavigationProps) {
super(props);
}
public render(): JSX.Element {
let isAdmin = true;
const menuItems = [
{
key: "home",
name: "Home",
iconProps: {
iconName: "Home"
},
onClick: (event: any, item: any) => {
this.props.onChangePath(item.key);
}
},
{
name: "Add New",
iconProps: {
iconName: "Add"
},
key: "form",
subMenuProps: {
items: [
{
name: "Page 1",
key: "page1",
iconProps: {
iconName: "NewTeamProject"
},
className: isAdmin ? "" : "hidden",
onClick: (event: any, item: any) => {
this.props.onChangePath(item.key);
}
},
{
name: "Page 2",
key: "/page2",
iconProps: {
iconName: "ProjectCollection"
},
onClick: (event: any, item: any) => {
this.props.onChangePath(item.key);
}
}
]
}
}
];
return (
<nav>
<CommandBar items={menuItems} />
</nav>
);
}
}
Actual export for AppNavigation will contain this dispatch(push(key)); in this push is from react-router-redux
import IStore from "../store/IStore";
import { Dispatch } from "redux";
import { connect } from "react-redux";
import AppNavigation, { IAppNavigationProps } from "../components/AppNavigation";
import { push } from "react-router-redux";
function mapStateToProps(store: IStore) {
return {
store: store
};
}
function mapDispatchToProps(dispatch: Dispatch<IStore>) {
return {
onChangePath: (key: string) => {
dispatch(push(key));
}
};
}
export default connect<{}, {}, IAppNavigationProps>(
mapStateToProps,
mapDispatchToProps
)(AppNavigation) as React.ComponentClass<{}>;
In our main App.tsx file, I use below element inside render function,
<div>
<div>
<Route path="/" component={AppNavigation} />
<div className="Grid">
<Route exact path="/" render={() => <Redirect to="/home" />} />
<Route path="/home" render={() => <h4>Home</h4>} />
<Route path="/page1" component={HelpIcon} />
<Route path="/page2" render={() => <h4>yet to develop</h4>} />
</div>
</div>
</div>
Finally, we setup of hashRouter using react-router-redux package.
And in the logs we can see how store gets updated by router
Github repo for this solution is available here (branch name is: feature/redux-router) -> https://github.com/ahamedfazil/react-redux-ts-sp/tree/feature/redux-router
Happy Coding
Ahamed
Leave a comment