How do I use a useSwipeable handler for multiple elements - reactjs

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>
);
}

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

React Select with avatar class component whats wrong here select not populating

Hi I had working select box with avatar on functional aporx. But I need build in functions to change selectbox properties like hidden: true/false, update option data function to add new option data to display selectbox with certain values on fly.
What I wrong here? Render part works as tested with functional version. The class factoring misses something as select box does not get info ether options and avatar to display and no width calculation happening.
Orginal functional code: https://codesandbox.io/s/react-select-longest-option-width-geunu?file=/src/App.js
Class based works but nodatin selectbox. Here is app.js with select.js fiddle: https://codesandbox.io/s/react-select-longest-option-width-forked-plqq0p?file=/src/Select.js
Source:
import React, { useRef } from "react";
import Select from "react-select";
class RtSelect extends React.Component {
constructor(props) {
super();
this.state = {
info: props.info,
options: props.options,
hidden: props.hidden,
menuIsOpen: false,
menuWidth: "",
IsCalculatingWidth: false
};
this.selectRef = React.createRef();
this.onMenuOpen = this.onMenuOpen.bind(this);
}
componentDidMount() {
if (!this.menuWidth && !this.isCalculatingWidth) {
setTimeout(() => {
this.setState({IsCalculatingWidth: true});
// setIsOpen doesn't trigger onOpenMenu, so calling internal method
this.selectRef.current.select.openMenu();
this.setState({MenuIsOpen: true});
}, 1);
}
}
onMenuOpen() {
if (!this.menuWidth && this.isCalculatingWidth) {
setTimeout(() => {
const width = this.selectRef.current.select.menuListRef.getBoundingClientRect()
.width;
this.setState({menuWidth: width});
this.setState({IsCalculatingWidth: false});
// setting isMenuOpen to undefined and closing menu
this.selectRef.current.select.onMenuClose();
this.setState({MenuIsOpen: true});
}, 1);
}
}
styles = {
menu: (css) => ({
...css,
width: "auto",
...(this.isCalculatingWidth && { height: 0, visibility: "hidden" })
}),
control: (css) => ({ ...css, display: "inline-flex " }),
valueContainer: (css) => ({
...css,
...(this.menuWidth && { width: this.menuWidth })
})
};
setData (props) {
this.setState({
info: props.info,
options: props.options,
hidden: props.hidden
})
}
render() {
return (
<div style={{ display: "flex" }}>
<div style={{ margin: "8px" }}>{this.info}</div>
<div>
<Select
ref={this.selectRef}
onMenuOpen={this.onMenuOpen}
options={this.options}
menuIsOpen={this.menuIsOpen}
styles={this.styles}
isDisabled={this.hidden}
formatOptionLabel={(options) => (
<div className="select-option" style={{ display: "flex" }}>
<div style={{ display: "inline", verticalAlign: "center" }}>
<img src={options.avatar} width="30px" alt="Avatar" />
</div>
<div style={{ display: "inline", marginLeft: "10px" }}>
<span>{options.label}</span>
</div>
</div>
)}
/>
</div>
</div>
);
}
}
export default RtSelect;
Got it working!
I had removed "undefined" from onOpen setState function. I compared those 2 fiddles and finally got it working.
// setting isMenuOpen to undefined and closing menu
this.selectRef.current.select.onMenuClose();
this.setState({MenuIsOpen: undefined});

React, onMouseOver not working on child component but it does in button

Given the following code: -codesandbox-
import "./styles.css";
import React from "react";
const B = () => {
return (
<div>
<C>C inside B</C>
</div>
);
};
const C = ({ children, estado = "lightgrey" }) => {
const handleBoxToggle = (e) =>
(e.target.style.backgroundColor = "blue !important");
return (
<div
style={{
backgroundColor: estado,
display: "flex",
alignItems: "center",
justifyContent: "center",
minWidth: "1rem",
minHeight: "1rem",
outline: "1px solid black"
}}
onMouseOver={(e) => handleBoxToggle(e)}
>
{children}
</div>
);
};
export default function App() {
return (
<div className="App">
<button onMouseOver={() => alert("hi")}>Alert on hover Button</button>
<B></B>
<C>Alert DIV estado</C>
</div>
);
}
Why does the button display the alert on mouse over but the component 'C' does not? Shouldn't it have the function integrated? How can I make the mouseover on 'C' Component work? (it will be created dinamically many times).
You didn't call your function handleBoxToggle inside onMouseOver event callback
Pass it like this onMouseOver={handleBoxToggle}
You need to call your funtion like
onMouseOver={handleBoxToggle}
OR if you want to pass any arguments to this funtion you can call like
onMouseOver={() => handleBoxToggle()}

how to show 10 data when I am scrolling down Its just show next 10 data in react js?

When I am using scroll down so that time our function "fetchMoreData"?
I want to scroll down that time my function is called
const fetchMoreData = () =>{
console.log("pulkit")
}
<InfiniteScroll
dataLength={25}
next={fetchMoreData}
hasMore={true}
loader={<h4>Loading...</h4>}>
<Grid container spacing={4} sx={{ marginTop: '1rem' }}>
{simulationList.map((item, i) => (
<Grid
item
xs={3}
key={item?._id}
>
</Grid>
))}
</Grid>
</InfiniteScroll>
Try using the different scroll libraries of functions to scroll in react.
for my code, i have been using this "react-scroll-wheel-handler" which allows me to display only the required number of items on the screen and even allows me to change the setting in the UI where i can change the number of results in the output table.
https://www.npmjs.com/package/react-scroll-wheel-handler
I've tried to recreate your scenario.It's from react-infinite-scroll-component
example.. Try this out
Sandbox code
you don't need any lib you can do using it to react onScroll event pls find the working demo here
import React from "react";
import { render } from "react-dom";
const style = {
height: 30,
border: "1px solid green",
margin: 6,
padding: 8
};
class App extends React.Component {
state = {
items: Array.from({ length: 20 })
};
fetchMoreData = () => {
console.log("pulkit");
setTimeout(() => {
this.setState({
items: this.state.items.concat(Array.from({ length: 20 }))
});
}, 1500);
};
onHandleScroll = (e) => {
e.persist();
if (e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight) {
this.fetchMoreData()
}
}
render() {
return (
<div onScroll={this.onHandleScroll} style={{
overflow: "scroll", height: '800px'
}}>
<h1>demo: react-infinite-scroll-component</h1>
<hr />
{this.state.items.map((i, index) => (
<div style={style} key={index}>
div - #{index}
</div>
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));

How to set focus on a custom filter dialog in ag-grid react?

We're developing a grid that will be used with screen readers. So far ag-grid is pretty accessible, but one issue is setting the focus on a custom filter when it's opened. (Note, the built in filters do set the focus correctly.)
Previous versions of the grid had a function "afterGuiAttached()" that could be used to set the focus after render. But we're using ag-grid-community 25.1.0 and ag-grid-react 25.1.0 and that function no longer exists.
Here is a plunker example and I've pasted a sample custom filter below.
Plunker Example
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useState,
useRef,
} from 'react';
export default forwardRef((props, ref) => {
const [filterText, setFilterText] = useState(null);
// expose AG Grid Filter Lifecycle callbacks
useImperativeHandle(ref, () => {
return {
doesFilterPass(params) {
// make sure each word passes separately, ie search for firstname, lastname
let passed = true;
filterText
.toLowerCase()
.split(' ')
.forEach((filterWord) => {
const value = props.valueGetter(params);
if (value.toString().toLowerCase().indexOf(filterWord) < 0) {
passed = false;
}
});
return passed;
},
isFilterActive() {
return filterText != null && filterText !== '';
},
getModel() {
return { value: filterText };
},
setModel(model) {
setFilterText(model.value);
},
};
});
const onChange = (event) => {
setFilterText(event.target.value);
};
useEffect(() => {
props.filterChangedCallback();
}, [filterText]);
return (
<div style={{ padding: 4, width: 200 }}>
<div style={{ fontWeight: 'bold' }}>Custom Athlete Filter</div>
<div>
<input
style={{ margin: '4 0 4 0' }}
type="text"
value={filterText}
onChange={onChange}
placeholder="Full name search..."
/>
</div>
<div style={{ marginTop: 20 }}>
This filter does partial word search on multiple words, eg "mich phel"
still brings back Michael Phelps.
</div>
<div style={{ marginTop: 20 }}>
Just to emphasise that anything can go in here, here is an image!!
</div>
<div>
<img
src="https://www.ag-grid.com/images/ag-Grid2-200.png"
style={{
width: 150,
textAlign: 'center',
padding: 10,
margin: 10,
border: '1px solid lightgrey',
}}
/>
</div>
</div>
);
});
I guess I'm late to the question, but I was facing the same issue, and I found a workaround. I am using ag-grid community v26.2.0. And the way I solved it is below.
Basically, you give your input an ID and on the onFilterOpened event, you do a direct focus on the DOM element itself. Of course you could add a small delay using setTimeout() if you have some animation set up while the popup is entering in the DOM.
<AgGridReact
{...otherGridOptions}
onFilterOpened={() => document.querySelector("#idOfYourInput")?.focus()}>
//columns or other children
</AgGridReact>

React Hooks useRef initialization issue, useRef only works on subsequent calls

I am implementing useRef into my project. I have a form that has clickable sections. Once clicked it opens the form. I'm using Reactstrap Collapse to show/hide the form. I need to be able to open the form and show the section that needs to be filled out, however the scrollIntoView once I click the section doesn't work until I open and close the form again. I'm stumped. I console.log(formRef), the ref returns as expected of the component that I want to be scrolled to the top of viewport on subsequent calls. My guess would be that the formRef is being initialized as null to begin with so initial calls to the ref do not work. However, once it knows the ref the subsequent calls work. I'm not sure how to go about this..
If I need to provide an example that is stripped please let me know. I am expecting this to be just an initialization issue.
Form
import React, { useRef, useContext, useEffect } from "react";
import {
FormQuestionsContext,
FormAnswersContext,
ExpandedSectionContext,
} from "../../Store";
import SectionHeader from "../SectionHeader";
import ImageUploader from "../CommentsSection";
import Ratings from "../Ratings";
import { Collapse, Button, CardBody, Card } from "reactstrap";
import FontAwesome from "react-fontawesome";
import styles from "./bedthreeform.module.css";
function BedThreeForm({ Name }) {
const formRef = useRef(null); //useRef Initialization
const [expandedSection, setExpandedSection] = useContext(
ExpandedSectionContext
);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const array = formQuestions.bedthree;
const onChange = (e, name) => {
const { value } = e.target;
setFormAnswers((state) => ({
...state,
[Name]: { ...state[Name], [name]: value },
}));
};
//! The function I use when I want to tell useRef to scrollIntoView
const handleOpen = () => {
expandedSection === Name
? setExpandedSection("")
: setExpandedSection(Name);
formRef.current.scrollIntoView();
};
const answeredQuestions = formAnswers.bedthree
? Object.keys(formAnswers.bedthree)
: null;
console.log(formRef);
return (
<div>
<Button
className={styles["CollapseBtn"]}
onClick={handleOpen} //Calling the function here
style={
answeredQuestions &&
answeredQuestions.length === formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#02BD43",
},
backgroundColor: "#02BD43",
marginBottom: "1rem",
width: "100%",
}
: answeredQuestions &&
answeredQuestions.length !== formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#bd0202",
},
backgroundColor: "#bd0202",
marginBottom: "1rem",
width: "100%",
}
: {
":focus": {
backgroundColor: "#fafafa",
},
marginBottom: "1rem",
width: "100%",
}
}
>
<p>BEDROOM #3 INSPECTION</p>
<FontAwesome
className="super-crazy-colors"
name="angle-up"
rotate={expandedSection === Name ? null : 180}
size="lg"
style={{
marginTop: "5px",
textShadow: "0 1px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</Button>
<Collapse
className={styles["Collapse"]}
isOpen={expandedSection === Name}
>
<Card>
<CardBody>
{array ? (
<div>
<SectionHeader title="Bedroom #3 Inspection" name={Name} />
<div
ref={formRef}
className={styles["BedroomThreeFormWrapper"]}
id="bedroom-three-form"
>
{array.map((question, index) => {
const selected =
formAnswers[Name] && formAnswers[Name][question]
? formAnswers[Name][question]
: "";
return (
<div className={styles["CheckboxWrapper"]} key={index}>
<h5>{question}</h5>
<Ratings
section={Name}
question={question}
onChange={onChange}
selected={selected}
/>
</div>
);
})}
</div>
{!answeredQuestions ? (
""
) : (
<Button
onClick={(e) => e.preventDefault()}
style={
!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? {
backgroundColor: "#bd0202",
color: "white",
pointerEvents: "none",
}
: {
backgroundColor: "#02BD43",
color: "white",
pointerEvents: "none",
}
}
>
{!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? "Incomplete"
: "Complete"}
</Button>
)}
<br />
<ImageUploader name="bedthree" title={"Bedroom #3"} />
</div>
) : (
<div></div>
)}
</CardBody>
</Card>
</Collapse>
</div>
);
}
export default BedThreeForm;
CodeSandbox Stripped Form Doesn't work as expected, however that is the stripped code.
Update I'm open to suggestions to bypass this, or an alternative way to do this. I'm not sure why it only does it on subsequent calls.
Look at these lines:
<CardBody>
{array ? (
...
<div
ref={formRef}
...
This (virtual) dom will be evaluated only if array is defined. In case you would like to have your formRef always to point to the dom, then You'll have to strip it out from your condition.
I've figured out the issue, the issue is calling it when the content in the collapse hasn't been loaded yet, Reactstrap has an attribute onEntered which basically when set, will run the function as soon as the collapse has fully opened. The example that I found is here. Also, by setting the attribute innerRef on a Reactstrap component I can manipulate it just like I could a regular component using ref.

Resources