How do I use a useSwipeable handler for multiple elements

Viewed 93

I'm trying to add support for touch events on slider inputs in my React app. Taps and drags work fine with bare React. The only other touch event I need is onTouchEnd to determine when a user has finished dragging a slider and the new value is to be committed.

I'm trying to use react-swipeable. I don't know if my question is specific to Swipeable or more generally to React. I've created the suggested handler for the onTouchEnd event and it works perfectly, but for only one element. I am mapping through 20 or 30 sliders and find that only the LAST slider works properly; the other sliders do not fire the handler at all.

I've tried it with and without the refPassthrough enhancement. The problem may be my limited understanding of useRef.

In the code below, three horizontal divs are created. Touching on the last (blue) div logs the event; the others don't.

I've also provided this working code in CodeSandBox

Any help would be appreciated, Bill

import { useRef } from "react";
import { useSwipeable } from "react-swipeable";

export default function App() {
  const handlers = useSwipeable({
    onTouchEndOrOnMouseUp: (e) => console.log("User Touched!", e)
  });

  const myRef = useRef();
  const refPassthrough = (el) => {
    handlers.ref(el);
    myRef.current = el;
  };

  return (
    <div>
      <h1>Re-use Swipeable handlers </h1>
      <div
        {...handlers}
        style={{ backgroundColor: "red", height: "50px" }}
      ></div>
      <div
        {...handlers}
        ref={refPassthrough}
        style={{ backgroundColor: "green", height: "50px" }}
      ></div>
      <div
        {...handlers}
        style={{ backgroundColor: "blue", height: "50px" }}
      ></div>
    </div>
  );
}
2 Answers

This is AN answer. It doesn't work perfectly for me because my use is within a function, not a functional component. I will try to re-work my app so that I can call useSwipeable only within functional Components.

import { useSwipeable } from "react-swipeable";

export default function App() {
  return (
    <div>
      <h1>Re-use Swipeable handlers </h1>
      <div
        {...useSwipeable({
          onTouchEndOrOnMouseUp: () => console.log("touch red")
        })}
        style={{ backgroundColor: "red", height: "50px" }}
      ></div>
      <div
        {...useSwipeable({
          onTouchEndOrOnMouseUp: () => console.log("touch green")
        })}
        style={{ backgroundColor: "green", height: "50px" }}
      ></div>
      <div
        {...useSwipeable({
          onTouchEndOrOnMouseUp: () => console.log("touch blue")
        })}
        style={{ backgroundColor: "blue", height: "50px" }}
      ></div>
    </div>
  );
}

Here is a more complete answer. Also in CodeSandbox

// It's surprisingly hard to process an onTouchEnd event for a slider in React.
// The useSwipeable hook does the heavy lifting.
// However, because it is a hook, it can't be .map-ped unless it is
// wrapped in a component.
// And, once it is wrapped in a component, it is hard to communicate
// onChange events to a parent component (the ususal tricks of passing
// setState or other changehandler do not seem to work for continuous
// slider onChange events.
// The approach here is to handle all of the onChange stuff in the wrapped
// component, including a local feedback display.
// Then, on the onTouchEnd event, the "normal" communication of the final
// value is returned to the parent via the dispatch prop.
import { useReducer, useState } from "react";
import { useSwipeable } from "react-swipeable";

export default function App() {
  const [reduceState, dispatch] = useReducer(reducer, {
    name1: "33",
    name2: "66"
  });

  function reducer(state, action) {
    return { ...state, [action.type]: action.data };
  }

  const MapWrappedSlider = (props) => {
    const [currentValue, setCurrentValue] = useState(props.initialValue);
    return (
      <div style={{ backgroundColor: "cornsilk" }}>
        <h2>{currentValue}</h2>
        <input
          type="range"
          value={currentValue}
          {...{
            onChange: (e) => setCurrentValue(e.target.value),
            onMouseUp: () =>
              dispatch({ type: props.paramName, data: currentValue })
          }}
          {...useSwipeable({
            // note: the current version of useSwipeable does not actually
            // handle onMouseUp here. Also, the advertised onTouchEnd
            // does not actually handle onTouchEnd
            onTouchEndOrOnMouseUp: () =>
              dispatch({ type: props.paramName, data: currentValue })
          })}
        />
      </div>
    );
  };

  return (
    <div style={{ textAlign: "center" }}>
      <h1>SWIPEABLE MAPPED SLIDERS</h1>
      <div
        style={{
          display: "flex",
          flexDirection: "row",
          justifyContent: "space-around"
        }}
      >
        <h2>{reduceState.valueName}</h2>
        {["name1", "name2"].map((paramName) => {
          return (
            <div key={paramName}>
              <h1>{reduceState[paramName]}</h1>
              <MapWrappedSlider
                paramName={paramName}
                initialValue={reduceState[paramName]}
                dispatch={dispatch}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
}
Related