Skip to content

Commit

Permalink
Fixes #28303 - connect react router to redux
Browse files Browse the repository at this point in the history
  • Loading branch information
amirfefer authored and tbrisker committed Jun 18, 2020
1 parent 8a32cb8 commit 88eb8d4
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 31 deletions.
12 changes: 12 additions & 0 deletions webpack/assets/javascripts/foreman_navigation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable jquery/no-show */

import $ from 'jquery';
import URI from 'urijs';
import { push } from 'connected-react-router';
import store from './react_app/redux';
import * as LayoutActions from './react_app/components/Layout/LayoutActions';
import { deprecate } from './react_app/common/DeprecationService';
Expand All @@ -24,6 +26,16 @@ export const reloadPage = () => {
window.location.reload();
};

/**
* Push a new url to foreman's react router
* @param {String} url - the base url i.e `/hosts`
* @param {Object} searchQuery - the query params, i.e {'per_page': 4, 'page': 2}
*/
export const pushUrl = (url, queryParams) => {
const urlWithQueries = new URI(url).search(queryParams).toString();
return store.dispatch(push(urlWithQueries));
};

export const showLoading = () => {
store.dispatch(LayoutActions.showLoading());
};
Expand Down
6 changes: 3 additions & 3 deletions webpack/assets/javascripts/react_app/Root/ReactApp.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Router } from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router';
import history from '../history';
import { getForemanContext } from '../Root/Context/ForemanContext';
import Layout, { propTypes as LayoutPropTypes } from '../components/Layout';
Expand All @@ -10,11 +10,11 @@ const ReactApp = ({ data: { layout, metadata } }) => {
const ForemanContext = getForemanContext(metadata);
return (
<ForemanContext.Provider value={metadata}>
<Router history={history}>
<ConnectedRouter history={history}>
<Layout data={layout}>
<AppSwitcher />
</Layout>
</Router>
</ConnectedRouter>
</ForemanContext.Provider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { componentMountData, serverResponse } from './notifications.fixtures';
import Notifications from './';
import { API, APIMiddleware } from '../../redux/API';
import { NOTIFICATIONS } from '../../redux/consts';
import { IntervalMiddleware } from '../../redux/middlewares';
import { IntervalMiddleware } from '../../redux/middlewares/IntervalMiddleware';
import { registeredIntervalException } from '../../redux/middlewares/IntervalMiddleware/IntervalHelpers';
import { DEFAULT_INTERVAL } from '../../redux/actions/notifications/constants';

Expand Down
4 changes: 3 additions & 1 deletion webpack/assets/javascripts/react_app/history.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createBrowserHistory } from 'history';
import forceSingleton from '../react_app/common/forceSingleton';

export default createBrowserHistory();
const history = forceSingleton('history', () => createBrowserHistory());
export default history;
21 changes: 2 additions & 19 deletions webpack/assets/javascripts/react_app/redux/index.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import { applyMiddleware, createStore, compose } from 'redux';
import forceSingleton from '../common/forceSingleton';

import reducers from './reducers';

import { IntervalMiddleware, APIMiddleware } from './middlewares';

let middleware = [thunk, IntervalMiddleware, APIMiddleware];

const logReduxToConsole = () => {
const isProduction = process.env.NODE_ENV === 'production';
const isLogger = process.env.REDUX_LOGGER;

if (!isProduction && !global.__testing__) {
if (isLogger === undefined || isLogger === true) return true;
}
return isProduction && isLogger;
};

if (logReduxToConsole()) middleware = [...middleware, createLogger()];
import { middlewares } from './middlewares';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export const generateStore = () =>
createStore(reducers, composeEnhancers(applyMiddleware(...middleware)));
createStore(reducers, composeEnhancers(applyMiddleware(...middlewares)));

const store = forceSingleton('redux_store', generateStore);

Expand Down
26 changes: 24 additions & 2 deletions webpack/assets/javascripts/react_app/redux/middlewares/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
export { APIMiddleware } from '../API';
export { IntervalMiddleware } from './IntervalMiddleware';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'connected-react-router';
import { APIMiddleware } from '../API';
import { IntervalMiddleware } from './IntervalMiddleware';
import history from '../../history';

const logReduxToConsole = () => {
const isProduction = process.env.NODE_ENV === 'production';
const isLogger = process.env.REDUX_LOGGER;

if (!isProduction && !global.__testing__) {
if (isLogger === undefined || isLogger === true) return true;
}
return isProduction && isLogger;
};

export const middlewares = [
thunk,
IntervalMiddleware,
APIMiddleware,
routerMiddleware(history),
...(logReduxToConsole() ? [createLogger()] : []),
];
3 changes: 3 additions & 0 deletions webpack/assets/javascripts/react_app/redux/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import history from '../../history';
import hosts from './hosts';
import notifications from './notifications';
import toasts from './toasts';
Expand Down Expand Up @@ -38,6 +40,7 @@ export function combineReducersAsync(asyncReducers) {
...factChartReducers,
...typeAheadSelectReducers,

router: connectRouter(history),
// Pages
...statisticsPageReducers,
...fillReducers,
Expand Down
6 changes: 6 additions & 0 deletions webpack/assets/javascripts/react_app/routes/RouterSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const selectRouter = state => state.router;
export const selectRouterLocation = state => selectRouter(state).location;
export const selectRouterPath = state => selectRouterLocation(state).pathname;
export const selectRouterSearch = state => selectRouterLocation(state).search;
export const selectRouterHash = state => selectRouterLocation(state).hash;
export const selectLastHistoryAction = state => selectRouter(state).action;
10 changes: 5 additions & 5 deletions webpack/assets/javascripts/react_app/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Switch, Route } from 'react-router-dom';
import { routes } from './routes';
import { visit } from '../../foreman_navigation';

let currentPath = window.location.pathname;
let currentPath = window.location.href;

const AppSwitcher = () => {
const updateCurrentPath = () => {
currentPath = window.location.pathname;
const updateCurrentPath = nextPath => {
currentPath = nextPath;
};

const handleRailsContainer = () => {
Expand All @@ -22,9 +22,9 @@ const AppSwitcher = () => {
};

const handleFallbackRoute = () => {
const nextPath = window.location.pathname;
const nextPath = window.location.href;
if (currentPath !== nextPath) {
updateCurrentPath();
updateCurrentPath(nextPath);
visit(nextPath);
}
return null;
Expand Down
42 changes: 42 additions & 0 deletions webpack/stories/docs/connected-react-router.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Meta } from '@theforeman/stories';

<Meta
title="Introduction|Connected React Router"
parameters={{
storyWeight: 140,
}}
/>

# Connected React Router
Foreman uses react-router for client routing in react pages, however foreman contains lots of erb content which includes react components.
in order to control react router better and to have one source of truth,
foreman uses `connected-react-router` which enable controling react router via redux.

### Push url
This pushed a new url (including querystring) to react router and creates a transition

```js
import { pushUrl } from '../foreman_navigation';
import { foremanUrl } from '../foreman_tools';

<Button onClick={() => pushUrl(foremanUrl('/hosts'))}>
Click Me!
</Button>
```

### React Router Selectors
You can use react router selectors for retriving data

selectRouterLocation - the current location.
electRouterPath - the current path
selectRouterSearch - the current search
selectRouterHash - the current hash
selectRouter - the entire router object which includes every entry above

```js
import { useSelector } from 'react-redux';
import { selectRouterLocation } from '../routes/RouterSelector';


const location = useSelector(selectRouterLocation);
```

0 comments on commit 88eb8d4

Please sign in to comment.