Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,57 @@ If you find any problem, you can open an issue, I will resolve it as soon as pos

If you have any suggestion, please also open an issue, it will be greatly appreciated!

## Subscribable Data
Issues arise when trying to use dynamic *maxVisibleSlides* property with dynamics items
(see details)[https://github.com/BotDanny/react-stacked-center-carousel/issues/22]
To work around this use subscribable data object
```
import React, { useEffect } from "react";
import { PromisedData, ResponsiveContainer, StackedCarousel } from "react-stacked-center-carousel/src";

// component for individual slides
const CarouselItem = React.memo(function (props: any) {
const item = props.data[props.dataIndex];
return (
<span>{item}</span>
);
});

// new data from Promise, RxJs Observable, fetch api, w/e
const fetchedItems = new Promise<any[]>((resolve, reject) => {
setTimeout(() => resolve(['one', 'two', 'three']), 1000);
});

// PromisedData object with initial items; RxJs BehaviorSubject can also be used
const promisedItems = new PromisedData(['one', 'two']);

export default function CarouselExample () {
const ref = React.useRef();

useEffect(() => {
fetchedItems.then(newItems => promisedItems.next(newItems))
}, []);

return (
<ResponsiveContainer
carouselRef={ref}
render={(parentWidth, carouselRef) => {
return (
<StackedCarousel
ref={carouselRef}
slideComponent={CarouselItem}
slideWidth={parentWidth < 800 ? parentWidth - 40 : 750}
carouselWidth={parentWidth}
promisedData={promisedItems}
maxVisibleSlide={promisedItems.value.length >= 3 ? 3 : 1}
useGrabCursor
/>
);
}}
/>
);
}
```

## License

Expand Down
36 changes: 28 additions & 8 deletions src/carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
staticSlideInfo,
renderedSlide
} from './interfaces';
import { IPromisedSub } from './promiseddata';

export default class StackedCarousel extends React.PureComponent<props, state> {
static defaultScaleFactor: number = 0.85;
Expand All @@ -21,16 +22,19 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
private centerPosition: number;
private maxZIndex: number;
private renderedSlidePerSide: number;

private subscription?: IPromisedSub;

private validateProps = () => {
const {
currentVisibleSlide,
maxVisibleSlide,
fadeDistance,
customScales,
data
data,
promisedData
} = this.props;
if (data.length < (maxVisibleSlide + 1) / 2) {

if ((promisedData ? promisedData.value : data).length < (maxVisibleSlide + 1) / 2) {
throw Error(
'you must have more than (maxVisibleSlide + 1) / 2 data item'
);
Expand All @@ -57,6 +61,10 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
};

private initializeProperties = (constructor: boolean = false) => {
// recalculate everything as if newly constructed
if (this.state?.dataUpdated)
constructor = true;

this.validateProps();
const {
carouselWidth,
Expand All @@ -78,7 +86,7 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
: this.state.renderedSlides.filter(({ slideIndex, dataIndex }) => {
return dataIndex === -1 || Math.abs(slideIndex) <= this.slidePerSide;
});
const slideInfoMap = {};
const slideInfoMap: any = {};

const newCenterSlideRelativeIndex = constructor
? (totalRenderCount - 1) / 2
Expand Down Expand Up @@ -241,6 +249,11 @@ export default class StackedCarousel extends React.PureComponent<props, state> {

constructor(props: props) {
super(props);
// mutually exclusive objects
if (props.promisedData && props.data) {
console.warn('`dataPromise` defined. `data` property will be ignored.');
}

const {
renderedSlides,
slideInfoMap,
Expand All @@ -257,7 +270,7 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
this.height = this.props.height || 0;
this.listRef = React.createRef();
this.clearSlideTimeout = null;
this.keyCount = props.data.length;
this.keyCount = (props.promisedData ? props.promisedData.value : props.data).length;
this.addedSlide = 0;
this.centerPosition = centerPosition;
this.maxZIndex = 100;
Expand All @@ -279,11 +292,15 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
}

componentDidMount() {
this.subscription = this.props.promisedData?.subscribe(_ => {
this.setState({...this.state, dataUpdated: true});
});
this.updateHeight();
}

componentWillUnmount() {
clearTimeout(this.clearSlideTimeout);
this.subscription?.unsubscribe();
}

componentDidUpdate(prevProps: props) {
Expand Down Expand Up @@ -326,8 +343,8 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
};

private modDataRange = (n: number) => {
const { data } = this.props;
const m = data.length;
const { data, promisedData } = this.props;
const m = (promisedData ? promisedData.value : data).length;
return ((n % m) + m) % m;
};

Expand Down Expand Up @@ -697,6 +714,7 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
transitionTime = StackedCarousel.defaultTransitionTime,
className,
data,
promisedData,
slideWidth,
customTransition,
carouselWidth,
Expand All @@ -706,6 +724,8 @@ export default class StackedCarousel extends React.PureComponent<props, state> {

const cursor =
(useGrabCursor && (swipeStarted ? 'grabbing' : 'grab')) || 'default';

let data2Use = promisedData ? promisedData.value : data;
return (
<div
className={`react-stacked-center-carousel ${className || ''}`}
Expand Down Expand Up @@ -763,7 +783,7 @@ export default class StackedCarousel extends React.PureComponent<props, state> {
{dataIndex !== -1 && (
<Component
dataIndex={dataIndex}
data={data}
data={data2Use}
slideIndex={slideIndex}
isCenterSlide={isCenterSlide}
swipeTo={this.swipeTo}
Expand Down
4 changes: 3 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import StackedCarousel from './carousel';
import { props, slideProp } from './interfaces'
import { PromisedData } from './promiseddata';
import ResponsiveContainer, {ResponsiveContainerProps} from './responsiveContainer';
export {
StackedCarousel,
ResponsiveContainer,
ResponsiveContainerProps,
props as StackedCarouselProps,
slideProp as StackedCarouselSlideProps
slideProp as StackedCarouselSlideProps,
PromisedData
};
9 changes: 8 additions & 1 deletion src/interfaces.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { IPromisedData, IPromisedSub } from "./promiseddata";

export interface props {
/**
* An array of data items used to populate your slides.
*/
data: any[];
data?: any[];
/**
* An promise
*/
promisedData?: IPromisedData;
/**
* The width of the carousel in px.
*/
Expand Down Expand Up @@ -120,4 +126,5 @@ export interface state {
swipePositionInfo: swipePositionInfo[];
swipRight: boolean;
tempShift: boolean;
dataUpdated?: boolean;
}
61 changes: 61 additions & 0 deletions src/promiseddata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Listenable interface compatible with RXJS BehaviorSubject
*/
export interface IPromisedData {
get value(): any;
next(data: any): void;
subscribe(next?: ((value: any) => void), error?: (error: any) => void, complete?: () => void): IPromisedSub;
}

/**
* interface compatible with RXJS Subscription
*/
export interface IPromisedSub {
unsubscribe(): void;
}

/**
* Fake listenable object compatible with RXJS BehaviorSubject
*/
export class PromisedData implements IPromisedData {
private _data: any = [];
private _handlers: ((data: any[]) => void)[] = [];

get value() { return this._data; }

constructor(initialData: any[]) {
this._data = initialData;
}

next(data: any[]) {
this._data = data;
this._handlers.forEach(h => h?.call(this, data));
}

subscribe (completed: (data: any[]) => void): IPromisedSub {
this._handlers.push(completed);

// immediately send last value
setTimeout(() => completed?.call(this, this._data), 1);

return new PromisedSubscription(this, completed);
}

removeHadler (handler: any) {
this._handlers = this._handlers.filter(h => h !== handler);
}
}

class PromisedSubscription implements IPromisedSub {
private _parent: PromisedData;
private _handler: any;

constructor(parent: PromisedData, handler: any) {
this._handler = handler;
this._parent = parent;
}

unsubscribe() {
this._parent.removeHadler(this._handler);
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
// "suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true
Expand Down