Skip to content

Commit

Permalink
feat: Automatic complex shared values collections management (#9)
Browse files Browse the repository at this point in the history
* Fix formatting issues

* Improve code quality checking github action

* Add useComplexValues hook dependencies to automatically create/remove values
  • Loading branch information
MatiPl01 authored Jul 26, 2024
1 parent 5d05fc0 commit 6570a45
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 74 deletions.
4 changes: 2 additions & 2 deletions example/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AnimatedIntervalExample from './examples/AnimatedInterval';
import ComplexSharedValues from './examples/ComplexSharedValues';

export default function App() {
return <AnimatedIntervalExample />;
return <ComplexSharedValues />;
}
145 changes: 90 additions & 55 deletions example/app/src/examples/ComplexSharedValues.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
/* eslint-disable import/no-unused-modules */
import { runOnUI } from 'react-native-reanimated';

import { useEffect, useState } from 'react';
import { runOnUI, type SharedValue } from 'react-native-reanimated';
import {
array,
mutable,
Expand All @@ -11,71 +13,56 @@ import {
useComplexSharedValues
} from 'reanimated-utils';

// const schema1 = { $key: mutable({ height: 0, width: 0 }) };
const schema1 = { $key: mutable({ height: 0, width: 0 }) };

// const schema2 = record(mutable(0));
const schema2 = record(mutable(0));

// type KeyToIndex = Record<string, SharedValue<number>>;
type KeyToIndex = Record<string, SharedValue<number>>;

// type ItemPositions = Record<
// string,
// { x: SharedValue<number>; y: SharedValue<number> }
// >;
type ItemPositions = Record<
string,
{ x: SharedValue<number>; y: SharedValue<number> }
>;

export default function ComplexSharedValues() {
// // Variant 1:
// // - with schema defined outside of the hook
// // - without explicit type annotations
// const dimensions = useComplexSharedValues(schema1);
// console.log('dimensions', dimensions.current);

// // Variant 2:
// // - with schema defined inside of the hook
// // - with explicit type annotations
// const keyToIndex = useComplexSharedValues<KeyToIndex>(schema2);
// console.log('keyToIndex', keyToIndex.current);

// // Variant 3:
// // - with schema defined inside of the hook
// // - without explicit type annotations
// const rowOffsets = useComplexSharedValues(tuple(mutable(0), mutable('1')));
// console.log(
// 'rowOffsets',
// rowOffsets.current.map(value => value.value)
// );
const dimensions = useComplexSharedValues(schema1);
console.log('dimensions', dimensions.current);

Check warning on line 29 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement

// // Variant 4:
// // - with schema defined inside of the hook
// // - with explicit type annotations
// const itemPositions = useComplexSharedValues<ItemPositions>(() =>
// record({
// x: mutable(0),
// y: mutable(0)
// })
// );
// console.log('itemPositions', itemPositions.current);

// const test1 = useComplexSharedValues({ a: mutable(0) });
// console.log('test1.a.value', test1.current.a.value);
// const test2 = useComplexSharedValues([mutable(0)]);
// console.log('test2', test2.current);
// const test3 = useComplexSharedValues(tuple(mutable(0), mutable('1')));
// console.log(
// 'test3',
// test3.current.map(value => value.value)
// );
// const test4 = useComplexSharedValues(array(mutable(0)));
// console.log('test4', test4.current);
// const test5 = useComplexSharedValues(array(record(mutable(0))));
// console.log('test5', test5.current);
const keyToIndex = useComplexSharedValues<KeyToIndex>(schema2);
console.log('keyToIndex', keyToIndex.current);

Check warning on line 32 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement

const itemPositions = useComplexSharedValues<ItemPositions>(() =>
record({
x: mutable(0),
y: mutable(0)
})
);
console.log('itemPositions', itemPositions.current);

Check warning on line 40 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement

const test1 = useComplexSharedValues({ a: mutable(0) });
console.log('test1.a.value', test1.current.a.value);

Check warning on line 43 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
const test2 = useComplexSharedValues([mutable(0)], 4);
console.log('test2', test2.current);

Check warning on line 45 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
const test3 = useComplexSharedValues(tuple(mutable(0), mutable('1')));
console.log(

Check warning on line 47 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
'test3',
test3.current.map(value => value.value)
);
const test4 = useComplexSharedValues(array(mutable(0)), 4);
console.log('test4', test4.current);

Check warning on line 52 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
const test5 = useComplexSharedValues<
Array<Record<string, SharedValue<number>>>
>(array(record(mutable(0))), 10);
console.log('test5', test5.current);

Check warning on line 56 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
const test6 = useComplexSharedValues(
array(
tuple(
record(mutable(0)),
{ a: mutable(tuple(0, 0)) },
object({ a: 0, b: 0, c: mutable('1') })
)
)
),
4
);
test6.push(3);
// console.log('test6', test6.current[0]?.[2].c.value);
Expand All @@ -91,6 +78,10 @@ export default function ComplexSharedValues() {
console.log('test6_set_1', test6_set_1[2].c.value);

Check warning on line 78 in example/app/src/examples/ComplexSharedValues.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Code Quality Checks

Unexpected console statement
})();

// const overrideItemDimensions = useComplexSharedValues<
// Record<string, SharedValue<Partial<{ width: number; height: number }>>>
// >({ $key: mutable({ height: 0, width: 0 }) });

// // Alternative syntax (record)
// // all return: Record<string, SharedValue<number>>
// const test_1_1 = useComplexSharedValues(() => ({ $key: mutable(0) }));
Expand Down Expand Up @@ -145,8 +136,52 @@ export default function ComplexSharedValues() {
// const test_5_1 = useComplexSharedValues(() => mutable(tuple(0, 1)));
// const test_5_2 = useComplexSharedValues(mutable(tuple(0, 1)));

// dimensions.set('key1');
// console.log('?', dimensions.current.key1?.value);
dimensions.set('key1');
console.log('?', dimensions.current.key1?.value);

const [keys, setKeys] = useState<Array<string>>([]);
const overrideItemDimensions = useComplexSharedValues(
s => s.record(s.mutable({ height: 0, width: 0 })),
keys
);

useEffect(() => {
console.log('effect');
const interval = setInterval(() => {
setKeys(prevKeys => {
const newKeys = [...prevKeys];
newKeys.push(`key${newKeys.length + 1}`);
return newKeys;
});
}, 1000);

return () => clearInterval(interval);
}, [overrideItemDimensions]);

console.log('body', keys);
console.log(
'current',
Object.values(overrideItemDimensions.current).map(value => value.value)
);

const [rowCount, setRowCount] = useState(0);
const rowOffsets = useComplexSharedValues(
s => s.array(s.mutable(0)),
rowCount
);

useEffect(() => {
const interval = setInterval(() => {
setRowCount(count => count + 1);
}, 1000);

return () => clearInterval(interval);
}, [rowOffsets]);

console.log(
'rowOffsets',
rowOffsets.current.map(value => value.value)
);

return null;
}
4 changes: 2 additions & 2 deletions packages/reanimated-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
"build": "yarn clean && bob build",
"clean": "../../scripts/clean.sh",
"postpack": "rm ./README.md",
"prepack": "bob build && cp ../../README.md ./README.md",
"prepack": "bob build && rm -f *.tgz && cp ../../README.md ./README.md",
"test": "jest",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"types": "dist/typescript/index.d.ts"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type {
ArrayMethods,
ComplexSharedValuesReturnType as ComplexSharedValues,
ComplexValue,
RecordMethods,
SchemaArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ type ConvertToSchemaOutsideMutable<T> =
| [ConvertToSchemaOutsideMutable<U>]
| SchemaArray<ConvertToSchemaOutsideMutable<U>>
: IsInfiniteObject<T> extends true
? SchemaRecord<ConvertToSchemaOutsideMutable<T[keyof T]>>
?
| { $key: ConvertToSchemaOutsideMutable<T[keyof T]> }
| SchemaRecord<ConvertToSchemaOutsideMutable<T[keyof T]>>
: T extends Record<string, any>
?
| {
Expand All @@ -141,7 +143,9 @@ type ConvertToSchemaInsideMutable<T> =
| [ConvertToSchemaInsideMutable<U>]
| SchemaArray<ConvertToSchemaInsideMutable<U>>
: IsInfiniteObject<T> extends true
? SchemaRecord<ConvertToSchemaInsideMutable<T[keyof T]>>
?
| { $key: ConvertToSchemaInsideMutable<T[keyof T]> }
| SchemaRecord<ConvertToSchemaInsideMutable<T[keyof T]>>
: T extends Record<string, any>
?
| {
Expand Down Expand Up @@ -200,6 +204,17 @@ type MethodType<V> =
? RecordMethods<V>
: SingletonMethods<V>;

export type DepsType<V> =
IsInfiniteArray<V> extends true
? number // items count in the top level array
: V extends SchemaArray<any>
? number
: IsInfiniteObject<V> extends true
? Array<string> // keys of the top level object
: V extends SchemaRecord<any>
? Array<string>
: never;

export type ComplexSharedValuesReturnType<V> = Simplify<
{
current: ComplexSharedValuesCurrentType<V>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useRef } from 'react';
import type {
ComplexSharedValuesReturnType,
ComplexSharedValuesSchema,
DepsType,
SingletonSchema
} from './types';
import { useComplexSharedValuesArray } from './useComplexSharedValuesArray';
Expand All @@ -19,17 +20,20 @@ import {
type SchemaFactory
} from './utils';

// Function overloads
export function useComplexSharedValues<V>(
schema: SchemaFactory<V>
): ComplexSharedValuesReturnType<V>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useComplexSharedValues<F extends SchemaFactory>(
schema: F,
deps?: DepsType<ReturnType<F>>
): ComplexSharedValuesReturnType<ReturnType<F>>;

export function useComplexSharedValues<V>(
schema: ComplexSharedValuesSchema<V>
schema: ComplexSharedValuesSchema<V> | SchemaFactory<V>,
deps?: DepsType<V>
): ComplexSharedValuesReturnType<V>;

export function useComplexSharedValues<V>(
schema: ComplexSharedValuesSchema<V> | SchemaFactory<V>
schema: ComplexSharedValuesSchema<V> | SchemaFactory<V>,
deps?: DepsType<V>
): ComplexSharedValuesReturnType<V> {
const schemaRef = useRef<ComplexSharedValuesSchema<V>>();

Expand All @@ -52,10 +56,16 @@ export function useComplexSharedValues<V>(
}

if (isArraySchema(currentSchema)) {
return useComplexSharedValuesArray(currentSchema);
return useComplexSharedValuesArray(
currentSchema,
deps as number | undefined
);
}
if (isRecordSchema(currentSchema)) {
return useComplexSharedValuesRecord(currentSchema);
return useComplexSharedValuesRecord(
currentSchema,
deps as Array<string> | undefined
);
}
return useComplexSharedValuesSingleton(currentSchema as SingletonSchema);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */

import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useRef } from 'react';

import { useCurrentValue } from './hooks';
import {
Expand All @@ -26,7 +26,8 @@ function isSchemaArray(obj: any): obj is SchemaArray<any> {
}

export function useComplexSharedValuesArray<V>(
arraySchema: ArraySchema
arraySchema: ArraySchema,
dep?: number
): ComplexSharedValuesReturnType<V> {
const schema = isSchemaArray(arraySchema)
? arraySchema.defaultValue
Expand Down Expand Up @@ -194,6 +195,20 @@ export function useComplexSharedValuesArray<V>(
[current, schema]
);

/**
* Update the array based on the dependencies
*/
const prevDepRef = useRef(dep);
if (dep !== undefined && dep !== prevDepRef.current) {
if (dep > current.length) {
push(dep - current.length);
} else if (dep < current.length) {
splice(dep);
}

prevDepRef.current = dep;
}

return useMemo<{ current: Record<string, any> } & ArrayMethods<V>>(
() => ({
clear,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback, useMemo } from 'react';
import { isEqual } from 'lodash-es';
import { useCallback, useMemo, useRef } from 'react';

import { useCurrentValue } from './hooks';
import {
Expand All @@ -25,7 +26,8 @@ function isSchemaRecord(obj: any): obj is SchemaRecord<any> {
}

export function useComplexSharedValuesRecord<V>(
recordSchema: RecordSchema
recordSchema: RecordSchema,
deps?: Array<string>
): ComplexSharedValuesReturnType<V> {
const schema = isSchemaRecord(recordSchema)
? recordSchema.defaultValue
Expand Down Expand Up @@ -121,6 +123,22 @@ export function useComplexSharedValuesRecord<V>(
[current, schema]
);

/**
* Update the record based on the dependencies
*/
const prevDepsRef = useRef<Array<string>>();
if (deps && !isEqual(deps, prevDepsRef.current)) {
const currentKeys = Object.keys(current);
const newKeysSet = new Set(deps);

const removedKeys = currentKeys.filter(key => !newKeysSet.has(key));
const addedKeys = deps.filter(key => !current[key]);
set([...addedKeys]);
remove(...removedKeys);

prevDepsRef.current = deps;
}

return useMemo<{ current: Record<string, any> } & RecordMethods<V>>(
() => ({
clear,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const object = <V>(defaultValue: V): SchemaObject<V> => ({
defaultValue
});

export type SchemaFactory<V> = (args: {
export type SchemaFactory<V = any> = (args: {
mutable: typeof mutable;
tuple: typeof tuple;
array: typeof array;
Expand Down

0 comments on commit 6570a45

Please sign in to comment.