Skip to content

Commit

Permalink
fix: 🐛 removing connected-react-router (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
Can-Sahin authored May 16, 2020
1 parent 5f5e413 commit f6a0350
Show file tree
Hide file tree
Showing 25 changed files with 91 additions and 148 deletions.
8 changes: 3 additions & 5 deletions docs/building-blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ First we have to look at what is happening when react starts its life with out `
It is one of the biggest files of the boilerplate. It contains all the global setup to make sure your app runs smoothly. Let's break its contents down:

- `react-app-polyfill` is imported to enable compatibility with many browsers and cool stuff like generator functions, Promises, etc.
- A `history` object is created, which remembers all the browsing history for your app. This is used by the ConnectedRouter to know which pages your users visit. (Very useful for analytics, by the way.)
- A redux `store` is instantiated.
- `ReactDOM.render()` not only renders the [root react component](https://github.com/react-boilerplate/react-boilerplate/blob/master/app/containers/App/index.js) called `<App />`, of your application, but it renders it with `<Provider />`, `<ConnectedRouter />`.
- `ReactDOM.render()` not only renders the [root react component](https://github.com/react-boilerplate/react-boilerplate/blob/master/app/containers/App/index.js) called `<App />`, of your application, but it renders it with `<Provider />`.
- Hot module replacement is set up via [Webpack HMR](https://webpack.js.org/guides/hot-module-replacement/) that makes all the reducers, injected sagas, components, containers, and i18n messages hot reloadable.
- i18n internationalization support setup.
- `<Provider />` connects your app with the redux `store`.
Expand Down Expand Up @@ -40,10 +39,9 @@ The store is created with the `createStore()` factory, which accepts three param
2. **Initial state:** The initial state of your app as determined by your reducers.
3. **Middleware/enhancers:** Middlewares are third party libraries which intercept each redux action dispatched to the redux store and then... do stuff. For example, if you install the [`redux-logger`](https://github.com/evgenyrodionov/redux-logger) middleware, it will listen to all the actions being dispatched to the store and print previous and next state in the browser console. It's helpful to track what happens in your app.

In our application we are using two such middleware.
In our application we are using a single middleware.

1. **Router middleware:** Keeps your routes in sync with the redux `store`.
2. **Redux saga:** Used for managing _side-effects_ such as dispatching actions asynchronously or accessing browser data.
1. **Redux saga:** Used for managing _side-effects_ such as dispatching actions asynchronously or accessing browser data.

### Redux-Toolkit

Expand Down
27 changes: 3 additions & 24 deletions docs/building-blocks/routing.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# Routing

`react-router` is the de-facto standard routing solution for react applications.
The thing is that with redux and a single state tree, the URL is part of that
state. `connected-react-router` takes care of synchronizing the location of our
application with the application state.

(See the [`connected-react-router` FAQ](https://github.com/supasate/connected-react-router/blob/master/FAQ.md)
for more information)
## Why not using [connected-react-router](https://github.com/supasate/connected-react-router)?

There is detailed explanation [here](https://reacttraining.com/react-router/web/guides/redux-integration/deep-integration). In short, it is not suggested to integrate route with redux, simply because it shouldn't be needed. There are other ways of navigating as explained there.

## Usage

Expand All @@ -20,25 +18,6 @@ Top level routes are located in `src/app/index.tsx`.

If you want your route component (or any component for that matter) to be loaded asynchronously, use container or component generator with 'Do you want to load resources asynchronously?' option activated.

To go to a new page use the `push` function by `connected-react-router`:

```ts
import { push } from 'connected-react-router';

dispatch(push('/path/to/somewhere'));
```

You can do the same thing in a saga:

```ts
import { push } from 'connected-react-router';
import { put } from 'redux-saga/effects';

export function* mySaga() {
yield put(push('/home'));
}
```

## Child Routes

For example, if you have a route called `about` at `/about` and want to make a child route called `team` at `/about/our-team`, follow the example
Expand Down
1 change: 0 additions & 1 deletion docs/misc/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import navigationBarReducer from 'containers/NavigationBar/reducer';
export function createReducer(injectedReducers: InjectedReducersType = {}) {
const rootReducer = combineReducers({
navigationBar: navigationBarReducer,
router: connectRouter(history) as Reducer<RouterState, AnyAction>,
...injectedReducers,
});

Expand Down
6 changes: 3 additions & 3 deletions internals/startingTemplate/src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { Switch, Route } from 'react-router-dom';
import { Switch, Route, BrowserRouter } from 'react-router-dom';

import { GlobalStyle } from 'styles/global-styles';

Expand All @@ -17,7 +17,7 @@ import { NotFoundPage } from './components/NotFoundPage/Loadable';

export function App() {
return (
<>
<BrowserRouter>
<Helmet
titleTemplate="%s - React Boilerplate"
defaultTitle="React Boilerplate"
Expand All @@ -30,6 +30,6 @@ export function App() {
<Route component={NotFoundPage} />
</Switch>
<GlobalStyle />
</>
</BrowserRouter>
);
}
17 changes: 6 additions & 11 deletions internals/startingTemplate/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import 'react-app-polyfill/stable';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import * as serviceWorker from 'serviceWorker';
import { history } from 'utils/history';
import 'sanitize.css/sanitize.css';

// Import root app
Expand All @@ -26,22 +24,19 @@ import { configureAppStore } from 'store/configureStore';
// Initialize languages
import './locales/i18n';

// Create redux store with history
const store = configureAppStore(history);
const store = configureAppStore();
const MOUNT_NODE = document.getElementById('root') as HTMLElement;

interface Props {
Component: typeof App;
}
const ConnectedApp = ({ Component }: Props) => (
<Provider store={store}>
<ConnectedRouter history={history}>
<HelmetProvider>
<React.StrictMode>
<Component />
</React.StrictMode>
</HelmetProvider>
</ConnectedRouter>
<HelmetProvider>
<React.StrictMode>
<Component />
</React.StrictMode>
</HelmetProvider>
</Provider>
);
const render = (Component: typeof App) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { configureAppStore } from '../configureStore';
import { history } from '../../utils/history';

describe('configureStore', () => {
it('should return a store with injected enhancers', () => {
Expand All @@ -11,11 +10,10 @@ describe('configureStore', () => {
injectedSagas: expect.any(Object),
}),
);
expect(store.getState().router).toBeDefined();
});

it('should return a store with router in state', () => {
const store = configureAppStore(history);
expect(store.getState().router).toBeDefined();
it('should return an empty store', () => {
const store = configureAppStore();
expect(store.getState()).toBeUndefined();
});
});
13 changes: 7 additions & 6 deletions internals/startingTemplate/src/store/__tests__/reducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import { createReducer } from '../reducers';
import { Reducer } from '@reduxjs/toolkit';

describe('reducer', () => {
it('should include router in reducer', () => {
const reducer = createReducer({}) as Reducer<any, any>;
const state = reducer({}, '');
expect(state.router).toBeDefined();
});

it('should inject reducers', () => {
const dummyReducer = (s = {}, a) => 'dummyResult';
const reducer = createReducer({ test: dummyReducer } as any) as Reducer<
Expand All @@ -17,4 +11,11 @@ describe('reducer', () => {
const state = reducer({}, '');
expect(state.test).toBe('dummyResult');
});

it('should return identity reducers when empty', () => {
const reducer = createReducer() as Reducer<any, any>;
const state = { a: 1 };
const newState = reducer(state, '');
expect(newState).toBe(state);
});
});
19 changes: 4 additions & 15 deletions internals/startingTemplate/src/store/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@
* Create the store with dynamic reducers
*/

import {
configureStore,
getDefaultMiddleware,
Middleware,
} from '@reduxjs/toolkit';
import { routerMiddleware } from 'connected-react-router';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { createInjectorsEnhancer, forceReducerReload } from 'redux-injectors';
import createSagaMiddleware from 'redux-saga';
import { History } from 'history';

import { createReducer } from './reducers';

export function configureAppStore(history?: History) {
export function configureAppStore() {
const reduxSagaMonitorOptions = {};
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
const { run: runSaga } = sagaMiddleware;

// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware] as Middleware[];
if (history) {
middlewares.push(routerMiddleware(history));
}
// Create the store with saga middleware
const middlewares = [sagaMiddleware];

const enhancers = [
createInjectorsEnhancer({
Expand Down
18 changes: 9 additions & 9 deletions internals/startingTemplate/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
* Combine all reducers in this file and export the combined reducers.
*/

import { combineReducers, Reducer, AnyAction } from '@reduxjs/toolkit';
import { connectRouter, RouterState } from 'connected-react-router';
import { combineReducers } from '@reduxjs/toolkit';

import { history } from 'utils/history';
import { InjectedReducersType } from 'utils/types/injector-typings';

/**
* Merges the main reducer with the router state and dynamically injected reducers
*/
export function createReducer(injectedReducers: InjectedReducersType = {}) {
const rootReducer = combineReducers({
...injectedReducers,
router: connectRouter(history) as Reducer<RouterState, AnyAction>,
});

return rootReducer;
// Initially we don't have any injectedReducers, so returning identity function to avoid the error
if (Object.keys(injectedReducers).length === 0) {
return state => state;
} else {
return combineReducers({
...injectedReducers,
});
}
}
2 changes: 0 additions & 2 deletions internals/startingTemplate/src/types/RootState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { RouterState } from 'connected-react-router';
// [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly

/*
Because the redux-injectors injects your reducers asynchronously somewhere in your code
You have to declare them here manually
*/
export interface RootState {
router?: RouterState;
// [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
}
2 changes: 1 addition & 1 deletion internals/startingTemplate/src/utils/@reduxjs/toolkit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
CreateSliceOptions,
} from '@reduxjs/toolkit';

/* Wrap createClice with stricter Name options */
/* Wrap createSlice with stricter Name options */

/* istanbul ignore next */
export const createSlice = <
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RootState } from 'types';
import { Reducer, AnyAction } from 'redux';
import { Saga } from 'redux-saga';
import { SagaInjectionModes } from 'redux-injectors';
import { Reducer, AnyAction } from '@reduxjs/toolkit';

type RequiredRootState = Required<RootState>;

Expand Down
8 changes: 0 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
},
"dependencies": {
"@reduxjs/toolkit": "1.3.2",
"connected-react-router": "6.8.0",
"fontfaceobserver": "2.1.0",
"history": "4.10.1",
"i18next": "19.3.4",
Expand Down
4 changes: 2 additions & 2 deletions src/app/__tests__/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<App /> should render and match the snapshot 1`] = `
<React.Fragment>
<BrowserRouter>
<Helmet
defaultTitle="React Boilerplate"
defer={true}
Expand All @@ -24,5 +24,5 @@ exports[`<App /> should render and match the snapshot 1`] = `
/>
</Switch>
<UNDEFINED />
</React.Fragment>
</BrowserRouter>
`;
6 changes: 3 additions & 3 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { Switch, Route } from 'react-router-dom';
import { Switch, Route, BrowserRouter } from 'react-router-dom';

import { GlobalStyle } from '../styles/global-styles';

Expand All @@ -17,7 +17,7 @@ import { NotFoundPage } from './containers/NotFoundPage/Loadable';

export function App() {
return (
<>
<BrowserRouter>
<Helmet
titleTemplate="%s - React Boilerplate"
defaultTitle="React Boilerplate"
Expand All @@ -30,6 +30,6 @@ export function App() {
<Route component={NotFoundPage} />
</Switch>
<GlobalStyle />
</>
</BrowserRouter>
);
}
21 changes: 8 additions & 13 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import 'react-app-polyfill/stable';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import FontFaceObserver from 'fontfaceobserver';
import * as serviceWorker from 'serviceWorker';
import { history } from 'utils/history';

import 'sanitize.css/sanitize.css';

Expand All @@ -38,24 +36,21 @@ openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
});

// Create redux store with history
const store = configureAppStore(history);
const store = configureAppStore();
const MOUNT_NODE = document.getElementById('root') as HTMLElement;

interface Props {
Component: typeof App;
}
const ConnectedApp = ({ Component }: Props) => (
<Provider store={store}>
<ConnectedRouter history={history}>
<ThemeProvider>
<HelmetProvider>
<React.StrictMode>
<Component />
</React.StrictMode>
</HelmetProvider>
</ThemeProvider>
</ConnectedRouter>
<ThemeProvider>
<HelmetProvider>
<React.StrictMode>
<Component />
</React.StrictMode>
</HelmetProvider>
</ThemeProvider>
</Provider>
);

Expand Down
Loading

0 comments on commit f6a0350

Please sign in to comment.