Skip to main content

Simple Pattern

Below is an example implementation of a simple pattern which utilizes the hooks provided by react-swipeable within a TypeScript context.

Simple Pattern Code Source

You can see this full example as pure code within the Pattern.tsx file within the React-Swipeable repo directly.

Simple Pattern Live Preview

Within this text area container, swipe the pattern seen below to make the carousel navigate to the next slide.

Swipe:

Simple Pattern Code Explained

Import the hook directly from the react-swipeable library, along with the directions from the library, and the SwipeEventData. In our example, we built and imported a local set of UI components: you can utilize your own UI and styling, or use your favorite UI component library of choice.

import React, { FunctionComponent, ReactNode } from 'react';
import { useSwipeable, UP, DOWN, SwipeEventData } from 'react-swipeable';
import {
Wrapper,
CarouselContainer,
CarouselSlot,
PatternBox,
PREV,
NEXT,
D
} from '../components';

In our example, we utilize SVGs for our UpArrow and DownArrow to give indications of when someone is successfully activating the pattern for user feedback, but know you can use whatever UI library of your choice, or stylize your own!

const UpArrow = ({active}: {active: boolean}) => (
<svg viewBox="0 0 16 16" version="1.1" style={{width: '15px'}}>
<g transform="translate(-35.399 -582.91)">
<path style={{fill: active ? '#EEEE00' : '#000000'}} d="m40.836 598.91v-6.75h-5.4375l4-4.625 4-4.625 4 4.625 4 4.625h-5.0938v6.75h-5.4688z" />
</g>
</svg>
);

const DownArrow = ({active}: {active: boolean}) => (
<svg viewBox="0 0 16 16" version="1.1" style={{width: '15px'}}>
<g transform="translate(-35.399 -598.91)">
<path style={{fill: active ? '#EEEE00' : '#000000'}} d="m40.836 598.91v6.75h-5.4375l4 4.625 4 4.625 4-4.625 4-4.625h-5.0938v-6.75h-5.4688z" />
</g>
</svg>
);

Next, we set up our types for the Directions, CarouselState, and CarouselAction.

type Direction = typeof PREV | typeof NEXT;

interface CarouselState {
pos: number;
sliding: boolean;
dir: Direction;
}

type CarouselAction =
| { type: Direction, numItems: number }
| { type: 'stopSliding' };

Below, we create a function called getOrder, which drives the position of each item in the carousel, and what order of position each will be displayed in the context of the carousel. Then, we set a pattern as an array of the pattern we want the user to follow to unlock the slide action. Finally here, we then set getInitialState, setting the position of the initial items, the sliding, as false, and the direction.


const getOrder = (index: number, pos: number, numItems: number) => {
return index - pos < 0 ? numItems - Math.abs(index - pos) : index - pos;
};

const pattern = [UP, DOWN, UP, DOWN];

const getInitialState = (numItems: number): CarouselState => ({ pos: numItems - 1, sliding: false, dir: NEXT });

At the bottom of the file, we set up a reducer for controlling the action of the Carousel utilizing a switch to set the CarouselState logic.

function reducer(state: CarouselState, action: CarouselAction): CarouselState {
switch (action.type) {
case PREV:
return {
...state,
dir: PREV,
sliding: true,
pos: state.pos === 0 ? action.numItems - 1 : state.pos - 1
};
case NEXT:
return {
...state,
dir: NEXT,
sliding: true,
pos: state.pos === action.numItems - 1 ? 0 : state.pos + 1
};
case 'stopSliding':
return { ...state, sliding: false };
default:
return state;
}
}

Then, building upon the reducer logic, the <Carousel> is constructed. We hold the number of items within numItems, and utilize the reducer within the React.useReducer hook.

By creating the slide, as a const, we can utilize that to call within handleSwiped as an action that is called upon the user successfully execution of the pattern.

It may help to briefly look at the handlers for a moment, and how we utilize useSwipeable. Within this, with each onSwiped, we call handleSwiped. So for each swipe the user takes within the text box above the carousel, we execute handleSwiped and pass along the eventData. If the eventData.dir matches the pattern for this indexed (pIdx) item, and the direction indicated, then we setPIdx to a greater number.

What does this do? Two things: it helps us know when the user successfully got to the end of the pattern, and activate the slide action, and it also controls the arrows activating the color within the <PatternBox> to give feedback to the user that they were successful in activating the steps of the pattern!

Two other important items to note: we utilized onTouchStartOrOnMouseDown to pass through event.preventDefault() as a callback, and used touchEventOptions: {passive: false} in case certain browsers ignored the preventDefault() callback bubbling up.

From there, the rest of the UI of the component is built. The <PatternBox> holds where the user swipes in order to interact with the Carousel itself, along with the arrows that give the feedback to the user that the pattern was successful. The <CarouselContainer> holds the Carousel display and items. Our Simple Pattern is complete!

const Carousel: FunctionComponent<{children: ReactNode}> = (props) => {
const numItems = React.Children.count(props.children);
const [state, dispatch] = React.useReducer(reducer, getInitialState(numItems));

const slide = (dir: Direction) => {
dispatch({ type: dir, numItems });
setTimeout(() => {
dispatch({ type: 'stopSliding' });
}, 50);
};

const [pIdx, setPIdx] = React.useState(0);

const handleSwiped = (eventData: SwipeEventData) => {
if (eventData.dir === pattern[pIdx]) {
// user successfully got to the end of the pattern!
if (pIdx + 1 === pattern.length) {
setPIdx(pattern.length);
slide(NEXT);
setTimeout(() => {
setPIdx(0);
}, 50);
} else {
// user is cont. with the pattern
setPIdx((i) => (i += 1));
}
} else {
// user got the next pattern step wrong, reset pattern
setPIdx(0);
}
};

const handlers = useSwipeable({
onSwiped: handleSwiped,
onTouchStartOrOnMouseDown: (({ event }) => event.preventDefault()),
touchEventOptions: { passive: false },
preventScrollOnSwipe: true,
trackMouse: true
});

return (
<>
<PatternBox {...handlers}>
Swipe the pattern below, within this box, to make the carousel go to the next
slide
{`\n`}
<p style={{textAlign: 'center', paddingTop: '15px'}}>
Swipe:
<D><UpArrow active={pIdx > 0} /></D>
<D><DownArrow active={pIdx > 1} /></D>
<D><UpArrow active={pIdx > 2} /></D>
<D><DownArrow active={pIdx > 3} /></D>
</p>
</PatternBox>
<div style={{paddingBottom: '15px'}}>
<Wrapper>
<CarouselContainer dir={state.dir} sliding={state.sliding}>
{React.Children.map(props.children, (child, index) => (
<CarouselSlot
key={index}
order={getOrder(index, state.pos, numItems)}
>
{child}
</CarouselSlot>
))}
</CarouselContainer>
</Wrapper>
</div>
</>
);
};