Skip to content

Instantly share code, notes, and snippets.

@likern
Created August 31, 2020 23:27
Show Gist options
  • Save likern/9ea94b38fe51cfab358afa239281e989 to your computer and use it in GitHub Desktop.
Save likern/9ea94b38fe51cfab358afa239281e989 to your computer and use it in GitHub Desktop.
import React, { useState, useCallback, useEffect } from 'react';
import { LayoutChangeEvent, Text } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedRef,
runOnUI,
measure
} from 'react-native-reanimated';
function useAnimatedState<T>(initial: T) {
const [state, setState] = useState(initial);
const sv = useSharedValue(initial);
function setMixedState(newValue: (arg: T) => T): void;
function setMixedState(newValue: T): void;
function setMixedState(newValue: any) {
'worklet';
if (typeof newValue === 'function') {
sv.value = newValue(sv.value);
setState(newValue);
} else {
sv.value = newValue;
setState(newValue);
}
}
const getState = () => {
'worklet';
// @ts-ignore
// eslint-disable-next-line no-undef
if (_WORKLET) {
return sv.value;
}
return state;
};
return [getState, setMixedState] as const;
}
export interface LayoutInfo {
x: number;
y: number;
width: number;
height: number;
pageX: number;
pageY: number;
}
export type Layout<T> = [number, T, LayoutInfo];
export type Layouts<T> = Layout<T>[];
export interface MeasureLayoutProps<P, T> {
propsList: P[];
componentRef: T;
onFinish(layouts: Layouts<P>): void;
}
export const MeasureLayout = <P extends {}, T extends React.ComponentType<P>>({
propsList,
componentRef,
onFinish
}: MeasureLayoutProps<P, T>) => {
const viewRef = useAnimatedRef();
const [getIndex, setIndex] = useAnimatedState(0);
const [getIsOnLayoutCalled, setIsOnLayoutCalled] = useAnimatedState(false);
const [getLayouts, setLayouts] = useAnimatedState<Layouts<P>>([]);
const ChildComponent = componentRef as React.ComponentType<P>;
console.log(
`[MeasureLayout] length [${propsList.length}], propsList: ${JSON.stringify(
propsList
)}`
);
const jsIndex = getIndex();
console.log(
`[MeasureLayout] index [${jsIndex}], props: ${JSON.stringify(
propsList[jsIndex]
)}`
);
const onLayoutCallback = useCallback(
(_: LayoutChangeEvent) => {
console.log('onLayoutCallback: called');
setIsOnLayoutCalled(true);
},
[setIsOnLayoutCalled]
);
function runOnUIWorklet() {
'worklet';
console.log('worklet start');
const onLayoutWasCalled = getIsOnLayoutCalled();
if (onLayoutWasCalled) {
const index = getIndex();
if (index < propsList.length) {
const layoutInfo: LayoutInfo = measure(viewRef);
const layout: Layout<P> = [index, propsList[index], layoutInfo];
console.log(
`worklet: index [${index}], measured layout: ${JSON.stringify(
layout
)}`
);
// FIXME: Doesn't work
// setLayouts([...getLayouts(), layout]);
setIndex(index + 1);
} else {
// Here return final measured layouts
console.log(`worklet: onFinish is called; index is ${index}`);
onFinish && onFinish(getLayouts());
}
}
}
useEffect(() => {
console.log('useEffect: before runOnUI');
runOnUI(runOnUIWorklet)();
console.log('useEffect: after runOnUI');
});
return (
<Animated.View ref={viewRef} onLayout={onLayoutCallback}>
{jsIndex < propsList.length && <ChildComponent {...propsList[jsIndex]} />}
</Animated.View>
);
};
interface TextComponentProps {
message: string;
}
export const RunLayoutMeasure = () => {
const TextComponent = (props: TextComponentProps) => (
<Text>{props.message}</Text>
);
const propsList: TextComponentProps[] = [
{ message: '1' },
{ message: '2' },
{ message: '3' },
{ message: '4' },
{ message: '5' }
];
return (
<MeasureLayout
propsList={propsList}
componentRef={TextComponent}
onFinish={(layouts) => {
console.log(
`measures: ${layouts}, isArray? ${Array.isArray(layouts)}, length ${
layouts.length
}`
);
}}
/>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment