Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supports immer #544

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Main features

:muscle: Supports TypeScript

:gem: Supports [Immer.js](https://immerjs.github.io/immer/)


Installation
-----------
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"posttest": "npm run lint"
},
"dependencies": {
"prop-types": "^15.7.2",
"lodash.isequalwith": "^4.4.0"
"immer": "^9.0.6",
"lodash.isequalwith": "^4.4.0",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"history": "^4.7.2",
Expand Down
11 changes: 11 additions & 0 deletions src/immer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import createConnectedRouter from "./ConnectedRouter"
import createConnectRouter from "./reducer-immer"
import createSelectors from "./selectors"
import immerStructure from "./structure/plain"

export { LOCATION_CHANGE, CALL_HISTORY_METHOD, onLocationChanged, push, replace, go, goBack, goForward, routerActions } from "./actions"
export { default as routerMiddleware } from "./middleware"

export const ConnectedRouter = /*#__PURE__*/ createConnectedRouter(immerStructure)
export const connectRouter = /*#__PURE__*/ createConnectRouter()
export const { getLocation, getAction, getHash, getRouter, getSearch, createMatchSelector } = /*#__PURE__*/ createSelectors(immerStructure)
89 changes: 89 additions & 0 deletions src/reducer-immer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { LOCATION_CHANGE } from './actions'
import produce from 'immer'

/**
* Adds query to location.
* Utilises the search prop of location to construct query.
*/
const injectQuery = (location) => {
if (location && location.query) {
// Don't inject query if it already exists in history
return location
}

const searchQuery = location && location.search

if (typeof searchQuery !== 'string' || searchQuery.length === 0) {
return {
...location,
query: {}
}
}

// Ignore the `?` part of the search string e.g. ?username=codejockie
const search = searchQuery.substring(1)
// Split the query string on `&` e.g. ?username=codejockie&name=Kennedy
const queries = search.split('&')
// Contruct query
const query = queries.reduce((acc, currentQuery) => {
// Split on `=`, to get key and value
const [queryKey, queryValue] = currentQuery.split('=')
return {
...acc,
[queryKey]: queryValue
}
}, {})

return {
...location,
query
}
}

const createConnectRouter = () => {

const createRouterReducer = (history) => {
const initialRouterState = {
location: injectQuery(history.location),
action: history.action,
}

/*
* This reducer will update the state with the most recent location history
* has transitioned to.
*/
// return (state = initialRouterState, { type, payload } = {}) => {
// if (type === LOCATION_CHANGE) {
// const { location, action, isFirstRendering } = payload
// // Don't update the state ref for the first rendering
// // to prevent the double-rendering issue on initilization
// return isFirstRendering
// ? state
// : merge(state, { location: fromJS(injectQuery(location)), action })
// }

// return state
// }


return produce((draft, { type, payload } = {}) => {
if (type === LOCATION_CHANGE) {
const { location, action, isFirstRendering } = payload

// Don't update the state ref for the first rendering
// to prevent the double-rendering issue on initilization
if(!isFirstRendering){
draft.action=action
draft.location=injectQuery(location)
}
}
return draft
}, initialRouterState)


}

return createRouterReducer
}

export default createConnectRouter
86 changes: 86 additions & 0 deletions test/reducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { combineReducers } from 'redux'
import { combineReducers as combineReducersImmutable } from 'redux-immutable'
import { combineReducers as combineReducersSeamlessImmutable } from 'redux-seamless-immutable'
import Immutable from 'immutable'
import produce from 'immer'
import { LOCATION_CHANGE, connectRouter } from '../src'
import { connectRouter as connectRouterImmutable } from '../src/immutable'
import {connectRouter as connectRouterImmer } from "../src/immer"
import { connectRouter as connectRouterSeamlessImmutable } from '../src/seamless-immutable'

describe('connectRouter', () => {
Expand Down Expand Up @@ -331,4 +333,88 @@ describe('connectRouter', () => {
expect(nextState).toBe(currentState)
})
})


describe('with immer structure', () => {
it('creates new root reducer with router reducer inside', () => {
const mockReducer =produce((draft, action) => {
switch (action.type) {
default:
return draft
}
},{})
const rootReducer = combineReducers({
mock: mockReducer,
router: connectRouterImmer(mockHistory)
})

const currentState = {
mock: {},
router: {
location: {
pathname: '/',
search: '',
hash: '',
},
action: 'POP',
},
}
const action = {
type: LOCATION_CHANGE,
payload: {
location: {
pathname: '/path/to/somewhere',
search: '?query=test',
hash: '',
},
action: 'PUSH',
}
}
const nextState = rootReducer(currentState, action)
const expectedState = {
mock: {},
router: {
location: {
pathname: '/path/to/somewhere',
search: '?query=test',
hash: '',
query: { query: 'test' }
},
action: 'PUSH',
},
}
expect(nextState).toEqual(expectedState)
})

it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => {
const rootReducer = combineReducers({
router: connectRouter(mockHistory)
})
const currentState = {
router: {
location: {
pathname: '/',
search: '',
hash: '',
},
action: 'POP',
},
}
const action = {
type: LOCATION_CHANGE,
payload: {
location: {
pathname: '/',
search: '',
hash: '',
},
action: 'POP',
isFirstRendering: true,
}
}
const nextState = rootReducer(currentState, action)
expect(nextState).toBe(currentState)
})
})

})
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3595,6 +3595,11 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==

immer@^9.0.6:
version "9.0.6"
resolved "https://registry.nlark.com/immer/download/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73"
integrity sha1-epa/JnTQbIFD4yfL9zU5OI3fGnM=

immutable@^3.8.1:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
Expand Down