Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
feat(datascrollercontext): add ability to use context instead of rowdata
Browse files Browse the repository at this point in the history
  • Loading branch information
benox3 committed Mar 3, 2020
1 parent 9ea3b09 commit 9f3589d
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 4 deletions.
109 changes: 109 additions & 0 deletions src/components/DataScrollerContext/DataScrollerContext.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';
import { DataScrollerContextProvider, getRowData } from './DataScrollerContext';
import { render, cleanup } from 'react-testing-library';

beforeEach(() => {
cleanup();
});

describe('getRowData', () => {
it('get the data correctly', () => {
const data = {
foo: 'foo',
bar: 'bar',
bam: 'bam',
};

const Child = (props: any) => {
return (
<div>
{Object.entries(props.data).map(([key, value]) => (
<div key={key}>{value}</div>
))}
</div>
);
};

const TestChild = getRowData()(Child);

const TestWrapper = () => (
<DataScrollerContextProvider data={data}>
<TestChild />
</DataScrollerContextProvider>
);

const { getByText } = render(<TestWrapper />);

expect(getByText('foo')).toBeTruthy();
expect(getByText('bar')).toBeTruthy();
expect(getByText('bam')).toBeTruthy();
});
it('gets only the requested correctly', () => {
const data = {
foo: 'foo',
bar: 'bar',
bam: 'bam',
};

const Child = (props: any) => {
return (
<div>
{Object.entries(props).map(([key, value]) => (
<div key={key}>{value}</div>
))}
</div>
);
};

const TestChild = getRowData((_, data: any) => ({
bar: data.bar,
}))(Child);

const TestWrapper = () => (
<DataScrollerContextProvider data={data}>
<TestChild />
</DataScrollerContextProvider>
);

const { getByText } = render(<TestWrapper />);

expect(getByText('bar')).toBeTruthy();
expect(() => getByText('foo')).toThrow();
expect(() => getByText('bam')).toThrow();
});

it('passes the props correctly', () => {
const data = {
bam: 'bam',
};

const Child = (props: any) => {
return (
<div>
{Object.entries(props).map(([key, value]) => (
<div key={key}>{value}</div>
))}
</div>
);
};

const TestChild = getRowData((props: any, data: any) => {
return {
...props,
bam: data.bam,
};
})(Child);

const TestWrapper = () => (
<DataScrollerContextProvider data={data}>
<TestChild foo="foo" bar="bar" />
</DataScrollerContextProvider>
);

const { getByText } = render(<TestWrapper />);

expect(getByText('foo')).toBeTruthy();
expect(getByText('bar')).toBeTruthy();
expect(getByText('bam')).toBeTruthy();
});
});
59 changes: 59 additions & 0 deletions src/components/DataScrollerContext/DataScrollerContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useContext } from 'react';

type DataScrollerContextValue = {
data: any;
};

export const DataScrollerContext = React.createContext<
DataScrollerContextValue
>({
data: {},
});

type ComponentProps<Data = any> = {
data: Data;
children: React.ReactNode;
};

export function DataScrollerContextProvider<Data>(props: ComponentProps<Data>) {
return (
<DataScrollerContext.Provider value={{ data: props.data }}>
{props.children}
</DataScrollerContext.Provider>
);
}

function defaultMapContextDataValueToProps<Props, Data>(
props: Props,
data: Data,
): any {
return { ...props, data };
}

type MapContextValueToProps<OuterProps, Data, InnerProps> = (
props: OuterProps,
data: Data,
) => InnerProps;

export function getRowData<OuterProps, Data, InnerProps>(
mapContextValueToProps?: MapContextValueToProps<OuterProps, Data, InnerProps>,
) {
return function(Component: React.FC<InnerProps>) {
const MemoizedComponent = (React.memo(Component) as any) as React.FC<
InnerProps
>;

return function<P extends OuterProps>(props: P) {
const contextValue = useContext(DataScrollerContext);

const propsWithState = mapContextValueToProps
? mapContextValueToProps(props, contextValue.data)
: (defaultMapContextDataValueToProps<OuterProps, Data>(
props,
contextValue.data,
) as InnerProps);

return <MemoizedComponent {...propsWithState} />;
};
};
}
1 change: 1 addition & 0 deletions src/components/DataScrollerContext/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DataScrollerContextProvider, getRowData } from './DataScrollerContext';
2 changes: 1 addition & 1 deletion src/components/RowChildren/RowChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const RowChildren = (props: RowChildrenProps) => {
<CellRenderer
columnIndex={adjustedColumnIndex}
rowIndex={props.rowIndex}
cellData={props.rowData[column.dataKey]}
cellData={props.rowData ? props.rowData[column.dataKey] : {}}
columnData={column.columnData}
dataKey={column.dataKey}
rowData={props.rowData}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Rows/Rows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ function Rows({
);
}

export default Rows;
export default React.memo(Rows);
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export {
export { default as Group, Props as GroupProps } from './components/Group';
export { default as Column } from './components/Column';
export { default as Row } from './components/Row';
export {
DataScrollerContextProvider,
getRowData,
} from './components/DataScrollerContext';

/* Types */
export * from './types';
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';

export type CellRendererArgs<ColumnData = any> = {
export type CellRendererArgs<ColumnData = {}> = {
cellData: any;
columnData?: ColumnData;
columnData: ColumnData;
columnIndex: number;
dataKey: string;
rowData: any;
Expand Down
133 changes: 133 additions & 0 deletions stories/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { Column } from '../src/';
import Group from '../src/components/Group';
import Row from '../src/components/Row';
import { RowProps, ColumnProps, GetRowKey } from '../src/types';
import {
getRowData,
DataScrollerContextProvider,
} from '../src/components/DataScrollerContext/DataScrollerContext';

import { storiesOf } from '@storybook/react';
import DataScroller, {
Expand Down Expand Up @@ -284,3 +288,132 @@ storiesOf('react-data-scroller', module).add('custom rowRenderer', () => {
/>
);
});

storiesOf('react-data-scroller', module).add('DataScrollerContext', () => {
const DetachedIndexCell = ({ index }: { index: number }) => {
return (
<div
style={{
boxShadow: '0 0 5px 2px black',
}}
>
{index}
</div>
);
};

const DetachedFirstNameCell = ({ firstName }: { firstName: string }) => {
return <CustomInput value={firstName} />;
};

const DetachedLastNameCell = ({ lastName }: { lastName: string }) => {
return <div>{lastName}</div>;
};

const InjectedIndexCell = getRowData((props: any, data: any) => ({
...props,
index: data[props.rowIndex].index,
}))(DetachedIndexCell);

const InjectedFirstNameCell = getRowData((props: any, data: any) => ({
...props,
firstName: data[props.rowIndex].firstName,
}))(DetachedFirstNameCell);

const InjectedLastNameCell = getRowData((props: any, data: any) => ({
...props,
lastName: data[props.rowIndex].lastName,
}))(DetachedLastNameCell);

const initialContextColumns = [
{
cellRenderer: InjectedIndexCell,
columnData: {},
dataKey: 'lastName',
headerRenderer: ({ columnData }: HeaderRendererArgs) => (
<div style={{ background: 'white' }}>
Header {columnData.columnIndex}
</div>
),
label: 'index',
width: 200,
},
{
cellRenderer: InjectedLastNameCell,
columnData: {},
dataKey: 'lastName',
headerRenderer: ({ columnData }: HeaderRendererArgs) => (
<div style={{ background: 'white' }}>
Header {columnData.columnIndex}
</div>
),
label: 'last name',
width: 200,
},
{
cellRenderer: InjectedFirstNameCell,
columnData: {},
dataKey: 'firstName',
headerRenderer: ({ columnData }: HeaderRendererArgs) => (
<div>Header{columnData.columnIndex}</div>
),
label: 'first name',
width: 200,
},
];

const rowGetter = () => {};

let contextColumns: any[] = [];
for (let counter = 0; counter < 10; counter += 1) {
contextColumns = [...initialContextColumns, ...(contextColumns || [])];
}

contextColumns = contextColumns.map((column, index) => ({
...column,
columnData: { ...(column.columnData || {}), columnIndex: index },
}));

let frozenContextColumns: any[] = [];
for (let counter = 0; counter < 2; counter += 1) {
frozenContextColumns = [
...initialContextColumns,
...(frozenContextColumns || []),
];
}

frozenContextColumns = frozenContextColumns.map((column, index) => ({
...column,
columnData: { ...(column.columnData || {}), columnIndex: index },
}));

return (
<DataScrollerContextProvider data={rows}>
<DataScroller
rowCount={rowCount}
rowGetter={rowGetter}
rowHeight={50}
height={500}
headerHeight={100}
width={500}
initialTopRowIndex={0}
groupHeaderHeight={30}
columns={[
<Group key="groupa" headerRenderer={GroupHeaderA}>
{contextColumns.map((column, index) => (
<Column key={index} {...column} />
))}
</Group>,
<Group key="groupb" headerRenderer={GroupHeaderB}>
{contextColumns.map((column, index) => (
<Column key={index} {...column} />
))}
</Group>,
]}
frozenColumns={frozenContextColumns.map((column, index) => (
<Column key={index} {...column} />
))}
/>
</DataScrollerContextProvider>
);
});

0 comments on commit 9f3589d

Please sign in to comment.