fix: Resolve Require Cycles And LegendList Render Updates
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
diff --git a/react-native.js b/react-native.js
|
||||
index 229f09a8230ab24079d471f65d28d36908f0f2ea..5c9a6c2501d8de3c44e138593831f3442b4b9204 100644
|
||||
--- a/react-native.js
|
||||
+++ b/react-native.js
|
||||
@@ -7256,15 +7256,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
|
||||
useWindowScroll: useWindowScrollResolved
|
||||
};
|
||||
state.refScroller = refScroller;
|
||||
- if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
|
||||
- resetAdaptiveRender(ctx);
|
||||
- }
|
||||
- if (shouldResetFreshDataLayout) {
|
||||
- resetInitialRenderState(ctx, {
|
||||
- resetInitialScroll: !!initialScrollProp,
|
||||
- resetLayout: true
|
||||
- });
|
||||
- }
|
||||
+ React2.useLayoutEffect(() => {
|
||||
+ if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
|
||||
+ resetAdaptiveRender(ctx);
|
||||
+ }
|
||||
+ if (shouldResetFreshDataLayout) {
|
||||
+ resetInitialRenderState(ctx, {
|
||||
+ resetInitialScroll: !!initialScrollProp,
|
||||
+ resetLayout: true
|
||||
+ });
|
||||
+ }
|
||||
+ }, [experimental_adaptiveRender, initialScrollProp, isFirstLocal, previousAdaptiveRender, shouldResetFreshDataLayout]);
|
||||
const memoizedLastItemKeys = React2.useMemo(() => {
|
||||
if (!dataProp.length) return [];
|
||||
return Array.from(
|
||||
diff --git a/react-native.mjs b/react-native.mjs
|
||||
index c2e0f3823f524e31e2105a5fbda76be78c926226..dc0c9c6d5b887968c631dd0ba1646fd40ba28c63 100644
|
||||
--- a/react-native.mjs
|
||||
+++ b/react-native.mjs
|
||||
@@ -7235,15 +7235,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
|
||||
useWindowScroll: useWindowScrollResolved
|
||||
};
|
||||
state.refScroller = refScroller;
|
||||
- if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
|
||||
- resetAdaptiveRender(ctx);
|
||||
- }
|
||||
- if (shouldResetFreshDataLayout) {
|
||||
- resetInitialRenderState(ctx, {
|
||||
- resetInitialScroll: !!initialScrollProp,
|
||||
- resetLayout: true
|
||||
- });
|
||||
- }
|
||||
+ useLayoutEffect(() => {
|
||||
+ if (!isFirstLocal && previousAdaptiveRender && !experimental_adaptiveRender) {
|
||||
+ resetAdaptiveRender(ctx);
|
||||
+ }
|
||||
+ if (shouldResetFreshDataLayout) {
|
||||
+ resetInitialRenderState(ctx, {
|
||||
+ resetInitialScroll: !!initialScrollProp,
|
||||
+ resetLayout: true
|
||||
+ });
|
||||
+ }
|
||||
+ }, [experimental_adaptiveRender, initialScrollProp, isFirstLocal, previousAdaptiveRender, shouldResetFreshDataLayout]);
|
||||
const memoizedLastItemKeys = useMemo(() => {
|
||||
if (!dataProp.length) return [];
|
||||
return Array.from(
|
||||
Generated
+5
-2
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
patchedDependencies:
|
||||
'@legendapp/list@3.3.3': 37a9c8d7d9c1bd33c957ba6ea78291c7b08532dc32de4925f3d612cb41f1a659
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -16,7 +19,7 @@ importers:
|
||||
version: 5.2.14(8883bda3e7ffd4622bceabc44263e41c)
|
||||
'@legendapp/list':
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3(react-native@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(@react-native/jest-preset@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@9.4.0)))(@types/react@19.2.17)(react@19.2.3)(supports-color@9.4.0))(react@19.2.3)
|
||||
version: 3.3.3(patch_hash=37a9c8d7d9c1bd33c957ba6ea78291c7b08532dc32de4925f3d612cb41f1a659)(react-native@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(@react-native/jest-preset@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@9.4.0)))(@types/react@19.2.17)(react@19.2.3)(supports-color@9.4.0))(react@19.2.3)
|
||||
'@noble/ciphers':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
@@ -7559,7 +7562,7 @@ snapshots:
|
||||
dependencies:
|
||||
jsbi: 4.3.2
|
||||
|
||||
'@legendapp/list@3.3.3(react-native@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(@react-native/jest-preset@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@9.4.0)))(@types/react@19.2.17)(react@19.2.3)(supports-color@9.4.0))(react@19.2.3)':
|
||||
'@legendapp/list@3.3.3(patch_hash=37a9c8d7d9c1bd33c957ba6ea78291c7b08532dc32de4925f3d612cb41f1a659)(react-native@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(@react-native/jest-preset@0.86.0(@babel/core@7.29.7(supports-color@9.4.0))(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@9.4.0)))(@types/react@19.2.17)(react@19.2.3)(supports-color@9.4.0))(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
use-sync-external-store: 1.6.0(react@19.2.3)
|
||||
|
||||
@@ -29,3 +29,6 @@ onlyBuiltDependencies:
|
||||
- better-sqlite3
|
||||
- esbuild
|
||||
- protobufjs
|
||||
|
||||
patchedDependencies:
|
||||
'@legendapp/list@3.3.3': patches/@legendapp__list@3.3.3.patch
|
||||
|
||||
@@ -3,7 +3,7 @@ import { StyleSheet, View, TextInput } from 'react-native';
|
||||
|
||||
import IconButtonV2 from '../IconButtonV2/IconButtonV2';
|
||||
import { ThemeColors } from '../../theme/types';
|
||||
import { Menu } from '@components';
|
||||
import Menu from '../Menu';
|
||||
import { MaterialDesignIconName } from '@type/icon';
|
||||
import { getString } from '@i18n/translations';
|
||||
import { Pressable } from 'react-native-gesture-handler';
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { render } from '@testing-library/react-native';
|
||||
import { Text } from 'react-native';
|
||||
import { LegendList } from '@legendapp/list/react-native';
|
||||
|
||||
const renderItem = ({ item }: { item: string }) => <Text>{item}</Text>;
|
||||
|
||||
describe('LegendList render lifecycle patch', () => {
|
||||
it('does not update its container layer while rendering fresh data', () => {
|
||||
const consoleError = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
const view = render(
|
||||
<LegendList
|
||||
data={['first']}
|
||||
estimatedItemSize={40}
|
||||
recycleItems={false}
|
||||
renderItem={renderItem}
|
||||
/>,
|
||||
);
|
||||
|
||||
view.rerender(
|
||||
<LegendList
|
||||
data={[]}
|
||||
estimatedItemSize={40}
|
||||
recycleItems={false}
|
||||
renderItem={renderItem}
|
||||
/>,
|
||||
);
|
||||
view.rerender(
|
||||
<LegendList
|
||||
data={['second']}
|
||||
estimatedItemSize={40}
|
||||
recycleItems={false}
|
||||
renderItem={renderItem}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(consoleError.mock.calls.flat().join(' ')).not.toContain(
|
||||
'Cannot update a component',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { Query } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@database/db';
|
||||
import type { FireOn } from './manager';
|
||||
|
||||
interface ExecutableSelect<TResult = any> {
|
||||
toSQL(): Query;
|
||||
all(): Promise<TResult[]>;
|
||||
}
|
||||
|
||||
export function useLiveQuery<T extends ExecutableSelect>(
|
||||
query: T,
|
||||
fireOn: FireOn,
|
||||
callback?: (data: Awaited<ReturnType<T['all']>>) => void,
|
||||
) {
|
||||
type ReturnValue = Awaited<ReturnType<T['all']>>;
|
||||
|
||||
const { sql: sqlString, params } = query.toSQL();
|
||||
const paramsKey = JSON.stringify(params);
|
||||
const fireOnKey = JSON.stringify(fireOn);
|
||||
const callbackRef = useRef(callback);
|
||||
|
||||
const [data, setData] = useState<ReturnValue>(
|
||||
() => db.executeSync(sqlString, params as any[]).rows as ReturnValue,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current?.(data);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = db.reactiveExecute({
|
||||
query: sqlString,
|
||||
arguments: params as any[],
|
||||
fireOn,
|
||||
callback: (result: { rows: ReturnValue }) => {
|
||||
setData(result.rows);
|
||||
},
|
||||
});
|
||||
return unsubscribe;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sqlString, paramsKey, fireOnKey]);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { db, drizzleDb } from '@database/db';
|
||||
import type { drizzleDb } from '@database/db';
|
||||
import type { SQLBatchTuple, Scalar } from '@op-engineering/op-sqlite';
|
||||
import { IDbManager } from './manager.d';
|
||||
import { DbTaskQueue } from './queue';
|
||||
import { Schema } from '../schema';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { GetSelectTableName } from 'drizzle-orm/query-builders/select.types';
|
||||
import {
|
||||
AnyColumn,
|
||||
@@ -21,7 +20,7 @@ type TransactionParameter = Parameters<
|
||||
|
||||
interface ExecutableSelect<TResult = any> {
|
||||
toSQL(): Query;
|
||||
all(): Promise<TResult[]>; // Or TResult[] if you are using a synchronous driver
|
||||
all(): Promise<TResult[]>;
|
||||
get(): Promise<TResult | undefined>;
|
||||
}
|
||||
|
||||
@@ -140,39 +139,4 @@ export const createDbManager = (dbInstance: DrizzleDb) => {
|
||||
};
|
||||
|
||||
type TableNames = GetSelectTableName<Schema[keyof Schema]>;
|
||||
type FireOn = { table: TableNames; ids?: number[] }[];
|
||||
|
||||
export function useLiveQuery<T extends ExecutableSelect>(
|
||||
query: T,
|
||||
fireOn: FireOn,
|
||||
callback?: (data: Awaited<ReturnType<T['all']>>) => void,
|
||||
) {
|
||||
type ReturnValue = Awaited<ReturnType<T['all']>>;
|
||||
|
||||
const { sql: sqlString, params } = query.toSQL();
|
||||
const paramsKey = JSON.stringify(params);
|
||||
const fireOnKey = JSON.stringify(fireOn);
|
||||
const cb = useRef(callback ?? (() => {}));
|
||||
|
||||
const [data, setData] = useState<ReturnValue>(() => {
|
||||
const r = db.executeSync(sqlString, params as any[]).rows as ReturnValue;
|
||||
cb.current(r);
|
||||
return r;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const unsub = db.reactiveExecute({
|
||||
query: sqlString,
|
||||
arguments: params as any[],
|
||||
fireOn,
|
||||
callback: (result: { rows: ReturnValue }) => {
|
||||
setData(result.rows);
|
||||
cb.current(result.rows);
|
||||
},
|
||||
});
|
||||
return unsub;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sqlString, paramsKey, fireOnKey]);
|
||||
|
||||
return data;
|
||||
}
|
||||
export type FireOn = { table: TableNames; ids?: number[] }[];
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
RootStackParamList,
|
||||
} from '@navigators/types';
|
||||
import { showToast } from '@utils/showToast';
|
||||
import { useLiveQuery } from '@database/manager/manager';
|
||||
import { useLiveQuery } from '@database/manager/liveQuery';
|
||||
import { repositorySchema } from '@database/schema';
|
||||
import { dbManager } from '@database/db';
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
createBackgroundTaskMetadata,
|
||||
fromNativeTaskRecord,
|
||||
} from './taskDefinitions';
|
||||
|
||||
export const BACKGROUND_TASKS_STORE_KEY = 'APP_SERVICE';
|
||||
import { BACKGROUND_TASKS_STORE_KEY } from './constants';
|
||||
|
||||
const makeTemporaryId = () =>
|
||||
`pending-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const BACKGROUND_TASKS_STORE_KEY = 'APP_SERVICE';
|
||||
@@ -1,5 +1,5 @@
|
||||
export { backgroundTasks } from './backgroundTasks';
|
||||
export { BACKGROUND_TASKS_STORE_KEY } from './BackgroundTaskQueue';
|
||||
export { BACKGROUND_TASKS_STORE_KEY } from './constants';
|
||||
export { runHeadlessBackgroundTask } from './headlessTask';
|
||||
export type {
|
||||
BackgroundTask,
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
import { BackupCategory, BackupNovel } from '@database/types';
|
||||
import { BackupEntryName } from './types';
|
||||
import { ROOT_STORAGE } from '@utils/Storages';
|
||||
import { BACKGROUND_TASKS_STORE_KEY } from '@services/backgroundTasks/BackgroundTaskQueue';
|
||||
import NativeFile from '@modules/native-file'
|
||||
import { BACKGROUND_TASKS_STORE_KEY } from '@services/backgroundTasks/constants';
|
||||
import NativeFile from '@modules/native-file';
|
||||
import { showToast } from '@utils/showToast';
|
||||
import { getString } from '@i18n/translations';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user