Question: I need to transform React Swipe this file to RN equivalent and it should respect the condition it should load only 3 articles at a time and display only one item
I am migrating a project from React + Cordova Project to Pure React Native.
I have Swipe Functionality between bunch for i.e., let say 64 article. It will load first two articles when user swipes forward it will load next article and it will not load all the articles at a
time.
interface IProps {
slideRange?: number
onChangeIndex?: (index: number) => void
onTransitionEnd?: (index: number) => void
axis?: 'x' | 'x-reverse' | 'y' | 'y-reverse'
hysteresis?: number
index?: number
threshold?: number
resistance?: boolean
slides?: IGeneralArticle[]
articleSource?: string
}
interface IState {
indexLatest: number
isDragging: boolean
displaySameSlide: boolean
isShowingSwipeAnimation: boolean
}
// We can only have one node at the time claiming ownership for handling the swipe.
// Otherwise, the UX would be confusing.
// That's why we use a singleton here.
const UNCERTAINTY_THRESHOLD = 3
const RESISTANCE_COEF = 0.6
const axisProperties = {
rotationMatrix: {
x: {
x: [1, 0],
y: [0, 1],
},
'x-reverse': {
x: [-1, 0],
y: [0, 1],
},
y: {
x: [0, 1],
y: [1, 0],
},
'y-reverse': {
x: [0, -1],
y: [1, 0],
},
},
}
// We are using a 2x2 rotation matrix.
const applyRotationMatrix = (
touch: { pageX: number; pageY: number },
axis: 'x' | 'x-reverse' | 'y' | 'y-reverse'
): { pageX: number; pageY: number } => {
const rotationMatrix = axisProperties.rotationMatrix[axis]
return {
pageX: rotationMatrix.x[0] * touch.pageX + rotationMatrix.x[1] * touch.pageY,
pageY: rotationMatrix.y[0] * touch.pageX + rotationMatrix.y[1] * touch.pageY,
}
}
export class ReactSwipe extends React.PureComponent<IProps, IState> {
public static defaultProps: IProps = {
axis: 'x',
hysteresis: 0.6,
index: 0,
threshold: 5,
resistance: false,
}
private wrapperNode: HTMLDivElement
private containerNode: HTMLDivElement
private viewLength = 0
private startX = 0
private lastX = 0
private vx = 0
private startY = 0
private isSwiping: boolean = undefined
private started = false
private startIndex = 0
private indexCurrent: number = null
private tutorialStorageKey = 'ReactSwipeAnimationShown'
private handlerSwipeStart: EventListenerOrEventListenerObject = this.handleSwipeStart.bind(this)
private handlerSwipeEnd: EventListenerOrEventListenerObject = this.handleSwipeEnd.bind(this)
private handlerSwipeMove: EventListenerOrEventListenerObject = this.handleSwipeMove.bind(this)
private handlerTransitionEnd: EventListenerOrEventListenerObject = this.handleTransitionEnd.bind(this)
constructor(props: IProps) {
super(props)
this.state = {
indexLatest: props.index,
// Set to true as soon as the component is swiping.
// It's the state counter part of this.isSwiping.
isDragging: false,
// Let the render method that we are going to display the same slide than previously.
displaySameSlide: true,
isShowingSwipeAnimation: false,
}
}
public componentDidMount(): void {
this.setIndexCurrent(this.props.index)
this.wrapperNode.addEventListener('touchstart', this.handlerSwipeStart, { passive: true })
this.wrapperNode.addEventListener('touchend', this.handlerSwipeEnd, { passive: true })
this.wrapperNode.addEventListener('touchmove', this.handlerSwipeMove, { passive: false })
this.containerNode.addEventListener('transitionend', this.handlerTransitionEnd, { passive: true })
if (!storageService.getItem(this.tutorialStorageKey) && this.props.slides) {
delay(4000).then(() => {
if (!storageService.getItem(this.tutorialStorageKey)) {
this.startSwipeAnimation()
}
})
}
}
public UNSAFE_componentWillReceiveProps(nextprops: IProps): void {
if (this.indexCurrent !== nextprops.index && nextprops.index !== null) {
console.log('nextprops', nextprops)
this.setIndexCurrent(nextprops.index, 'transform 0.35s cubic-bezier(0.15, 0.3, 0.25, 1) 0s', true)
}
}
public componentWillUnmount(): void {
this.wrapperNode.removeEventListener('touchstart', this.handlerSwipeStart)
this.wrapperNode.removeEventListener('touchend', this.handlerSwipeEnd)
this.wrapperNode.removeEventListener('touchmove', this.handlerSwipeMove)
this.containerNode.removeEventListener('transitionend', this.handlerTransitionEnd)
}
public render(): JSX.Element {
const { children, slides, articleSource } = this.props
return (
<div className='swipe-wrapper' ref={el => (this.wrapperNode = el)}>
<div className='swipe-container' ref={el => (this.containerNode = el)}>
{slides &&
slides.map((slide, index) => {
// here is the condition it will check before rendering
return Math.abs(index - this.state.indexLatest) <= this.props.slideRange ? (
<div className='swipe-item ' key={index} style={{position: 'absolute', left: `${100 * index}%`}}>
{this.state.isShowingSwipeAnimation && index === this.state.indexLatest && (
<Component1/>
)}
<Component2
idURL={slide.idURL}
single={false}
visible={index === this.state.indexLatest}
articleSource={articleSource}
count={this.state.indexLatest + 1}
total={slides.length}
/>
</div>
) : null
}
)}
{!slides &&
React.Children.map(children, (child: JSX.Element, index: number) => {
if (!React.isValidElement(child)) {
return null
}
return (
<div className='swipe-item ' aria-hidden={index !== this.state.indexLatest} key={index}>
{child}
</div>
)
})}
</div>
</div>
)
}
private getChildrenCount(): number {
return this.props.slides ? this.props.slides.length - 1 : React.Children.toArray(this.props.children).length - 1
}
private computeIndex = (params: any): { index: number; startX: number } => {
const startIndex = params.startIndex
const startX = params.startX
const pageX = params.pageX
const viewLength = params.viewLength
const resistance = params.resistance
const indexMax = this.getChildrenCount()
let index = startIndex + (startX - pageX) / viewLength
let newStartX
if (!resistance) {
// Reset the starting point
if (index < 0) {
index = 0
newStartX = (index - startIndex) * viewLength + pageX
} else if (index > indexMax) {
index = indexMax
newStartX = (index - startIndex) * viewLength + pageX
}
} else if (index < 0) {
index = Math.exp(index * RESISTANCE_COEF) - 1
} else if (index > indexMax) {
index = indexMax + 1 - Math.exp((indexMax - index) * RESISTANCE_COEF)
}
return {
index,
startX: newStartX,
}
}
private setIndexCurrent(indexCurrent: number, transition = 'all 0s ease 0s', hasEnded = false): void {
this.indexCurrent = indexCurrent
console.log('setIndexCurrent', indexCurrent)
if (this.containerNode) {
const transform = `translate3d(-${indexCurrent * 100}%, 0, 0)`
this.containerNode.style.cssText = `transform: ${transform}; -webkit-transform: ${transform}; transition: ${transition}; -webkit-transition: ${transition};`
}
if (hasEnded) {
if (this.props.onChangeIndex && indexCurrent !== this.state.indexLatest) {
this.props.onChangeIndex(indexCurrent)
}
this.setState({
indexLatest: indexCurrent,
isDragging: false,
})
if (!storageService.getItem(this.tutorialStorageKey) && this.props.slides) {
this.stopSwipeAnimation()
}
}
}
private handleSwipeStart(event: TouchEvent): void {
const axis = this.props.axis
const touch = applyRotationMatrix(event.touches[0], axis)
this.viewLength = this.containerNode.getBoundingClientRect().width
this.startX = touch.pageX
this.lastX = touch.pageX
this.vx = 0
this.startY = touch.pageY
this.isSwiping = undefined
this.started = true
const computedStyle = window.getComputedStyle(this.containerNode)
const transform = computedStyle.getPropertyValue('-webkit-transform') || computedStyle.getPropertyValue('transform')
if (transform && transform !== 'none') {
const transformValues = transform
.split('(')[1]
.split(')')[0]
.split(',')
const rootStyle = window.getComputedStyle(this.containerNode)
const tranformNormalized = applyRotationMatrix(
{
pageX: parseInt(transformValues[4], 10),
pageY: parseInt(transformValues[5], 10),
},
axis
)
this.startIndex =
-tranformNormalized.pageX /
(this.viewLength - parseInt(rootStyle.paddingLeft, 10) - parseInt(rootStyle.paddingRight, 10)) || 0
}
}
private handleSwipeMove(event: TouchEvent): void {
const { axis, children, resistance } = this.props
console.log('handleSwipeMove', event,event.touches[0], this.props)
const touch = applyRotationMatrix(event.touches[0], axis) // We don't know yet.
const dx = Math.abs(touch.pageX - this.startX)
const dy = Math.abs(touch.pageY - this.startY)
const isSwiping = dx > dy && dx > UNCERTAINTY_THRESHOLD // We let the parent handle the scroll.
if (isSwiping && this.startX > 100 && !(touch.pageX - this.startX > 0 && this.indexCurrent === 0)) {
event.stopPropagation()
}
// The touch start event can be cancel.
// Makes sure we set a starting point.
if (!this.started) {
this.handleSwipeStart(event)
return
}
if (this.isSwiping === undefined) {
if (
this.startX < 25 ||
(this.indexCurrent === 0 && this.startX < touch.pageX) ||
(this.indexCurrent === this.getChildrenCount() && this.startX > touch.pageX)
) {
this.isSwiping = false
return
}
// We are likely to be swiping, let's prevent the scroll event.
if (dx > dy) {
event.preventDefault()
}
if (isSwiping || dy > UNCERTAINTY_THRESHOLD) {
this.isSwiping = isSwiping
this.startX = touch.pageX // Shift the starting point.
return // Let's wait the next touch event to move something.
}
}
if (!this.isSwiping) {
return
}
// We are swiping, let's prevent the scroll event.
event.preventDefault() // Low Pass filter.
this.vx = this.vx * 0.5 + (touch.pageX - this.lastX) * 0.5
this.lastX = touch.pageX
const computeIndex = this.computeIndex({
children,
resistance,
pageX: touch.pageX,
startIndex: this.startIndex,
startX: this.startX,
viewLength: this.viewLength,
})
const index = computeIndex.index
const startX = computeIndex.startX // Add support for native scroll elements.
if (startX) {
this.startX = startX
}
this.setIndexCurrent(index)
if (this.state.displaySameSlide || !this.state.isDragging) {
this.setState({
displaySameSlide: false,
isDragging: true,
})
}
}
private handleSwipeEnd(): void {
// The touch start event can be cancel.
// Makes sure that a starting point is set.
if (!this.started) {
return
}
this.started = false
if (!this.isSwiping) {
return
}
const indexLatest = this.state.indexLatest
const indexCurrent = this.indexCurrent
const delta = indexLatest - indexCurrent
let indexNew: number
// Quick movement
if (Math.abs(this.vx) > this.props.threshold) {
if (this.vx > 0) {
indexNew = Math.floor(indexCurrent)
} else {
indexNew = Math.ceil(indexCurrent)
}
} else if (Math.abs(delta) > this.props.hysteresis) {
// Some hysteresis with indexLatest
indexNew = delta > 0 ? Math.floor(indexCurrent) : Math.ceil(indexCurrent)
} else {
indexNew = indexLatest
}
const indexMax = this.getChildrenCount()
if (indexNew < 0) {
indexNew = 0
} else if (indexNew > indexMax) {
indexNew = indexMax
}
this.setIndexCurrent(indexNew, 'transform 0.35s cubic-bezier(0.15, 0.3, 0.25, 1) 0s', true)
}
private handleTransitionEnd(event: TouchEvent): void {
if (event.target !== this.containerNode) {
return
}
if (this.props.onTransitionEnd) {
this.props.onTransitionEnd(this.indexCurrent)
}
}
private startSwipeAnimation(): void {
this.setState({ isShowingSwipeAnimation: true })
}
private stopSwipeAnimation(): void {
this.setState({ isShowingSwipeAnimation: false })
}
}
i tried different things but it didn't help