Skip to content

Commit

Permalink
fix: Reactive.FlatList $data prop was not working because it's mutabl…
Browse files Browse the repository at this point in the history
…e, so it also has to add extraData
  • Loading branch information
jmeistrich committed Sep 16, 2023
1 parent 0a21524 commit 8d412e3
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 18 deletions.
20 changes: 19 additions & 1 deletion src/config/enableReactNativeComponents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FCReactive, FCReactiveObject, configureReactive } from '@legendapp/state/react';
import { useRef } from 'react';
import type { Observable } from '@legendapp/state';
import { FCReactive, FCReactiveObject, configureReactive, useSelector } from '@legendapp/state/react';
import {
ActivityIndicator,
ActivityIndicatorProps,
Expand Down Expand Up @@ -57,6 +59,22 @@ export function enableReactNativeComponents() {
defaultValue: false,
},
},
FlatList: {
data: {
selector: (propsOut: Record<string, any>, p: Observable<any>) => {
const state = useRef(0);
// Increment renderNum whenever the array changes shallowly
const [renderNum, value] = useSelector(() => [state.current++, p.get(true)]);

// Set extraData to renderNum so that it will re-render when renderNum changes.
// This is necessary because the observable array is mutable so changes to it
// won't trigger re-renders by default.
propsOut.extraData = renderNum;

return value;
},
},
},
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/react/Reactive.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isEmpty, isFunction } from '@legendapp/state';
import { ComponentClass, FC, createElement, forwardRef } from 'react';
import { BindKeys } from './reactInterfaces';
import { reactive } from './reactive-observer';
import { ComponentClass, FC, createElement, forwardRef } from 'react';

const ReactiveFns = new Map<string, FC | ComponentClass>();
const ReactiveFnBinders = new Map<string, BindKeys>();
Expand Down
11 changes: 9 additions & 2 deletions src/react/reactInterfaces.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import type { FC, LegacyRef, ReactNode } from 'react';
import type { Selector } from '../observableInterfaces';
import type { Observable, Selector } from '../observableInterfaces';

export type ShapeWithNew$<T> = Partial<Omit<T, 'children'>> & {
[K in keyof T as K extends `$${string & K}` ? K : `$${string & K}`]?: Selector<T[K]>;
} & { children?: Selector<ReactNode> };

export type BindKeys<P = any> = Record<keyof P, { handler: keyof P; getValue: (e: any) => any; defaultValue?: any }>;
export interface BindKey<P> {
handler?: keyof P;
getValue?: (e: any) => any;
defaultValue?: any;
selector?: (propsOut: Record<string, any>, p: Observable<any>) => any;
}

export type BindKeys<P = any> = Record<keyof P, BindKey<P>>;

export type FCReactiveObject<T> = {
[K in keyof T]: FC<ShapeWithNew$<T[K]>>;
Expand Down
34 changes: 20 additions & 14 deletions src/react/reactive-observer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,32 @@ function createReactiveComponent<P = object>(
// }
const k = key.endsWith('$') ? key.slice(0, -1) : key.slice(1);
// Return raw value and listen to the selector for changes
propsOut[k] = useSelector(p);

// If this key is one of the bind keys set up a two-way binding
const bind = bindKeys?.[k as keyof P];
if (bind && isObservable(p)) {
const shouldBind = bind && isObservable(p);

propsOut[k] = shouldBind && bind?.selector ? bind.selector(propsOut, p) : useSelector(p);

// If this key is one of the bind keys set up a two-way binding
if (shouldBind) {
// Use the bind's defaultValue if value is undefined
if (bind.defaultValue !== undefined && propsOut[k] === undefined) {
propsOut[k] = bind.defaultValue;
}
// Hook up the change lander
const handlerFn = (e: ChangeEvent) => {
p.set(bind.getValue(e));
props[bind.handler]?.(e);
};

(propsOut[bind.handler as string] as any) =
// If in development mode, don't memoize the handler. fix fast refresh bug
process.env.NODE_ENV === 'development'
? handlerFn
: useCallback(handlerFn, [props[bind.handler], bindKeys]);

if (bind.handler && bind.getValue) {
// Hook up the change lander
const handlerFn = (e: ChangeEvent) => {
p.set(bind.getValue!(e));
props[bind.handler!]?.(e);
};

(propsOut[bind.handler as string] as any) =
// If in development mode, don't memoize the handler. fix fast refresh bug
process.env.NODE_ENV === 'development'
? handlerFn
: useCallback(handlerFn, [props[bind.handler], bindKeys]);
}
}

// Delete the reactive key
Expand Down

0 comments on commit 8d412e3

Please sign in to comment.