Skip to content

Commit 16ce574

Browse files
authored
Merge pull request #45 from Lodin/feat/listref
Add listRef prop
2 parents 3aa4894 + 3101511 commit 16ce574

9 files changed

+140
-55
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ To see how it works you can check the [AsyncDataIdle](./__stories__/AsyncDataIdl
195195

196196
This option works in tandem with the `placeholder` option. With it, you can set the task timeout for the `requestIdleCallback`. The `buildingTaskTimeout` will be sent directly as the `requestIdleCallback`'s `timeout` option.
197197

198+
##### `listRef: Ref<FixedSizeList>`
199+
200+
This option allows you to get the instance of the internal `react-window` list. It is usually unnecessary because all necessary methods are already provided but still can be useful for edge cases.
201+
198202
##### `rowComponent: component`
199203

200204
This property receives a custom `Row` component for the `FixedSizeList` that will override the default one. It can be used for adding new functionality.
@@ -406,6 +410,10 @@ Since `VariableSizeList` in general inherits properties from the `FixedSizeList`
406410

407411
The `Node` component. It is the same as the [`FixedSizeTree`](#fixedsizetree)'s one but receives properties from the [`VariableSizeNodePublicState`](#types-1) object.
408412

413+
##### `listRef: Ref<VariableSizeList>`
414+
415+
Same as [`listRef`](#listref-reffixedsizelist) of `FixedSizeTree`.
416+
409417
##### `rowComponent: component`
410418

411419
See [`rowComponent`](#rowcomponent-component) in the `FixedSizeTree` section; the `getRecordData` returns the [`VirtualSizeNodePublicState`](#types-1) object.

__tests__/FixedSizeTree.spec.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {mount, ReactWrapper} from 'enzyme';
2-
import React, {FC} from 'react';
2+
import React, {createRef, FC} from 'react';
33
import {FixedSizeList} from 'react-window';
44
import {
55
FixedSizeNodeData,
@@ -287,6 +287,23 @@ describe('FixedSizeTree', () => {
287287
]);
288288
});
289289

290+
it('allows providing a listRef prop', () => {
291+
const refObject = createRef<FixedSizeList>();
292+
const refCallback = jest.fn();
293+
294+
const instance = component.find(FixedSizeList).instance();
295+
296+
component.setProps({listRef: refObject});
297+
298+
expect(refObject.current).toBe(instance);
299+
300+
component.setProps({
301+
listRef: refCallback,
302+
});
303+
304+
expect(refCallback).toHaveBeenCalledWith(instance);
305+
});
306+
290307
describe('placeholder', () => {
291308
const testRICTimeout = 16;
292309
let unmockRIC: () => void;

__tests__/VariableSizeTree.spec.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {mount, ReactWrapper} from 'enzyme';
2-
import React, {FC} from 'react';
2+
import React, {createRef, FC} from 'react';
33
import {VariableSizeList} from 'react-window';
44
import {
55
Row,
@@ -308,6 +308,23 @@ describe('VariableSizeTree', () => {
308308
]);
309309
});
310310

311+
it('allows providing a listRef prop', () => {
312+
const refObject = createRef<VariableSizeList>();
313+
const refCallback = jest.fn();
314+
315+
const instance = component.find(VariableSizeList).instance();
316+
317+
component.setProps({listRef: refObject});
318+
319+
expect(refObject.current).toBe(instance);
320+
321+
component.setProps({
322+
listRef: refCallback,
323+
});
324+
325+
expect(refCallback).toHaveBeenCalledWith(instance);
326+
});
327+
311328
describe('placeholder', () => {
312329
const testRICTimeout = 16;
313330
let unmockRIC: () => void;

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"react-window": "^1.8.5"
104104
},
105105
"dependencies": {
106-
"@babel/runtime": "^7.11.0"
106+
"@babel/runtime": "^7.11.0",
107+
"react-merge-refs": "^1.1.0"
107108
}
108109
}

src/FixedSizeTree.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import {FixedSizeList, FixedSizeListProps} from 'react-window';
33
import Tree, {
44
createTreeComputer,
55
NodeData,
6+
NodePublicState,
67
TreeProps,
78
TreeState,
8-
NodePublicState,
99
} from './Tree';
1010
import {createBasicRecord, getIdByIndex} from './utils';
1111

@@ -17,13 +17,15 @@ export type FixedSizeNodePublicState<
1717

1818
export type FixedSizeTreeProps<TData extends FixedSizeNodeData> = TreeProps<
1919
TData,
20-
FixedSizeNodePublicState<TData>
20+
FixedSizeNodePublicState<TData>,
21+
FixedSizeList
2122
> &
2223
Readonly<Pick<FixedSizeListProps, 'itemSize'>>;
2324

2425
export type FixedSizeTreeState<TData extends FixedSizeNodeData> = TreeState<
2526
TData,
26-
FixedSizeNodePublicState<TData>
27+
FixedSizeNodePublicState<TData>,
28+
FixedSizeList
2729
>;
2830

2931
const computeTree = createTreeComputer<
@@ -69,13 +71,14 @@ export class FixedSizeTree<
6971
public render(): ReactNode {
7072
const {
7173
children,
74+
listRef,
7275
placeholder,
7376
treeWalker,
7477
rowComponent,
7578
...rest
7679
} = this.props;
7780

78-
const {order} = this.state;
81+
const {attachRefs, order} = this.state;
7982

8083
return placeholder && order!.length === 0 ? (
8184
placeholder
@@ -84,9 +87,10 @@ export class FixedSizeTree<
8487
{...rest}
8588
itemCount={order!.length}
8689
itemData={this.getItemData()}
87-
ref={this.list}
8890
// eslint-disable-next-line @typescript-eslint/unbound-method
8991
itemKey={getIdByIndex}
92+
// eslint-disable-next-line @typescript-eslint/unbound-method
93+
ref={attachRefs}
9094
>
9195
{rowComponent!}
9296
</FixedSizeList>

src/Tree.tsx

+60-36
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
import React, {
33
Component,
44
ComponentType,
5+
createRef,
56
PropsWithChildren,
67
PureComponent,
78
ReactElement,
89
ReactNode,
10+
Ref,
11+
RefCallback,
12+
RefObject,
913
} from 'react';
14+
import mergeRefs from 'react-merge-refs';
1015
import {
1116
Align,
1217
FixedSizeList,
@@ -95,35 +100,53 @@ export type OpennessState<
95100
Record<string, OpennessStateUpdateRules<TData, TNodePublicState> | boolean>
96101
>;
97102

98-
export type TreeProps<
103+
export type TreeComputerProps<TData extends NodeData> = Readonly<{
104+
async?: boolean;
105+
buildingTaskTimeout?: number;
106+
placeholder?: ReactNode;
107+
treeWalker: TreeWalker<TData>;
108+
}>;
109+
110+
export type TreeComputerState<
99111
TData extends NodeData,
100112
TNodePublicState extends NodePublicState<TData>
113+
> = Readonly<{
114+
order?: string[];
115+
records: ReadonlyMap<string | symbol, NodeRecord<TNodePublicState>>;
116+
setState: Component<
117+
any,
118+
TreeComputerState<TData, TNodePublicState>
119+
>['setState'];
120+
// A simple hack to get over the PureComponent shallow comparison
121+
updateRequest: object;
122+
}>;
123+
124+
export type TreeProps<
125+
TData extends NodeData,
126+
TNodePublicState extends NodePublicState<TData>,
127+
TListComponent extends FixedSizeList | VariableSizeList
101128
> = Readonly<Omit<ListProps, 'children' | 'itemCount' | 'itemKey'>> &
129+
TreeComputerProps<TData> &
102130
Readonly<{
103-
buildingTaskTimeout?: number;
104131
children: ComponentType<NodeComponentProps<TData, TNodePublicState>>;
105-
placeholder?: ReactNode;
106-
async?: boolean;
132+
listRef?: Ref<TListComponent>;
107133
rowComponent?: ComponentType<ListChildComponentProps>;
108-
treeWalker: TreeWalker<TData>;
109134
}>;
110135

111136
export type TreeState<
112137
TData extends NodeData,
113-
TNodePublicState extends NodePublicState<TData>
114-
> = Readonly<{
115-
order?: string[];
116-
computeTree: TreeComputer<any, any, any, any>;
117-
records: ReadonlyMap<string | symbol, NodeRecord<TNodePublicState>>;
118-
recomputeTree: (
119-
options: OpennessState<TData, TNodePublicState>,
120-
) => Promise<void>;
121-
setState: Component<any, TreeState<TData, TNodePublicState>>['setState'];
122-
treeWalker: TreeWalker<TData>;
123-
124-
// A simple hack to get over the PureComponent shallow comparison
125-
updateRequest: object;
126-
}>;
138+
TNodePublicState extends NodePublicState<TData>,
139+
TListComponent extends FixedSizeList | VariableSizeList
140+
> = TreeComputerState<TData, TNodePublicState> &
141+
Readonly<{
142+
attachRefs: RefCallback<TListComponent>;
143+
computeTree: TreeComputer<any, any, any, any>;
144+
list: RefObject<TListComponent>;
145+
recomputeTree: (
146+
options: OpennessState<TData, TNodePublicState>,
147+
) => Promise<void>;
148+
treeWalker: TreeWalker<TData>;
149+
}>;
127150

128151
export type TypedListChildComponentData<
129152
TData extends NodeData,
@@ -183,7 +206,7 @@ export const Row = <
183206
export type TreeCreatorOptions<
184207
TData extends NodeData,
185208
TNodePublicState extends NodePublicState<TData>,
186-
TState extends TreeState<TData, TNodePublicState>
209+
TState extends TreeComputerState<TData, TNodePublicState>
187210
> = Readonly<{
188211
createRecord: (
189212
data: TData,
@@ -204,8 +227,8 @@ export type TreeComputerOptions<
204227
export type TreeComputer<
205228
TData extends NodeData,
206229
TNodePublicState extends NodePublicState<TData>,
207-
TProps extends TreeProps<TData, TNodePublicState>,
208-
TState extends TreeState<TData, TNodePublicState>
230+
TProps extends TreeComputerProps<TData>,
231+
TState extends TreeComputerState<TData, TNodePublicState>
209232
> = (
210233
props: TProps,
211234
state: TState,
@@ -219,8 +242,8 @@ export type TreeComputer<
219242
const generateNewTree = <
220243
TData extends NodeData,
221244
TNodePublicState extends NodePublicState<TData>,
222-
TProps extends TreeProps<TData, TNodePublicState>,
223-
TState extends TreeState<TData, TNodePublicState>
245+
TProps extends TreeComputerProps<TData>,
246+
TState extends TreeComputerState<TData, TNodePublicState>
224247
>(
225248
{createRecord}: TreeCreatorOptions<TData, TNodePublicState, TState>,
226249
{buildingTaskTimeout, placeholder, async = false, treeWalker}: TProps,
@@ -379,8 +402,8 @@ const MAX_FUNCTION_ARGUMENTS = 32768;
379402
const updateExistingTree = <
380403
TData extends NodeData,
381404
TNodePublicState extends NodePublicState<TData>,
382-
TProps extends TreeProps<TData, TNodePublicState>,
383-
TState extends TreeState<TData, TNodePublicState>
405+
TProps extends TreeComputerProps<TData>,
406+
TState extends TreeComputerState<TData, TNodePublicState>
384407
>(
385408
{order, records}: TState,
386409
{opennessState}: TreeComputerOptions<TData, TNodePublicState>,
@@ -564,8 +587,8 @@ const updateExistingTree = <
564587
export const createTreeComputer = <
565588
TData extends NodeData,
566589
TNodePublicState extends NodePublicState<TData>,
567-
TProps extends TreeProps<TData, TNodePublicState>,
568-
TState extends TreeState<TData, TNodePublicState>
590+
TProps extends TreeComputerProps<TData>,
591+
TState extends TreeComputerState<TData, TNodePublicState>
569592
>(
570593
creatorOptions: TreeCreatorOptions<TData, TNodePublicState, TState>,
571594
): TreeComputer<TData, TNodePublicState, TProps, TState> => (
@@ -580,8 +603,8 @@ export const createTreeComputer = <
580603
class Tree<
581604
TData extends NodeData,
582605
TNodePublicState extends NodePublicState<TData>,
583-
TProps extends TreeProps<TData, TNodePublicState>,
584-
TState extends TreeState<TData, TNodePublicState>,
606+
TProps extends TreeProps<TData, TNodePublicState, TListComponent>,
607+
TState extends TreeState<TData, TNodePublicState, TListComponent>,
585608
TListComponent extends FixedSizeList | VariableSizeList
586609
> extends PureComponent<TProps, TState> {
587610
public static defaultProps: Partial<DefaultTreeProps> = {
@@ -592,26 +615,26 @@ class Tree<
592615
props: DefaultTreeProps,
593616
state: DefaultTreeState,
594617
): Partial<DefaultTreeState> | null {
595-
const {treeWalker} = props;
596-
const {computeTree, order, treeWalker: oldTreeWalker} = state;
618+
const {listRef = null, treeWalker} = props;
619+
const {computeTree, list, order, treeWalker: oldTreeWalker} = state;
597620

598621
return {
622+
attachRefs: mergeRefs([list, listRef]),
599623
...(treeWalker !== oldTreeWalker || !order
600624
? computeTree(props, state, {refresh: true})
601625
: null),
602626
treeWalker,
603627
};
604628
}
605629

606-
protected readonly list: React.RefObject<TListComponent> = React.createRef();
607-
608630
public constructor(props: TProps, context: any) {
609631
super(props, context);
610632

611633
this.getRecordData = this.getRecordData.bind(this);
612634

613635
/* eslint-disable react/no-unused-state,@typescript-eslint/consistent-type-assertions */
614636
this.state = {
637+
list: createRef(),
615638
recomputeTree: this.recomputeTree.bind(this),
616639
setState: this.setState.bind(this),
617640
} as TState;
@@ -653,12 +676,13 @@ class Tree<
653676
}
654677

655678
public scrollTo(scrollOffset: number): void {
656-
this.list.current?.scrollTo(scrollOffset);
679+
// eslint-disable-next-line react/destructuring-assignment
680+
this.state.list.current?.scrollTo(scrollOffset);
657681
}
658682

659683
public scrollToItem(id: string, align?: Align): void {
660684
// eslint-disable-next-line react/destructuring-assignment
661-
this.list.current?.scrollToItem(this.state.order!.indexOf(id), align);
685+
this.state.list.current?.scrollToItem(this.state.order!.indexOf(id), align);
662686
}
663687
}
664688

0 commit comments

Comments
 (0)