diff --git a/frontend/apps/remark42/app/components/auth-panel/auth-panel.test.tsx b/frontend/apps/remark42/app/components/auth-panel/auth-panel.test.tsx
index 6902508b..fc3aad89 100644
--- a/frontend/apps/remark42/app/components/auth-panel/auth-panel.test.tsx
+++ b/frontend/apps/remark42/app/components/auth-panel/auth-panel.test.tsx
@@ -1,7 +1,7 @@
import { mount } from 'enzyme';
import createMockStore from 'redux-mock-store';
import { Middleware } from 'redux';
-import { Provider } from 'react-redux';
+import { Provider } from 'store/context';
import { IntlProvider } from 'react-intl';
import type { User } from 'common/types';
diff --git a/frontend/apps/remark42/app/components/auth/auth.tsx b/frontend/apps/remark42/app/components/auth/auth.tsx
index 12f9b31f..bd98e6ac 100644
--- a/frontend/apps/remark42/app/components/auth/auth.tsx
+++ b/frontend/apps/remark42/app/components/auth/auth.tsx
@@ -2,7 +2,7 @@ import clsx from 'clsx';
import { h, Fragment, JSX } from 'preact';
import { useState, useRef } from 'preact/hooks';
import { useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
+import { useDispatch } from 'store/context';
import { setUser } from 'store/user/actions';
import { Input } from 'components/input';
diff --git a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.test.tsx b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.test.tsx
index 9459d0f6..54124001 100644
--- a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.test.tsx
+++ b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.test.tsx
@@ -1,6 +1,6 @@
import { mount } from 'enzyme';
import { act } from 'preact/test-utils';
-import { Provider } from 'react-redux';
+import { Provider } from 'store/context';
import { Middleware } from 'redux';
import createMockStore from 'redux-mock-store';
import { IntlProvider } from 'react-intl';
diff --git a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx
index 37d7ebd3..63e51e82 100644
--- a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx
+++ b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx
@@ -1,6 +1,6 @@
import { h, FunctionComponent, Fragment } from 'preact';
import { useState, useCallback, useRef } from 'preact/hooks';
-import { useSelector, useDispatch } from 'react-redux';
+import { useSelector, useDispatch } from 'store/context';
import clsx from 'clsx';
import { useIntl, defineMessages, IntlShape, FormattedMessage } from 'react-intl';
diff --git a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-rss/comment-form__subscribe-by-rss.test.tsx b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-rss/comment-form__subscribe-by-rss.test.tsx
index 5e650742..8b0eb867 100644
--- a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-rss/comment-form__subscribe-by-rss.test.tsx
+++ b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-rss/comment-form__subscribe-by-rss.test.tsx
@@ -4,7 +4,7 @@ import { SubscribeByRSS, createSubscribeUrl } from '.';
import styles from './subscribe-by-rss.module.css';
-jest.mock('react-redux', () => ({
+jest.mock('store/context', () => ({
useSelector: jest.fn((fn) => fn({ theme: 'light' })),
}));
diff --git a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-telegram/comment-form__subscribe-by-telegram.tsx b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-telegram/comment-form__subscribe-by-telegram.tsx
index c504508d..410f84af 100644
--- a/frontend/apps/remark42/app/components/comment-form/__subscribe-by-telegram/comment-form__subscribe-by-telegram.tsx
+++ b/frontend/apps/remark42/app/components/comment-form/__subscribe-by-telegram/comment-form__subscribe-by-telegram.tsx
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import { h, FunctionComponent, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks';
-import { useSelector } from 'react-redux';
+import { useSelector } from 'store/context';
import { useIntl, defineMessages } from 'react-intl';
import { User } from 'common/types';
diff --git a/frontend/apps/remark42/app/components/comment/comment-votes.tsx b/frontend/apps/remark42/app/components/comment/comment-votes.tsx
index 76da83e0..168fee82 100644
--- a/frontend/apps/remark42/app/components/comment/comment-votes.tsx
+++ b/frontend/apps/remark42/app/components/comment/comment-votes.tsx
@@ -3,7 +3,7 @@ import { h } from 'preact';
import { useState } from 'preact/hooks';
import { defineMessages, useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
+import { useDispatch } from 'store/context';
import { patchComment } from 'store/comments/actions';
import { putCommentVote } from 'common/api';
import { StaticStore } from 'common/static-store';
diff --git a/frontend/apps/remark42/app/components/root/root.tsx b/frontend/apps/remark42/app/components/root/root.tsx
index 5bb065d0..ab29254b 100644
--- a/frontend/apps/remark42/app/components/root/root.tsx
+++ b/frontend/apps/remark42/app/components/root/root.tsx
@@ -1,5 +1,5 @@
import { h, Component, Fragment } from 'preact';
-import { useSelector } from 'react-redux';
+import { useSelector } from 'store/context';
import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl';
import clsx from 'clsx';
diff --git a/frontend/apps/remark42/app/components/thread/thread.tsx b/frontend/apps/remark42/app/components/thread/thread.tsx
index 2478d02f..89c856f3 100644
--- a/frontend/apps/remark42/app/components/thread/thread.tsx
+++ b/frontend/apps/remark42/app/components/thread/thread.tsx
@@ -1,5 +1,5 @@
import { h, FunctionComponent, type AriaRole } from 'preact';
-import { shallowEqual } from 'react-redux';
+import { shallowEqual } from 'store/context';
import { useCallback } from 'preact/hooks';
import clsx from 'clsx';
import { useIntl } from 'react-intl';
diff --git a/frontend/apps/remark42/app/hooks/useAction.ts b/frontend/apps/remark42/app/hooks/useAction.ts
index 3f051588..64165aba 100644
--- a/frontend/apps/remark42/app/hooks/useAction.ts
+++ b/frontend/apps/remark42/app/hooks/useAction.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useMemo } from 'preact/compat';
-import { useDispatch } from 'react-redux';
+import { useDispatch } from 'store/context';
import { BoundActionCreator, BoundActionCreators } from 'utils/actionBinder';
/** binds actions to dispatch */
diff --git a/frontend/apps/remark42/app/hooks/useTheme.ts b/frontend/apps/remark42/app/hooks/useTheme.ts
index 38eec0f4..ae8d13de 100644
--- a/frontend/apps/remark42/app/hooks/useTheme.ts
+++ b/frontend/apps/remark42/app/hooks/useTheme.ts
@@ -1,4 +1,4 @@
-import { useSelector } from 'react-redux';
+import { useSelector } from 'store/context';
import { StoreState } from 'store';
import { Theme } from 'common/types';
diff --git a/frontend/apps/remark42/app/remark.tsx b/frontend/apps/remark42/app/remark.tsx
index c129071a..5df11131 100644
--- a/frontend/apps/remark42/app/remark.tsx
+++ b/frontend/apps/remark42/app/remark.tsx
@@ -1,6 +1,6 @@
import { h, render } from 'preact';
import { bindActionCreators } from 'redux';
-import { Provider } from 'react-redux';
+import { Provider } from 'store/context';
import { IntlProvider } from 'react-intl';
import { loadLocale } from 'utils/loadLocale';
diff --git a/frontend/apps/remark42/app/store/context.test.tsx b/frontend/apps/remark42/app/store/context.test.tsx
new file mode 100644
index 00000000..179ed3fd
--- /dev/null
+++ b/frontend/apps/remark42/app/store/context.test.tsx
@@ -0,0 +1,91 @@
+import { h } from 'preact';
+import { render, screen, act } from '@testing-library/preact';
+import { createStore } from 'redux';
+
+import { Provider, useSelector } from './context';
+
+type State = { value: number };
+
+function reducer(state: State = { value: 0 }, action: { type: string }): State {
+ return action.type === 'bump' ? { value: state.value + 1 } : state;
+}
+
+function Display() {
+ const value = useSelector((s: State) => s.value);
+ return {value};
+}
+
+describe('useSelector', () => {
+ it('renders the value at mount', () => {
+ const store = createStore(reducer);
+ render(
+
+
+
+ );
+ expect(screen.getByTestId('value').textContent).toBe('0');
+ });
+
+ it('re-renders on a store change', () => {
+ const store = createStore(reducer);
+ render(
+
+
+
+ );
+ act(() => {
+ store.dispatch({ type: 'bump' });
+ });
+ expect(screen.getByTestId('value').textContent).toBe('1');
+ });
+
+ // the subscribe-time check must not re-run a selector that builds a fresh object,
+ // or every connected component renders twice at mount
+ it('renders once at mount with an object-building selector', () => {
+ const store = createStore(reducer);
+ let renders = 0;
+
+ function Counting() {
+ const { value } = useSelector((s: State) => ({ value: s.value }));
+
+ renders += 1;
+
+ return {value};
+ }
+
+ render(
+
+
+
+ );
+
+ expect(renders).toBe(1);
+ expect(screen.getByTestId('value').textContent).toBe('0');
+ });
+
+ it('does not miss a dispatch that lands between render and subscribe', () => {
+ const store = createStore(reducer);
+
+ // dispatch from inside the component body, which runs during render and so
+ // before the subscribing effect. this is the window a real async resolution
+ // can land in, and a subscription created afterwards never hears about it.
+ let dispatched = false;
+ function Racing() {
+ const value = useSelector((s: State) => s.value);
+ if (!dispatched) {
+ dispatched = true;
+ store.dispatch({ type: 'bump' });
+ }
+ return {value};
+ }
+
+ render(
+
+
+
+ );
+
+ expect(store.getState().value).toBe(1);
+ expect(screen.getByTestId('value').textContent).toBe('1');
+ });
+});
diff --git a/frontend/apps/remark42/app/store/context.tsx b/frontend/apps/remark42/app/store/context.tsx
new file mode 100644
index 00000000..815f7669
--- /dev/null
+++ b/frontend/apps/remark42/app/store/context.tsx
@@ -0,0 +1,109 @@
+import { createContext, h, type ComponentChildren } from 'preact';
+import { useContext, useLayoutEffect, useRef, useState } from 'preact/hooks';
+import type { Store, Dispatch, Action } from 'redux';
+
+/**
+ * Minimal store binding over preact context, replacing react-redux.
+ *
+ * Only what the widget uses: a provider, dispatch, and a selector with an optional
+ * equality function.
+ */
+
+const StoreContext = createContext(null);
+
+export function Provider({
+ store,
+ children,
+}: {
+ store: Store;
+ children?: ComponentChildren;
+}) {
+ return {children};
+}
+
+function useStore(): Store {
+ const store = useContext(StoreContext);
+ if (!store) {
+ throw new Error('store accessed outside of a Provider');
+ }
+ return store as unknown as Store;
+}
+
+export function useDispatch(): D {
+ return useStore().dispatch as D;
+}
+
+export function useSelector(selector: (state: S) => R, equalityFn?: (a: R, b: R) => boolean): R {
+ const store = useStore();
+ const [, setTick] = useState(0);
+
+ const state = store.getState();
+ const selected = selector(state);
+
+ // refs keep the subscription stable while always comparing against the latest
+ // render's selector and value, so a changed selector cannot resurrect a stale result
+ const selectorRef = useRef(selector);
+ const equalityRef = useRef(equalityFn);
+ const selectedRef = useRef(selected);
+ const stateRef = useRef(state);
+ selectorRef.current = selector;
+ equalityRef.current = equalityFn;
+ selectedRef.current = selected;
+ stateRef.current = state;
+
+ useLayoutEffect(() => {
+ const checkForUpdates = () => {
+ const next = selectorRef.current(store.getState());
+ const equal = equalityRef.current
+ ? equalityRef.current(selectedRef.current, next)
+ : Object.is(selectedRef.current, next);
+
+ if (!equal) {
+ selectedRef.current = next;
+ setTick((n) => n + 1);
+ }
+ };
+
+ const unsubscribe = store.subscribe(checkForUpdates);
+
+ // a dispatch landing between this render and the subscription above is not
+ // delivered, since the listener did not exist yet. check once on subscribe so
+ // that update cannot be lost until some later, unrelated dispatch.
+ //
+ // only when the state actually moved, though: reducers return a new root
+ // object on every change, so an unchanged reference means nothing was missed.
+ // checking unconditionally would re-run the selector, and one building a fresh
+ // object fails Object.is against the render's value and forces a second render
+ // of every connected component at mount
+ if (store.getState() !== stateRef.current) {
+ checkForUpdates();
+ }
+
+ return unsubscribe;
+ }, [store]);
+
+ return selected;
+}
+
+export function shallowEqual(a: unknown, b: unknown): boolean {
+ if (Object.is(a, b)) {
+ return true;
+ }
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
+ return false;
+ }
+
+ const aKeys = Object.keys(a);
+ const bKeys = Object.keys(b);
+
+ return (
+ aKeys.length === bKeys.length &&
+ aKeys.every(
+ (key) =>
+ Object.prototype.hasOwnProperty.call(b, key) &&
+ Object.is((a as Record)[key], (b as Record)[key])
+ )
+ );
+}
+
+export type TypedUseSelectorHook = (selector: (state: S) => R, equalityFn?: (a: R, b: R) => boolean) => R;
diff --git a/frontend/apps/remark42/app/store/index.ts b/frontend/apps/remark42/app/store/index.ts
index 8f2ba6ce..761c4cb9 100644
--- a/frontend/apps/remark42/app/store/index.ts
+++ b/frontend/apps/remark42/app/store/index.ts
@@ -1,5 +1,5 @@
import { createStore, applyMiddleware, AnyAction, compose, combineReducers } from 'redux';
-import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
+import { useDispatch, useSelector, type TypedUseSelectorHook } from './context';
import thunk, { ThunkAction, ThunkDispatch } from 'redux-thunk';
import { rootProvider } from './reducers';
import { ACTIONS } from './actions';
diff --git a/frontend/apps/remark42/app/tests/utils.tsx b/frontend/apps/remark42/app/tests/utils.tsx
index 415aacac..fba4ea2e 100644
--- a/frontend/apps/remark42/app/tests/utils.tsx
+++ b/frontend/apps/remark42/app/tests/utils.tsx
@@ -1,7 +1,7 @@
import { h, ComponentChild } from 'preact';
import { IntlProvider } from 'react-intl';
import { render as originalRender } from '@testing-library/preact';
-import { Provider } from 'react-redux';
+import { Provider } from 'store/context';
import en from 'locales/en.json';
import { mockStore } from '__stubs__/store';
diff --git a/frontend/apps/remark42/package.json b/frontend/apps/remark42/package.json
index 647b7c49..fa2cd597 100644
--- a/frontend/apps/remark42/package.json
+++ b/frontend/apps/remark42/package.json
@@ -39,7 +39,6 @@
"react": "npm:@preact/compat@^18.3.2",
"react-dom": "npm:@preact/compat@^18.3.2",
"react-intl": "6.0.5",
- "react-redux": "^8.0.2",
"redux": "^4.2.0",
"redux-thunk": "^2.4.1"
},
@@ -66,7 +65,6 @@
"@types/lodash-es": "^4.17.12",
"@types/node": "^18.0.1",
"@types/node-emoji": "^1.8.1",
- "@types/react-redux": "^7.1.34",
"@types/redux-mock-store": "^1.5.0",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/webpack-env": "^1.18.8",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 31d8b310..5b825974 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -122,9 +122,6 @@ importers:
react-intl:
specifier: 6.0.5
version: 6.0.5(@preact/compat@18.3.2(preact@10.29.8))(typescript@5.9.3)
- react-redux:
- specifier: ^8.0.2
- version: 8.1.3(@preact/compat@18.3.2(preact@10.29.8))(@preact/compat@18.3.2(preact@10.29.8))(@types/react@18.3.31)(redux@4.2.1)
redux:
specifier: ^4.2.0
version: 4.2.1
@@ -198,9 +195,6 @@ importers:
'@types/node-emoji':
specifier: ^1.8.1
version: 1.8.2
- '@types/react-redux':
- specifier: ^7.1.34
- version: 7.1.34
'@types/redux-mock-store':
specifier: ^1.5.0
version: 1.5.0
@@ -1926,18 +1920,12 @@ packages:
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
- '@types/react-redux@7.1.34':
- resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==}
-
'@types/react@16.14.70':
resolution: {integrity: sha512-DM5Q7rSx9G6QYcVvMgxvEurL5P06OxcDNUXrLxlpBzG4ccUewcBCmsztYbxJBobzO8RIwwmjoaD5OsKqdHDuYQ==}
'@types/react@18.3.31':
resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
- '@types/react@19.2.17':
- resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
-
'@types/redux-mock-store@1.5.0':
resolution: {integrity: sha512-jcscBazm6j05Hs6xYCca6psTUBbFT2wqMxT7wZEHAYFxHB/I8jYk7d5msrHUlDiSL02HdTqTmkK2oIV8i3C8DA==}
@@ -1977,9 +1965,6 @@ packages:
'@types/tough-cookie@4.0.5':
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
- '@types/use-sync-external-store@0.0.3':
- resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
-
'@types/webpack-env@1.18.8':
resolution: {integrity: sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==}
@@ -5589,27 +5574,6 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
- react-redux@8.1.3:
- resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==}
- peerDependencies:
- '@types/react': ^16.8 || ^17.0 || ^18.0
- '@types/react-dom': ^16.8 || ^17.0 || ^18.0
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- react-native: '>=0.59'
- redux: ^4 || ^5.0.0-beta.0
- peerDependenciesMeta:
- '@types/react':
- optional: true
- '@types/react-dom':
- optional: true
- react-dom:
- optional: true
- react-native:
- optional: true
- redux:
- optional: true
-
read-pkg-up@7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
@@ -6385,11 +6349,6 @@ packages:
file-loader:
optional: true
- use-sync-external-store@1.6.0:
- resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -8483,11 +8442,6 @@ snapshots:
'@types/react': 18.3.31
hoist-non-react-statics: 3.3.2
- '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.17)':
- dependencies:
- '@types/react': 19.2.17
- hoist-non-react-statics: 3.3.2
-
'@types/html-minifier-terser@6.1.0': {}
'@types/http-errors@2.0.5': {}
@@ -8553,13 +8507,6 @@ snapshots:
'@types/range-parser@1.2.7': {}
- '@types/react-redux@7.1.34':
- dependencies:
- '@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.17)
- '@types/react': 19.2.17
- hoist-non-react-statics: 3.3.2
- redux: 4.2.1
-
'@types/react@16.14.70':
dependencies:
'@types/prop-types': 15.7.15
@@ -8571,10 +8518,6 @@ snapshots:
'@types/prop-types': 15.7.15
csstype: 3.2.3
- '@types/react@19.2.17':
- dependencies:
- csstype: 3.2.3
-
'@types/redux-mock-store@1.5.0':
dependencies:
redux: 4.2.1
@@ -8621,8 +8564,6 @@ snapshots:
'@types/tough-cookie@4.0.5': {}
- '@types/use-sync-external-store@0.0.3': {}
-
'@types/webpack-env@1.18.8': {}
'@types/ws@8.18.1':
@@ -12830,20 +12771,6 @@ snapshots:
react-is@18.3.1: {}
- react-redux@8.1.3(@preact/compat@18.3.2(preact@10.29.8))(@preact/compat@18.3.2(preact@10.29.8))(@types/react@18.3.31)(redux@4.2.1):
- dependencies:
- '@babel/runtime': 7.29.7
- '@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.31)
- '@types/use-sync-external-store': 0.0.3
- hoist-non-react-statics: 3.3.2
- react: '@preact/compat@18.3.2(preact@10.29.8)'
- react-is: 18.3.1
- use-sync-external-store: 1.6.0(@preact/compat@18.3.2(preact@10.29.8))
- optionalDependencies:
- '@types/react': 18.3.31
- react-dom: '@preact/compat@18.3.2(preact@10.29.8)'
- redux: 4.2.1
-
read-pkg-up@7.0.1:
dependencies:
find-up: 4.1.0
@@ -13772,10 +13699,6 @@ snapshots:
optionalDependencies:
file-loader: 6.2.0(webpack@5.108.3)
- use-sync-external-store@1.6.0(@preact/compat@18.3.2(preact@10.29.8)):
- dependencies:
- react: '@preact/compat@18.3.2(preact@10.29.8)'
-
util-deprecate@1.0.2: {}
utila@0.4.0: {}