diff --git a/apps/example/src/app/redux/index.tsx b/apps/example/src/app/redux/index.tsx index 878bc51..9478aaa 100644 --- a/apps/example/src/app/redux/index.tsx +++ b/apps/example/src/app/redux/index.tsx @@ -42,4 +42,4 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, -}); \ No newline at end of file +}); diff --git a/apps/example/src/app/redux/store.ts b/apps/example/src/app/redux/store.ts index fe5ff80..c5806f5 100644 --- a/apps/example/src/app/redux/store.ts +++ b/apps/example/src/app/redux/store.ts @@ -1,10 +1,12 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, Middleware } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; +import { reduxDevToolsMiddleware } from '@dev-plugins/redux/src/useReduxDevTools'; export const store = configureStore({ reducer: { counter: counterReducer, }, + middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(reduxDevToolsMiddleware), }); export type RootState = ReturnType; diff --git a/packages/redux/README.md b/packages/redux/README.md index 38c00e6..6409df6 100644 --- a/packages/redux/README.md +++ b/packages/redux/README.md @@ -16,10 +16,23 @@ npx expo install @dev-plugins/redux import { useReduxDevTools } from '@dev-plugins/redux'; import { store } from './store'; - export default function App() { useReduxDevTools(store); /* ... */ } -``` \ No newline at end of file +``` + +### Add middleware to store + +```jsx +import { reduxDevToolsMiddleware } from '@dev-plugins/redux'; +import thunk from 'redux-thunk'; +import rootReducer from './reducers'; + +const store = createStore( + rootReducer, + middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(reduxDevToolsMiddleware), +); + +``` diff --git a/packages/redux/src/useReduxDevTools.ts b/packages/redux/src/useReduxDevTools.ts index 123f282..908892a 100644 --- a/packages/redux/src/useReduxDevTools.ts +++ b/packages/redux/src/useReduxDevTools.ts @@ -7,18 +7,29 @@ import { Store } from '@reduxjs/toolkit'; * * @param store - The Redux store. */ + +let lastAction = null; export function useReduxDevTools(store: Store) { const client = useDevToolsPluginClient('redux'); useEffect(() => { - client?.sendMessage("storeUpdated", store.getState()); + client?.sendMessage('storeUpdated', { store: store.getState(), lastAction }); + const unsubscribeFn = store.subscribe(() => { const state = store.getState(); - client?.sendMessage("storeUpdated", state); + client?.sendMessage('storeUpdated', { store: store.getState(), lastAction }); }); return () => { unsubscribeFn(); - } - }, [client]) -} \ No newline at end of file + }; + }, [client, lastAction]); +} + +export const reduxDevToolsMiddleware = (store) => (next) => (action) => { + if (action !== lastAction) { + lastAction = { ...action, time: new Date().toISOString() }; + } + const result = next(action); + return result; +}; diff --git a/packages/redux/webui/src/App.tsx b/packages/redux/webui/src/App.tsx index e28b277..a75fb69 100644 --- a/packages/redux/webui/src/App.tsx +++ b/packages/redux/webui/src/App.tsx @@ -1,35 +1,130 @@ -import { App } from 'antd'; +import { App, List, Typography, Divider, Tabs, ThemeConfig, ConfigProvider } from 'antd'; // Importing components from Ant Design import { useDevToolsPluginClient, type EventSubscription } from 'expo/devtools'; import { Fragment, useEffect, useState } from 'react'; import ReactJson from 'react-json-view'; +const { Item } = List; +const { Text } = Typography; +const { TabPane } = Tabs; + export default function Main() { const client = useDevToolsPluginClient('redux'); - const [storeHistory, setStoreHistory] = useState([]) + const [storeHistory, setStoreHistory] = useState([]); + const [selectedState, setSelectedState] = useState(); useEffect(() => { const subscriptions: EventSubscription[] = []; - + subscriptions.push( client?.addMessageListener('storeUpdated', (data) => { - setStoreHistory(prevHistory => [...prevHistory, data]); + if (!data.store) return; + setStoreHistory((prevHistory) => [ + ...prevHistory, + { state: data.store, action: data.lastAction }, + ]); }) ); - + return () => { for (const subscription of subscriptions) { subscription?.remove(); } }; }, [client]); - + + const handleItemClick = (index: number) => { + setSelectedState(storeHistory[index]); + }; + + const calculateDiff = (stateBefore: typeof selectedState, stateAfter: typeof selectedState) => { + const diff: { [key: string]: any } = {}; + + if (!stateBefore) return stateAfter; + + Object.keys(stateAfter).forEach((key) => { + if (stateBefore[key] !== stateAfter[key]) { + diff[key] = stateAfter[key]; + } + }); + + return diff; + }; return ( - - {storeHistory.map((history, index) => { - return - })} - + +
+ Action History + ( + handleItemClick(index)} + style={{ + cursor: 'pointer', + backgroundColor: selectedState === item ? '#f0f0f0' : 'white', + }}> + {index + 1} {/* Displaying index for clarity */} +     + {!item?.action && '@@INIT'} {/* Displaying initial state */} + {item.action?.type?.toUpperCase()} {/* Displaying action type */} +
+ + {item.action?.time && new Date(item.action.time).toLocaleTimeString()} + + {/* Displaying timestamp */} +
+ )} + /> +
+
+ + + + + + {selectedState?.action ? ( + <> + { + if (key !== 'time') { + acc[key] = selectedState.state[key]; + } + return acc; + }, + {} + )} + /> + + Action dispatched at{' '} + {selectedState?.action?.time && + new Date(selectedState?.action.time).toLocaleTimeString()} + + + ) : ( + @@INIT + )} + + + {selectedState && ( + + )} + + +
); }