diff --git a/docs/tutorials/basic-tutorial.md b/docs/tutorials/basic-tutorial.md index be5333e3d..9efb17e0a 100644 --- a/docs/tutorials/basic-tutorial.md +++ b/docs/tutorials/basic-tutorial.md @@ -9,7 +9,7 @@ hide_title: true Welcome to Redux Toolkit ! This tutorial will show you the basic functions that are included with Redux Toolkit (also known as "RTK" for short). -This tutorial assumes that you are already familiar with the concepts of the core Redux library, as well as how to use it with React. If you aren't, please take some time to read through the [Redux docs](https://redux.js.org) and [React-Redux docs](https://react-redux.js.org) first, as the explanations here focus on how RTK usage differs from "typical" Redux code. +This tutorial assumes that you are already familiar with the concepts of the core [Redux](https://redux.js.org) library, as well as how to use it with [React](https://reactjs.org). If you aren't, please take some time to read through the [Redux docs](https://redux.js.org) and [React-Redux docs](https://react-redux.js.org) first, as the explanations here focus on how RTK usage differs from "typical" Redux code. ## Introduction: Writing a Counter Application diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 8cc880649..b63f169c1 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -13,6 +13,7 @@ import { current } from 'immer'; import { DeepPartial } from 'redux'; import { Dispatch } from 'redux'; import { Draft } from 'immer'; +import { freeze } from 'immer'; import { Middleware } from 'redux'; import { OutputParametricSelector } from 'reselect'; import { OutputSelector } from 'reselect'; @@ -271,6 +272,8 @@ export interface EntityStateAdapter { // @public (undocumented) export function findNonSerializableValue(value: unknown, path?: ReadonlyArray, isSerializable?: (value: unknown) => boolean, getEntries?: (value: unknown) => [string, any][], ignoredPaths?: string[]): NonSerializableValue | false; +export { freeze } + // @public export function getDefaultMiddleware = { thunk: true; diff --git a/package-lock.json b/package-lock.json index dbdfd60dc..93704a4d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4602,9 +4602,9 @@ "dev": true }, "immer": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.3.tgz", - "integrity": "sha512-1f/jaF27WVb3X5Tk1AmOYZE+JZgMl50pIS5I8e/Je+HpYHjHwPfzm6dTEvOAUi6jq+/anFXev7agoOgJaL5RFw==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.0.tgz", + "integrity": "sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg==" }, "import-fresh": { "version": "3.2.1", @@ -4741,9 +4741,9 @@ } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "invariant": { @@ -7641,9 +7641,9 @@ "dev": true }, "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "dev": true, "requires": { "glob": "^7.0.0", diff --git a/package.json b/package.json index d58d55a26..27f24eb19 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "src" ], "dependencies": { - "immer": "^7.0.3", + "immer": "^8.0.0", "redux": "^4.0.0", "redux-thunk": "^2.3.0", "reselect": "^4.0.0" diff --git a/src/createReducer.test.ts b/src/createReducer.test.ts index 780a2004e..b2e7c0abf 100644 --- a/src/createReducer.test.ts +++ b/src/createReducer.test.ts @@ -50,6 +50,48 @@ describe('createReducer', () => { behavesLikeReducer(todosReducer) }) + describe('Immer in a production environment', () => { + let originalNodeEnv = process.env.NODE_ENV + + beforeEach(() => { + jest.resetModules() + process.env.NODE_ENV = 'production' + }) + + afterEach(() => { + process.env.NODE_ENV = originalNodeEnv + }) + + test('Freezes data in production', () => { + const { createReducer } = require('./createReducer') + const addTodo: AddTodoReducer = (state, action) => { + const { newTodo } = action.payload + state.push({ ...newTodo, completed: false }) + } + + const toggleTodo: ToggleTodoReducer = (state, action) => { + const { index } = action.payload + const todo = state[index] + todo.completed = !todo.completed + } + + const todosReducer = createReducer([] as TodoState, { + ADD_TODO: addTodo, + TOGGLE_TODO: toggleTodo + }) + + const result = todosReducer([], { + type: 'ADD_TODO', + payload: { text: 'Buy milk' } + }) + + const mutateStateOutsideReducer = () => (result[0].text = 'edited') + expect(mutateStateOutsideReducer).toThrowError( + 'Cannot add property text, object is not extensible' + ) + }) + }) + describe('given pure reducers with immutable updates', () => { const addTodo: AddTodoReducer = (state, action) => { const { newTodo } = action.payload diff --git a/src/index.ts b/src/index.ts index 7b0747447..2385c7ded 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { enableES5 } from 'immer' export * from 'redux' -export { default as createNextState, Draft, current } from 'immer' +export { default as createNextState, Draft, current, freeze } from 'immer' export { createSelector, Selector,