Skip to content

Commit 9d5adf1

Browse files
committed
Remove toString override from action creators, in favour of explicit .type field.
1 parent e9dfc93 commit 9d5adf1

File tree

6 files changed

+44
-76
lines changed

6 files changed

+44
-76
lines changed

docs/api/createAction.mdx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const action = increment(3)
3131
// { type: 'counter/increment', payload: 3 }
3232
```
3333
34-
The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action. Also, the action creator overrides [toString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) so that the action type becomes its string representation.
34+
The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action.
3535
3636
```ts
3737
import { createAction } from '@reduxjs/toolkit'
@@ -44,10 +44,7 @@ let action = increment()
4444
action = increment(3)
4545
// returns { type: 'counter/increment', payload: 3 }
4646

47-
console.log(increment.toString())
48-
// 'counter/increment'
49-
50-
console.log(`The action type is: ${increment}`)
47+
console.log(`The action type is: ${increment.type}`)
5148
// 'The action type is: counter/increment'
5249
```
5350
@@ -89,7 +86,7 @@ If provided, all arguments from the action creator will be passed to the prepare
8986
9087
## Usage with createReducer()
9188
92-
Because of their `toString()` override, action creators returned by `createAction()` can be used directly as keys for the case reducers passed to [createReducer()](createReducer.mdx).
89+
Action creators can be passed directly to `addCase` in a [createReducer()](createReducer.mdx) build callback.
9390
9491
```ts
9592
import { createAction, createReducer } from '@reduxjs/toolkit'
@@ -103,21 +100,23 @@ const counterReducer = createReducer(0, (builder) => {
103100
})
104101
```
105102
103+
<!-- TODO: how do we handle this? -->
104+
106105
## Non-String Action Types
107106
108107
In principle, Redux lets you use any kind of value as an action type. Instead of strings, you could theoretically use numbers, [symbols](https://developer.mozilla.org/en-US/docs/Glossary/Symbol), or anything else ([although it's recommended that the value should at least be serializable](https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)).
109108
110-
However, Redux Toolkit rests on the assumption that you use string action types. Specifically, some of its features rely on the fact that with strings, the `toString()` method of an `createAction()` action creator returns the matching action type. This is not the case for non-string action types because `toString()` will return the string-converted type value rather than the type itself.
109+
However, Redux Toolkit rests on the assumption that you use string action types.
111110
112111
```js
113112
const INCREMENT = Symbol('increment')
114113
const increment = createAction(INCREMENT)
115114

116-
increment.toString()
115+
increment.type.toString()
117116
// returns the string 'Symbol(increment)',
118117
// not the INCREMENT symbol itself
119118

120-
increment.toString() === INCREMENT
119+
increment.type.toString() === INCREMENT
121120
// false
122121
```
123122

docs/introduction/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Redux Toolkit includes these APIs:
9494

9595
- [`configureStore()`](../api/configureStore.mdx): wraps `createStore` to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes `redux-thunk` by default, and enables use of the Redux DevTools Extension.
9696
- [`createReducer()`](../api/createReducer.mdx): that lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/immerjs/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`.
97-
- [`createAction()`](../api/createAction.mdx): generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant.
97+
- [`createAction()`](../api/createAction.mdx): generates an action creator function for the given action type string.
9898
- [`createSlice()`](../api/createSlice.mdx): accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.
9999
- [`createAsyncThunk`](../api/createAsyncThunk.mdx): accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/fulfilled/rejected` action types based on that promise
100100
- [`createEntityAdapter`](../api/createEntityAdapter.mdx): generates a set of reusable reducers and selectors to manage normalized data in the store

docs/usage/usage-guide.md

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -279,24 +279,16 @@ addTodo({ text: 'Buy milk' })
279279

280280
### Using Action Creators as Action Types
281281

282-
Redux reducers need to look for specific action types to determine how they should update their state. Normally, this is done by defining action type strings and action creator functions separately. Redux Toolkit `createAction` function uses a couple tricks to make this easier.
283-
284-
First, `createAction` overrides the `toString()` method on the action creators it generates. **This means that the action creator itself can be used as the "action type" reference in some places**, such as the keys provided to `builder.addCase` or the `createReducer` object notation.
285-
286-
Second, the action type is also defined as a `type` field on the action creator.
282+
Redux reducers need to look for specific action types to determine how they should update their state. Normally, this is done by defining action type strings and action creator functions separately. Redux Toolkit `createAction` function make this easier, by defining the action type as a `type` field on the action creator.
287283

288284
```js
289285
const actionCreator = createAction('SOME_ACTION_TYPE')
290286

291-
console.log(actionCreator.toString())
292-
// "SOME_ACTION_TYPE"
293-
294287
console.log(actionCreator.type)
295288
// "SOME_ACTION_TYPE"
296289

297290
const reducer = createReducer({}, (builder) => {
298-
// actionCreator.toString() will automatically be called here
299-
// also, if you use TypeScript, the action type will be correctly inferred
291+
// if you use TypeScript, the action type will be correctly inferred
300292
builder.addCase(actionCreator, (state, action) => {})
301293

302294
// Or, you can reference the .type field:
@@ -307,7 +299,7 @@ const reducer = createReducer({}, (builder) => {
307299

308300
This means you don't have to write or use a separate action type variable, or repeat the name and value of an action type like `const SOME_ACTION_TYPE = "SOME_ACTION_TYPE"`.
309301

310-
Unfortunately, the implicit conversion to a string doesn't happen for switch statements. If you want to use one of these action creators in a switch statement, you need to call `actionCreator.toString()` yourself:
302+
If you want to use one of these action creators in a switch statement, you need to call `actionCreator.type` yourself:
311303

312304
```js
313305
const actionCreator = createAction('SOME_ACTION_TYPE')
@@ -319,19 +311,13 @@ const reducer = (state = {}, action) => {
319311
break
320312
}
321313
// CORRECT: this will work as expected
322-
case actionCreator.toString(): {
323-
break
324-
}
325-
// CORRECT: this will also work right
326314
case actionCreator.type: {
327315
break
328316
}
329317
}
330318
}
331319
```
332320

333-
If you are using Redux Toolkit with TypeScript, note that the TypeScript compiler may not accept the implicit `toString()` conversion when the action creator is used as an object key. In that case, you may need to either manually cast it to a string (`actionCreator as string`), or use the `.type` field as the key.
334-
335321
## Creating Slices of State
336322

337323
Redux state is typically organized into "slices", defined by the reducers that are passed to `combineReducers`:
@@ -619,17 +605,17 @@ A typical implementation might look like:
619605

620606
```js
621607
const getRepoDetailsStarted = () => ({
622-
type: "repoDetails/fetchStarted"
608+
type: 'repoDetails/fetchStarted',
623609
})
624610
const getRepoDetailsSuccess = (repoDetails) => ({
625-
type: "repoDetails/fetchSucceeded",
626-
payload: repoDetails
611+
type: 'repoDetails/fetchSucceeded',
612+
payload: repoDetails,
627613
})
628614
const getRepoDetailsFailed = (error) => ({
629-
type: "repoDetails/fetchFailed",
630-
error
615+
type: 'repoDetails/fetchFailed',
616+
error,
631617
})
632-
const fetchIssuesCount = (org, repo) => async dispatch => {
618+
const fetchIssuesCount = (org, repo) => async (dispatch) => {
633619
dispatch(getRepoDetailsStarted())
634620
try {
635621
const repoDetails = await getRepoDetails(org, repo)
@@ -1118,11 +1104,11 @@ It is also strongly recommended to blacklist any api(s) that you have configured
11181104

11191105
```ts
11201106
const persistConfig = {
1121-
key: "root",
1107+
key: 'root',
11221108
version: 1,
11231109
storage,
11241110
blacklist: [pokemonApi.reducerPath],
1125-
};
1111+
}
11261112
```
11271113

11281114
See [Redux Toolkit #121: How to use this with Redux-Persist?](https://github.com/reduxjs/redux-toolkit/issues/121) and [Redux-Persist #988: non-serializable value error](https://github.com/rt2zz/redux-persist/issues/988#issuecomment-552242978) for further discussion.

packages/toolkit/src/createAction.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,7 @@ export type PayloadActionCreator<
224224
/**
225225
* A utility function to create an action creator for the given action type
226226
* string. The action creator accepts a single argument, which will be included
227-
* in the action object as a field called payload. The action creator function
228-
* will also have its toString() overridden so that it returns the action type,
229-
* allowing it to be used in reducer logic that is looking for that action type.
227+
* in the action object as a field called payload.
230228
*
231229
* @param type The action type to use for created actions.
232230
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
@@ -241,9 +239,7 @@ export function createAction<P = void, T extends string = string>(
241239
/**
242240
* A utility function to create an action creator for the given action type
243241
* string. The action creator accepts a single argument, which will be included
244-
* in the action object as a field called payload. The action creator function
245-
* will also have its toString() overridden so that it returns the action type,
246-
* allowing it to be used in reducer logic that is looking for that action type.
242+
* in the action object as a field called payload.
247243
*
248244
* @param type The action type to use for created actions.
249245
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
@@ -277,8 +273,6 @@ export function createAction(type: string, prepareAction?: Function): any {
277273
return { type, payload: args[0] }
278274
}
279275

280-
actionCreator.toString = () => `${type}`
281-
282276
actionCreator.type = type
283277

284278
actionCreator.match = (action: Action<unknown>): action is PayloadAction =>
@@ -328,22 +322,6 @@ function isValidKey(key: string) {
328322
return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1
329323
}
330324

331-
/**
332-
* Returns the action type of the actions created by the passed
333-
* `createAction()`-generated action creator (arbitrary action creators
334-
* are not supported).
335-
*
336-
* @param action The action creator whose action type to get.
337-
* @returns The action type used by the action creator.
338-
*
339-
* @public
340-
*/
341-
export function getType<T extends string>(
342-
actionCreator: PayloadActionCreator<any, T>
343-
): T {
344-
return `${actionCreator}` as T
345-
}
346-
347325
// helper types for more readable typings
348326

349327
type IfPrepareActionMethodProvided<

packages/toolkit/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export type { DevToolsEnhancerOptions } from './devtoolsExtension'
3131
export {
3232
// js
3333
createAction,
34-
getType,
3534
isAction,
3635
isActionCreator,
3736
isFSA as isFluxStandardAction,

packages/toolkit/src/tests/createAction.test.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createAction, getType, isAction } from '@reduxjs/toolkit'
1+
import { createAction, isAction, isActionCreator } from '@reduxjs/toolkit'
22

33
describe('createAction', () => {
44
it('should create an action', () => {
@@ -9,13 +9,6 @@ describe('createAction', () => {
99
})
1010
})
1111

12-
describe('when stringifying action', () => {
13-
it('should return the action type', () => {
14-
const actionCreator = createAction('A_TYPE')
15-
expect(`${actionCreator}`).toEqual('A_TYPE')
16-
})
17-
})
18-
1912
describe('when passing a prepareAction method only returning a payload', () => {
2013
it('should use the payload returned from the prepareAction method', () => {
2114
const actionCreator = createAction('A_TYPE', (a: number) => ({
@@ -122,12 +115,13 @@ describe('createAction', () => {
122115
})
123116
})
124117

118+
const actionCreator = createAction('anAction')
119+
120+
class Action {
121+
type = 'totally an action'
122+
}
125123
describe('isAction', () => {
126124
it('should only return true for plain objects with a type property', () => {
127-
const actionCreator = createAction('anAction')
128-
class Action {
129-
type = 'totally an action'
130-
}
131125
const testCases: [action: unknown, expected: boolean][] = [
132126
[{ type: 'an action' }, true],
133127
[{ type: 'more props', extra: true }, true],
@@ -143,9 +137,21 @@ describe('isAction', () => {
143137
})
144138
})
145139

146-
describe('getType', () => {
147-
it('should return the action type', () => {
148-
const actionCreator = createAction('A_TYPE')
149-
expect(getType(actionCreator)).toEqual('A_TYPE')
140+
describe('isActionCreator', () => {
141+
it('should only return true for action creators', () => {
142+
expect(isActionCreator(actionCreator)).toBe(true)
143+
const notActionCreators = [
144+
{ type: 'an action' },
145+
{ type: 'more props', extra: true },
146+
actionCreator(),
147+
Promise.resolve({ type: 'an action' }),
148+
new Action(),
149+
false,
150+
'a string',
151+
false,
152+
]
153+
for (const notActionCreator of notActionCreators) {
154+
expect(isActionCreator(notActionCreator)).toBe(false)
155+
}
150156
})
151157
})

0 commit comments

Comments
 (0)