diff --git a/examples/action-listener/counter/package.json b/examples/action-listener/counter/package.json index 566062a4f6..f5e5e5b068 100644 --- a/examples/action-listener/counter/package.json +++ b/examples/action-listener/counter/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@reduxjs/toolkit": "^1.6.0-rc.1", - "@rtk-incubator/action-listener-middleware": "^0.6.0", + "@rtk-incubator/action-listener-middleware": "^0.8.0", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", diff --git a/examples/action-listener/counter/src/components/App/App.tsx b/examples/action-listener/counter/src/components/App/App.tsx new file mode 100644 index 0000000000..5779032d6d --- /dev/null +++ b/examples/action-listener/counter/src/components/App/App.tsx @@ -0,0 +1,36 @@ +import React, { useEffect } from 'react' +import { Provider } from 'react-redux' +import type { Unsubscribe } from '@reduxjs/toolkit' +import { setupThemeListeners } from '../../services/theme/listeners' +import { setupCounterListeners } from '../../services/counter/listeners' +import { ChangeThemeForm } from '../ChangeThemeForm/ChangeThemeForm' +import { CounterList } from '../CounterList/CounterList' +import { CreateCounterForm } from '../CreateCounterForm/CreateCounterForm' +import { store, startAppListening } from '../../store' + + +export function App() { + useEffect(() => { + const subscriptions: Unsubscribe[] = [ + setupCounterListeners(startAppListening), + setupThemeListeners(startAppListening), + ] + + return () => subscriptions.forEach((unsubscribe) => unsubscribe()) + }, []) + + return ( + + +
+
+

Counter example

+
+ + + +
+
+
+ ) +} diff --git a/examples/action-listener/counter/src/index.tsx b/examples/action-listener/counter/src/index.tsx index 28724d1316..ededcf300a 100644 --- a/examples/action-listener/counter/src/index.tsx +++ b/examples/action-listener/counter/src/index.tsx @@ -1,29 +1,11 @@ -import React from 'react' import ReactDOM from 'react-dom' import './index.css' -import { Provider } from 'react-redux' import { store } from './store' import { themeActions } from './services/theme/slice' -import { ChangeThemeForm } from './components/ChangeThemeForm/ChangeThemeForm' -import { CounterList } from './components/CounterList/CounterList' -import { CreateCounterForm } from './components/CreateCounterForm/CreateCounterForm' +import { App } from './components/App/App' if (window.matchMedia('(prefers-color-scheme: dark)').matches) { store.dispatch(themeActions.changeColorScheme('dark')) } -ReactDOM.render( - - -
-
-

Counter example

-
- - - -
-
-
, - document.getElementById('root') -) +ReactDOM.render(, document.getElementById('root')) diff --git a/examples/action-listener/counter/src/services/counter/listeners.ts b/examples/action-listener/counter/src/services/counter/listeners.ts index 7d49cc583e..3aa6113493 100644 --- a/examples/action-listener/counter/src/services/counter/listeners.ts +++ b/examples/action-listener/counter/src/services/counter/listeners.ts @@ -1,6 +1,6 @@ import { counterActions, counterSelectors } from './slice' -import { AnyAction, isAllOf, isAnyOf, PayloadAction } from '@reduxjs/toolkit' -import type { AppListenerApi, AppActionListenerMiddleware } from '../../store' +import { AnyAction, isAllOf, isAnyOf, PayloadAction, Unsubscribe } from '@reduxjs/toolkit' +import type { AppListenerEffectAPI, AppStartListening } from '../../store' function shouldStopAsyncTasksOf(id: string) { return isAllOf( @@ -14,7 +14,7 @@ async function onUpdateByPeriodically( { payload: { id, delta }, }: ReturnType, - { dispatch, getState, getOriginalState, condition }: AppListenerApi + { dispatch, getState, getOriginalState, condition }: AppListenerEffectAPI ) { const counter = counterSelectors.selectById(getState(), id) @@ -44,7 +44,7 @@ async function onUpdateAsync( { payload: { id, delta, delayMs }, }: ReturnType, - { condition, dispatch, getState }: AppListenerApi + { condition, dispatch, getState }: AppListenerEffectAPI ) { const counter = counterSelectors.selectById(getState(), id) @@ -70,17 +70,15 @@ async function onUpdateAsync( * }, []); * ``` */ -export function setupCounterListeners( - actionListener: AppActionListenerMiddleware -) { +export function setupCounterListeners(startListening: AppStartListening): Unsubscribe { const subscriptions = [ - actionListener.addListener({ + startListening({ actionCreator: counterActions.updateByPeriodically, - listener: onUpdateByPeriodically, + effect: onUpdateByPeriodically, }), - actionListener.addListener({ + startListening({ actionCreator: counterActions.updateByAsync, - listener: onUpdateAsync, + effect: onUpdateAsync, }), ] diff --git a/examples/action-listener/counter/src/services/theme/listeners.ts b/examples/action-listener/counter/src/services/theme/listeners.ts index 93864d3e6c..b9906f69d4 100644 --- a/examples/action-listener/counter/src/services/theme/listeners.ts +++ b/examples/action-listener/counter/src/services/theme/listeners.ts @@ -1,21 +1,22 @@ import { themeActions } from './slice' -import type { AppActionListenerMiddleware } from '../../store' +import type { AppStartListening } from '../../store' +import { Unsubscribe } from '@reduxjs/toolkit' function onChangeColorScheme( action: ReturnType ) { - if (action.payload === 'light') { - document.documentElement.classList.remove('dark') - } else { - document.documentElement.classList.add('dark') - } + document.documentElement.classList.toggle('dark', action.payload !== 'light') } export function setupThemeListeners( - actionListener: AppActionListenerMiddleware -) { - return actionListener.addListener({ - actionCreator: themeActions.changeColorScheme, - listener: onChangeColorScheme, - }) + startListening: AppStartListening +): Unsubscribe { + const listeners = [ + startListening({ + actionCreator: themeActions.changeColorScheme, + effect: onChangeColorScheme, + }), + ] + + return () => listeners.forEach((unsubscribe) => unsubscribe()) } diff --git a/examples/action-listener/counter/src/store.ts b/examples/action-listener/counter/src/store.ts index dd1a7bd6ab..4c7328299f 100644 --- a/examples/action-listener/counter/src/store.ts +++ b/examples/action-listener/counter/src/store.ts @@ -2,15 +2,15 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' import { configureStore } from '@reduxjs/toolkit' import { counterSlice } from './services/counter/slice' import { - createActionListenerMiddleware, - ActionListenerMiddlewareAPI, - ActionListenerMiddleware, + createListenerMiddleware, + TypedStartListening, + TypedAddListener, + ListenerEffectAPI, + addListener, } from '@rtk-incubator/action-listener-middleware' import { themeSlice } from './services/theme/slice' -import { setupCounterListeners } from './services/counter/listeners' -import { setupThemeListeners } from './services/theme/listeners' -const actionListenerMiddleware = createActionListenerMiddleware({ +const listenerMiddlewareInstance = createListenerMiddleware({ onError: () => console.error, }) @@ -19,7 +19,7 @@ const store = configureStore({ [counterSlice.name]: counterSlice.reducer, [themeSlice.name]: themeSlice.reducer, }, - middleware: (gDM) => gDM().prepend(actionListenerMiddleware), + middleware: (gDM) => gDM().prepend(listenerMiddlewareInstance.middleware), }) export { store } @@ -29,19 +29,15 @@ export type RootState = ReturnType // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch -export type AppListenerApi = ActionListenerMiddlewareAPI -export type AppActionListenerMiddleware = ActionListenerMiddleware< - RootState, - AppDispatch -> +export type AppListenerEffectAPI = ListenerEffectAPI -// Typed version of `actionListenerMiddleware` -export const appActionListener = - actionListenerMiddleware as AppActionListenerMiddleware +export type AppStartListening = TypedStartListening +export type AppAddListener = TypedAddListener + +export const startAppListening = + listenerMiddlewareInstance.startListening as AppStartListening +export const addAppListener = addListener as AppAddListener // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch() export const useAppSelector: TypedUseSelectorHook = useSelector - -setupCounterListeners(appActionListener) -setupThemeListeners(appActionListener) diff --git a/yarn.lock b/yarn.lock index d1cc443d13..563ce0f517 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3673,7 +3673,7 @@ __metadata: resolution: "@examples-action-listener/counter@workspace:examples/action-listener/counter" dependencies: "@reduxjs/toolkit": ^1.6.0-rc.1 - "@rtk-incubator/action-listener-middleware": ^0.6.0 + "@rtk-incubator/action-listener-middleware": ^0.8.0 "@types/node": ^12.0.0 "@types/react": ^17.0.0 "@types/react-dom": ^17.0.0 @@ -5513,16 +5513,7 @@ __metadata: languageName: node linkType: hard -"@rtk-incubator/action-listener-middleware@npm:^0.6.0": - version: 0.6.0 - resolution: "@rtk-incubator/action-listener-middleware@npm:0.6.0" - peerDependencies: - "@reduxjs/toolkit": ^1.6.0 - checksum: 01e600a9e513f883e4c6d02cbe4565b9691d6b43ebff432a9ad7f4f96d07c3164c3a0c14fde4391e3d3f65e18753e567b67d9645a2af27daba6b0aadd5fa2066 - languageName: node - linkType: hard - -"@rtk-incubator/action-listener-middleware@workspace:packages/action-listener-middleware": +"@rtk-incubator/action-listener-middleware@^0.8.0, @rtk-incubator/action-listener-middleware@workspace:packages/action-listener-middleware": version: 0.0.0-use.local resolution: "@rtk-incubator/action-listener-middleware@workspace:packages/action-listener-middleware" dependencies: