Repository: reduxjs/redux-toolkit
Pull request #519 (2020-04-23 17:58:58)

docs: fix IssueDetailsPage.tsx diff code in Advanced Tutorial

netlify[bot]: Deploy preview for *redux-starter-kit-docs* ready!

Built with commit 62114962ade810b675e38c0dd0984eac5b62c031

https://deploy-preview-519--redux-starter-kit-docs.netlify.app
codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/reduxjs/redux-toolkit/pr/519/builds/21949) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 62114962ade810b675e38c0dd0984eac5b62c031:

|Sandbox| Source |
|--|--|
|[kind-cloud-4fjt5](https://codesandbox.io/s/kind-cloud-4fjt5)| Configuration |
|[condescending-nobel-p9rnj](https://codesandbox.io/s/condescending-nobel-p9rnj)| Configuration |
|[unruffled-colden-qeojg](https://codesandbox.io/s/unruffled-colden-qeojg)| Configuration |


Patch:
diff --git a/docs/api/createAction.md b/docs/api/createAction.md
index 0ace5dab..63317bb6 100644
--- a/docs/api/createAction.md
+++ b/docs/api/createAction.md
@@ -54,13 +54,13 @@ By default, the generated action creators accept a single argument, which become
 In many cases, you may want to write additional logic to customize the creation of the `payload` value, such as accepting multiple parameters for the action creator, generating a random ID, or getting the current timestamp. To do this, `createAction` accepts an optional second argument: a "prepare callback" that will be used to construct the payload value.
 
 ```js
-import v4 from 'uuid/v4'
+import { nanoid } from '@reduxjs/toolkit'
 
 const addTodo = createAction('todos/add', function prepare(text) {
   return {
     payload: {
       text,
-      id: v4(),
+      id: nanoid(),
       createdAt: new Date().toISOString()
     }
   }
@@ -72,7 +72,7 @@ console.log(addTodo('Write more docs'))
  *   type: 'todos/add',
  *   payload: {
  *     text: 'Write more docs',
- *     id: '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed',
+ *     id: '4AJvwMSWEHCchcWYga3dj',
  *     createdAt: '2019-10-03T07:53:36.581Z'
  *   }
  * }
diff --git a/docs/tutorials/advanced-tutorial.md b/docs/tutorials/advanced-tutorial.md
index 6346a134..fb484793 100644
--- a/docs/tutorials/advanced-tutorial.md
+++ b/docs/tutorials/advanced-tutorial.md
@@ -1024,7 +1024,6 @@ export const IssueDetailsPage = ({
 - const [issue, setIssue] = useState<Issue | null>(null)
   const [comments, setComments] = useState<Comment[]>([])
 - const [commentsError, setCommentsError] = useState<Error | null>(null)
-+ const [commentsError] = useState<Error | null>(null)
 
 + const dispatch = useDispatch()
 
diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md
index 95def8f7..ab1a5d46 100644
--- a/docs/usage/usage-with-typescript.md
+++ b/docs/usage/usage-with-typescript.md
@@ -233,7 +233,7 @@ createSlice({
 
 You might have noticed that it is not a good idea to pass your `SliceState` type as a generic to `createSlice`. This is due to the fact that in almost all cases, follow-up generic parameters to `createSlice` need to be inferred, and TypeScript cannot mix explicit declaration and inference of generic types within the same "generic block".
 
-The standard approach is to declare an interface or type for your state, create an initial state value that uses that type, and pass the initial state value to `createSlice. You can also use the construct`initialState: myInitialState as SliceState`.
+The standard approach is to declare an interface or type for your state, create an initial state value that uses that type, and pass the initial state value to `createSlice`. You can also use the construct `initialState: myInitialState as SliceState`.
 
 ```ts {1,4,8,15}
 type SliceState = { state: 'loading' } | { state: 'finished'; data: string }
diff --git a/src/entities/state_adapter.ts b/src/entities/state_adapter.ts
index a0008917..75895118 100644
--- a/src/entities/state_adapter.ts
+++ b/src/entities/state_adapter.ts
@@ -1,7 +1,21 @@
 import createNextState, { isDraft } from 'immer'
-import { EntityState } from './models'
+import { EntityState, PreventAny } from './models'
 import { PayloadAction, isFSA } from '../createAction'
 
+export function createSingleArgumentStateOperator<V>(
+  mutator: (state: EntityState<V>) => void
+) {
+  const operator = createStateOperator((_: undefined, state: EntityState<V>) =>
+    mutator(state)
+  )
+
+  return function operation<S extends EntityState<V>>(
+    state: PreventAny<S, V>
+  ): S {
+    return operator(state as S, undefined)
+  }
+}
+
 export function createStateOperator<V, R>(
   mutator: (arg: R, state: EntityState<V>) => void
 ) {
diff --git a/src/entities/unsorted_state_adapter.ts b/src/entities/unsorted_state_adapter.ts
index 8d41f242..13f20733 100644
--- a/src/entities/unsorted_state_adapter.ts
+++ b/src/entities/unsorted_state_adapter.ts
@@ -5,7 +5,10 @@ import {
   Update,
   EntityId
 } from './models'
-import { createStateOperator } from './state_adapter'
+import {
+  createStateOperator,
+  createSingleArgumentStateOperator
+} from './state_adapter'
 import { selectIdValue } from './utils'
 
 export function createUnsortedStateAdapter<T>(
@@ -64,8 +67,8 @@ export function createUnsortedStateAdapter<T>(
     }
   }
 
-  function removeAll(state: R): any {
-    return Object.assign({}, state, {
+  function removeAllMutably(state: R): void {
+    Object.assign(state, {
       ids: [],
       entities: {}
     })
@@ -156,7 +159,7 @@ export function createUnsortedStateAdapter<T>(
   }
 
   return {
-    removeAll,
+    removeAll: createSingleArgumentStateOperator(removeAllMutably),
     addOne: createStateOperator(addOneMutably),
     addMany: createStateOperator(addManyMutably),
     setAll: createStateOperator(setAllMutably),


Test patch:
diff --git a/src/entities/sorted_state_adapter.test.ts b/src/entities/sorted_state_adapter.test.ts
index 104375a3..daa45b7b 100644
--- a/src/entities/sorted_state_adapter.test.ts
+++ b/src/entities/sorted_state_adapter.test.ts
@@ -7,6 +7,7 @@ import {
   AClockworkOrange,
   AnimalFarm
 } from './fixtures/book'
+import { createNextState } from '..'
 
 describe('Sorted State Adapter', () => {
   let adapter: EntityStateAdapter<BookModel>
@@ -436,4 +437,263 @@ describe('Sorted State Adapter', () => {
       }
     })
   })
+
+  describe('can be used mutably when wrapped in createNextState', () => {
+    test('removeAll', () => {
+      const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
+      const result = createNextState(withTwo, draft => {
+        adapter.removeAll(draft)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {},
+          "ids": Array [],
+        }
+      `)
+    })
+
+    test('addOne', () => {
+      const result = createNextState(state, draft => {
+        adapter.addOne(draft, TheGreatGatsby)
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('addMany', () => {
+      const result = createNextState(state, draft => {
+        adapter.addMany(draft, [TheGreatGatsby, AnimalFarm])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "af",
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('setAll', () => {
+      const result = createNextState(state, draft => {
+        adapter.setAll(draft, [TheGreatGatsby, AnimalFarm])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "af",
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('updateOne', () => {
+      const withOne = adapter.addOne(state, TheGreatGatsby)
+      const changes = { title: 'A New Hope' }
+      const result = createNextState(withOne, draft => {
+        adapter.updateOne(draft, {
+          id: TheGreatGatsby.id,
+          changes
+        })
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('updateMany', () => {
+      const firstChange = { title: 'First Change' }
+      const secondChange = { title: 'Second Change' }
+      const withMany = adapter.setAll(state, [TheGreatGatsby, AClockworkOrange])
+
+      const result = createNextState(withMany, draft => {
+        adapter.updateMany(draft, [
+          { id: TheGreatGatsby.id, changes: firstChange },
+          { id: AClockworkOrange.id, changes: secondChange }
+        ])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "aco": Object {
+              "id": "aco",
+              "title": "Second Change",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "First Change",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "aco",
+          ],
+        }
+      `)
+    })
+
+    test('upsertOne (insert)', () => {
+      const result = createNextState(state, draft => {
+        adapter.upsertOne(draft, TheGreatGatsby)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('upsertOne (update)', () => {
+      const withOne = adapter.upsertOne(state, TheGreatGatsby)
+      const result = createNextState(withOne, draft => {
+        adapter.upsertOne(draft, {
+          id: TheGreatGatsby.id,
+          title: 'A New Hope'
+        })
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('upsertMany', () => {
+      const withOne = adapter.upsertOne(state, TheGreatGatsby)
+      const result = createNextState(withOne, draft => {
+        adapter.upsertMany(draft, [
+          {
+            id: TheGreatGatsby.id,
+            title: 'A New Hope'
+          },
+          AnimalFarm
+        ])
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('removeOne', () => {
+      const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
+      const result = createNextState(withTwo, draft => {
+        adapter.removeOne(draft, TheGreatGatsby.id)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+          },
+          "ids": Array [
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('removeMany', () => {
+      const withThree = adapter.addMany(state, [
+        TheGreatGatsby,
+        AnimalFarm,
+        AClockworkOrange
+      ])
+      const result = createNextState(withThree, draft => {
+        adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id])
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "aco": Object {
+              "id": "aco",
+              "title": "A Clockwork Orange",
+            },
+          },
+          "ids": Array [
+            "aco",
+          ],
+        }
+      `)
+    })
+  })
 })
diff --git a/src/entities/unsorted_state_adapter.test.ts b/src/entities/unsorted_state_adapter.test.ts
index 87a4ad62..44f5d696 100644
--- a/src/entities/unsorted_state_adapter.test.ts
+++ b/src/entities/unsorted_state_adapter.test.ts
@@ -6,6 +6,7 @@ import {
   AClockworkOrange,
   AnimalFarm
 } from './fixtures/book'
+import { createNextState } from '..'
 
 describe('Unsorted State Adapter', () => {
   let adapter: EntityStateAdapter<BookModel>
@@ -352,4 +353,263 @@ describe('Unsorted State Adapter', () => {
       }
     })
   })
+
+  describe('can be used mutably when wrapped in createNextState', () => {
+    test('removeAll', () => {
+      const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
+      const result = createNextState(withTwo, draft => {
+        adapter.removeAll(draft)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {},
+          "ids": Array [],
+        }
+      `)
+    })
+
+    test('addOne', () => {
+      const result = createNextState(state, draft => {
+        adapter.addOne(draft, TheGreatGatsby)
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('addMany', () => {
+      const result = createNextState(state, draft => {
+        adapter.addMany(draft, [TheGreatGatsby, AnimalFarm])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('setAll', () => {
+      const result = createNextState(state, draft => {
+        adapter.setAll(draft, [TheGreatGatsby, AnimalFarm])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('updateOne', () => {
+      const withOne = adapter.addOne(state, TheGreatGatsby)
+      const changes = { title: 'A New Hope' }
+      const result = createNextState(withOne, draft => {
+        adapter.updateOne(draft, {
+          id: TheGreatGatsby.id,
+          changes
+        })
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('updateMany', () => {
+      const firstChange = { title: 'First Change' }
+      const secondChange = { title: 'Second Change' }
+      const withMany = adapter.setAll(state, [TheGreatGatsby, AClockworkOrange])
+
+      const result = createNextState(withMany, draft => {
+        adapter.updateMany(draft, [
+          { id: TheGreatGatsby.id, changes: firstChange },
+          { id: AClockworkOrange.id, changes: secondChange }
+        ])
+      })
+
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "aco": Object {
+              "id": "aco",
+              "title": "Second Change",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "First Change",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "aco",
+          ],
+        }
+      `)
+    })
+
+    test('upsertOne (insert)', () => {
+      const result = createNextState(state, draft => {
+        adapter.upsertOne(draft, TheGreatGatsby)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "The Great Gatsby",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('upsertOne (update)', () => {
+      const withOne = adapter.upsertOne(state, TheGreatGatsby)
+      const result = createNextState(withOne, draft => {
+        adapter.upsertOne(draft, {
+          id: TheGreatGatsby.id,
+          title: 'A New Hope'
+        })
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+          ],
+        }
+      `)
+    })
+
+    test('upsertMany', () => {
+      const withOne = adapter.upsertOne(state, TheGreatGatsby)
+      const result = createNextState(withOne, draft => {
+        adapter.upsertMany(draft, [
+          {
+            id: TheGreatGatsby.id,
+            title: 'A New Hope'
+          },
+          AnimalFarm
+        ])
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+            "tgg": Object {
+              "id": "tgg",
+              "title": "A New Hope",
+            },
+          },
+          "ids": Array [
+            "tgg",
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('removeOne', () => {
+      const withTwo = adapter.addMany(state, [TheGreatGatsby, AnimalFarm])
+      const result = createNextState(withTwo, draft => {
+        adapter.removeOne(draft, TheGreatGatsby.id)
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "af": Object {
+              "id": "af",
+              "title": "Animal Farm",
+            },
+          },
+          "ids": Array [
+            "af",
+          ],
+        }
+      `)
+    })
+
+    test('removeMany', () => {
+      const withThree = adapter.addMany(state, [
+        TheGreatGatsby,
+        AnimalFarm,
+        AClockworkOrange
+      ])
+      const result = createNextState(withThree, draft => {
+        adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id])
+      })
+      expect(result).toMatchInlineSnapshot(`
+        Object {
+          "entities": Object {
+            "aco": Object {
+              "id": "aco",
+              "title": "A Clockwork Orange",
+            },
+          },
+          "ids": Array [
+            "aco",
+          ],
+        }
+      `)
+    })
+  })
 })