React onTouch event not functioning properly - reactjs

I am trying to make a react carousel component with swipe options when accessed on touch screen device. So far I have been able to find only few tutorials about this type of React code. I tried some, but for some reason my images are moving only for small amount of space, no matter how big swipe is made on my touch screen.
Here you can see my lines of code:
import React, {useState, useEffect} from 'react'
import IMGComp from './imgComp'
import './slider.scss'
import i1 from './images/image1.jpg'
import i2 from './images/image2.jpg'
import i3 from './images/image3.jpg'
import i4 from './images/image4.jpg'
import i5 from './images/image5.jpg'
export default function Slider () {
let sliderArr = [<IMGComp src={i1}/>,<IMGComp src={i2}/>,<IMGComp src={i3}/>,<IMGComp src={i4}/>,<IMGComp src={i5}/>]
const [x, setX] = useState(0)
let touchPoint
/* useEffect(() => {
(setX(x+change) + console.log(`this is new ${x}`));
}, [touchmove]);*/
const goLeft = () => {
x===0? setX(-100*(sliderArr.length -1)) : setX(x+100)
}
const goRight = () => {
(x=== -100*(sliderArr.length-1)) ? setX(0) : setX(x-100)
}
const touchstart = (event) =>{
touchPoint = event.touches[0].clientX
}
const touchmove = (event) =>{
let touch = event.touches[0]
let change = Math.floor((touch.clientX - touchPoint)/10)
if (change < 0 ) {
(setX((change)*10) + console.log(`main point is ${change} moving to right`))
} else {
(setX((change)*10) + console.log(`main point is ${change} moving to left`))
}
//event.preventDefault()
}
const touchend = (event) =>{
let change = event.changedTouches[0].clientX - touchPoint
let screenw = ((screen.width / 4)/10)
console.log(screenw)
if (change> screenw) {
console.log(`next screen ${screenw}`)
(x=== -100*(sliderArr.length-1)) ? setX(0) : setX(x-100)
} else {setX(x-(change*10))}
}
return (
<div className="slider">
{
sliderArr.map((item, index)=>{
return(
<div key={index} className="slide" style={{transform: `translateX(${x}%)`}}
onTouchStart={touchstart}
onTouchMove={touchmove}
onTouchEnd={touchend}
>
{item}
</div>
)
})
}
<button id="goLeft" onClick={goLeft}>left</button>
<button id="goRight" onClick={goRight}>right</button>
</div>
)
}
Or you can access the whole repository on github: https://github.com/jilvins/carousel-with-swipes
Can someone tell me how to correct this Carousel item in order to make it usable for touch screens? Good info/tutorial sources about touch events and React based mobile web development would be greatly appreciated too.

Related

Incorporate and display JSON file of global lat, lon, and temp data with D3.js + React Globe?

My current job needs me to develop a 3d globe application using D3.js, and React.js, that incorporates a huge JSON file of global latitudes, longitudes, and temperature data. I have built a couple versions of the globe, and can get them functioning (they spin, and have JSON data of countries and landmass loading in perfectly), but incorporating the JSON file of the other data is proving much more difficult than I expected. Ideally, if you click anywhere on the globe, the relevant info (lat, lon, max temp, min temp, and median temp) will print into the browser, but I just can't figure this out. Let me just say I'm a very junior dev, and this is probably a simple solution that I'm just not seeing :(
I used one JSON file to load countries data and landmass data, and mapped through it. This worked.
I'm using a second JSON file that has lat, lon, and temp data. Essentially, if I map through this file, I can print it to the console, where half the time everything freezes because the JSON file is so massive. But affixing it to the actual globe is proving to be really difficult. I'm not an expert in D3.js, but my job needed me to learn it quick, I feel like I've made a lot of progress but this one area is throwing me.
Access the countries + landmass correctly-
`
import React, {useState, useEffect} from "react";
import { json } from 'd3';
import { feature, mesh } from 'topojson-client';
const jsonUrl =
"https://unpkg.com/world-atlas#2.0.2/countries-50m.json";
export const useData = () => {
const [data, setData] = useState(null);
useEffect(() => {
json(jsonUrl).then(topojsonData => {
const {countries, land} = topojsonData.objects
setData (
{
land: feature(topojsonData, land),
interiors: mesh(topojsonData, countries, (a, b) => a !== b),
}
)
});
}, [])
return data;
};
`
Able to access the lat, lon, and temp-
`
var data3 = require("./data/out.json")
export const ClimateData = () => {
let climateData = data3;
return climateData;
};
`
Main build file (I've commented out what I've tried to use so far, these have printed the data to the console though not to the globe itself)-
`
import { geoOrthographic, geoPath } from 'd3';
import React, { useState, useCallback } from 'react';
import $ from 'jquery';
import { ClimateData } from './ClimateData';
import styled from 'styled-components';
const width = 960;
const height = 500;
const initialMousePosition = {x: width/2, y:height/2};
const projection = geoOrthographic();
const path = geoPath(projection);
const climateData = ClimateData();
export const Marks = ({data: {land, interiors}}) => {
const [MousePosition, SetMousePosition] = useState(initialMousePosition);
const [mouseDown, SetMouseDown] = useState(false);
const handleMouseDown = useCallback((event) => {
SetMouseDown(true);
}, [])
const handleMouseUp = useCallback((event) => {
$('.marks').css('cursor', '')
SetMouseDown(false);
}, [])
const handleMouseMove = useCallback((event) => {
const {clientX, clientY} = event;
if(mouseDown) {
SetMousePosition({x:clientX, y:clientY});
$('.marks').css('cursor', 'pointer')
}
}, [SetMousePosition, mouseDown]);
return(
<svg width="1200" height="1200">
<g className='marks'
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
{projection.rotate([MousePosition.x + 30 / 60, -MousePosition.y, 0])}
{/* access map and globe data */}
<path className='sphere' d={path({type: 'Sphere' })}/>
{land.features.map(feature => (
<path className='feature' d={path(feature)}/>
))}
<path className='interiors' d={path(interiors)}/>
{/* access climate data below */}
{/* {climateData.map(climate => (
<path className='climate' key={climate.lat} d={path(climate.lat)}/>
, console.log(climate.lat, 'LATITUDES TEST')
))}
</g>
</svg>
);
}
export default Marks;
`
A mini version of the JSON data I'm trying to incorporate-
[{
"lon":0.0,
"lat":90.0,
"tmax":262.0498352051,
"tmin":256.5426025391,
"tmed":259.2962036133
},
{
"lon":0.0,
"lat":89.5,
"tmax":261.589050293,
"tmin":256.0026245117,
"tmed":258.7958374023
}
]

How do I access the 'currentImageIdIndex' when using the stack scroll tool in Cornerstone.js in a React functional component?

I'm currently successfully displaying a stack of images in a React component but am unsure where to place an event listener in order to access the currentImageIdIndex when scrolling.
import React, { useEffect, useRef, useCallback } from "react";
import cornerstone from "cornerstone-core";
import cornerstoneMath from "cornerstone-math";
import cornerstoneTools from "cornerstone-tools";
import cornerstoneFileImageLoader from "cornerstone-file-image-loader";
import Hammer from "hammerjs";
function StackImageViewport(props) {
const viewerRef = useRef(null);
const base64StringToArrayBuffer = useCallback((base64) => {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}, []);
const initializeCornerstone = () => {
// Initialise cornerstone and link to DOM element
cornerstoneTools.external.cornerstone = cornerstone;
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
cornerstoneFileImageLoader.external.cornerstone = cornerstone;
cornerstoneTools.external.Hammer = Hammer;
cornerstoneTools.init();
cornerstone.enable(viewerRef.current);
};
const setCornerstoneTools = () => {
// define Cornerstone Tools
const StackScrollTool = cornerstoneTools.StackScrollTool;
const StackScrollMouseWheelTool =
cornerstoneTools.StackScrollMouseWheelTool;
const WindowingTool = cornerstoneTools.WwwcTool;
// Add tools
cornerstoneTools.addTool(StackScrollTool);
cornerstoneTools.addTool(StackScrollMouseWheelTool);
cornerstoneTools.addTool(WindowingTool);
// set tools to Active state
cornerstoneTools.setToolActive("StackScroll", { mouseButtonMask: 1 });
cornerstoneTools.setToolActive("StackScrollMouseWheel", {});
cornerstoneTools.setToolActive("Wwwc", { mouseButtonMask: 2 });
};
const displayStack = (stackMediaArray) => {
let mediaArray = [];
// 'stackMediaArray' is an array of images, each containing a buffer of the image
Promise.all(
stackMediaArray.map((mediaObject) => {
return new Promise((resolve, reject) => {
let imageBuffer = base64StringToArrayBuffer(mediaObject.buffer);
const imageId =
cornerstoneFileImageLoader.fileManager.addBuffer(imageBuffer);
mediaArray.push(imageId);
resolve(mediaObject);
}).catch(console.error);
})
);
//define the stack
const stack = {
currentImageIdIndex: 0,
imageIds: mediaArray,
};
// load images and set the stack
cornerstone.loadAndCacheImage(mediaArray[0]).then((image) => {
cornerstone.displayImage(viewerRef.current, image);
cornerstoneTools.addStackStateManager(viewerRef.current, ["stack"]);
cornerstoneTools.addToolState(viewerRef.current, "stack", stack);
});
setCornerstoneTools();
};
useEffect(() => {
if (!viewerRef.current) {
return;
}
initializeCornerstone();
displayStack(props.stackMediaArray);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewerRef]);
return (
<>
<div ref={viewerRef} id="viewer" className="flex h-1/2-screen"></div>
</>
);
}
export default StackImageViewport;
This attempts to answer the question:
https://github.com/cornerstonejs/cornerstoneTools/issues/1121
however, I don't want to access DOM elements to add the event listener to.
It's also clear that events are accessible in Cornertsone.js:
https://github.com/cornerstonejs/cornerstoneTools/blob/master/src/events.js
... but I'm still not sure where to place the event listener ?
Any help would be much appreciated.
Thanks.
Listening to events in ReactJS might be confusing for someone new, even more, when using a new tool such as CornerstoneJS. However, you can create an event listener by using the window.addEventListener method, just like you would in a Vanilla JavaScript. Note that this might change from browser to mobile environments. Moreover, your cornerstoneFileImageLoader can also be a challenge.
For this purpose, you can follow the structure:
window.addEventListener('keydown', (event) => {
...
});
But now, we need to understand "where" to place it. Imagine that all of your pages are just components, as ReactJS is a component-based system. Meaning that the event listener need to happen inside the component.
For instance, you can do like that:
import React from 'react';
const App = (props) => {
window.addEventListener('keydown', (event) => {
...
});
return (
<div className='container'>
<h1>Welcome to the Keydown Listening Component</h1>
</div>
);
};

Manually setting Desktop vesion in React

I have a custom Hook useWindowSize that determines whether there is a Mobile or Desktop environment. This is fine for most cases except when a mobile user wants to access the desktop version on his/her phone. I have a button to manually override the current windowsize but am not sure how to approach this.
Here I determine the opposite of the loaded windowsize but how can I switch and reload to the appropriate mode on click?
I will need this mode to stay afterwards even if the window is resized to keep the components linked to either mobile or desktop.
import "./styles.css";
import "./app.scss";
import useWindowSize from "./useWindowSize";
export default function App() {
const windowSize = useWindowSize();
const otherMode = windowSize <= "useMobileVersion" ? "useDesktopVersion" : "useDesktopVersion";
return (
<div className="App">
<p>Right now you are in {windowSize} mode. <button onClick={() => setPageMode("otherMode")}>
Switch to {otherMode} mode
</button>
</p>
</div>
);
}
The codesandbox is here.
The custom Hook is her:
import { useState, useEffect } from "react";
//a Util function that will convert the absolute width into breakpoints
function getBreakPoint(windowWidth) {
if (windowWidth) {
if (windowWidth < 420) {
return "useMobileVersion";
} else {
return "useDesktopVersion";
}
} else {
return "useDesktopVersion";
}
}
function useWindowSize() {
const isWindowClient = typeof window === "object";
const [windowSize, setWindowSize] = useState(
isWindowClient ? getBreakPoint(window.innerWidth) : undefined
);
useEffect(() => {
//a handler which will be called on change of the screen resize
function setSize() {
setWindowSize(getBreakPoint(window.innerWidth));
}
if (isWindowClient) {
//register the window resize listener
window.addEventListener("resize", setSize);
//unregister the listener
return () => window.removeEventListener("resize", setSize);
}
}, [isWindowClient, setWindowSize]);
return windowSize;
}
export default useWindowSize;

react show job progress

I'm making requests inside a loop and would like to show its progress on a div:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
function Job() {
const [part, setPart] = useState(0);
const [message, setMessage] = useState(null);
async function executeJob() {
const N_PARTS = 10;
for (let i = 0; i < N_PARTS; i++) {
setPart(i);
setMessage(`Current part: ${part}`);
await axios.post(`/parts/${i}/doit`);
}
}
return (
<>
<div>{message}</div>
<button onClick={executeJob}>Execute</button>
</>
);
}
When I run this, the requests were made correctly, but it doesn't display message correctly. I suspect that I can't set states sequentially this way and have to put it inside an effect. I'm new to using React hooks, please let me know the correct way.
I've made a simple example.
export default function App() {
const [ part, setPart] = React.useState(0);
async function execute(){
for await(let i of new Array(10).fill(0)){
setPart((prev)=>prev+1);
console.log(i);
await fetch(`https://randomuser.me/api`).then((data)=>{
console.log(data);
})
}
}
return (
<div className="App">
<h1>Hello CodeSandbox {part}</h1>
<button onClick={execute}>Excute</button>
</div>
);
}
Here is the same example in codesandbox
https://codesandbox.io/s/adoring-cannon-srppr
you can use for-loop with async-await.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of

useState not setting after initial setting

I have a functional component that is using useState and uses the #react-google-maps/api component. I have a map that uses an onLoad function to initalize a custom control on the map (with a click event). I then set state within this click event. It works the first time, but every time after that doesn't toggle the value.
Function component:
import React, { useCallback } from 'react';
import { GoogleMap, LoadScript } from '#react-google-maps/api';
export default function MyMap(props) {
const [radiusDrawMode, setRadiusDrawMode] = React.useState(false);
const toggleRadiusDrawMode = useCallback((map) => {
map.setOptions({ draggableCursor: (!radiusDrawMode) ? 'crosshair' : 'grab' });
setRadiusDrawMode(!radiusDrawMode);
}, [setRadiusDrawMode, radiusDrawMode]); // Tried different dependencies.. nothing worked
const mapInit = useCallback((map) => {
var radiusDiv = document.createElement('div');
radiusDiv.index = 1;
var radiusButton = document.createElement('div');
radiusDiv.appendChild(radiusButton);
var radiusText = document.createElement('div');
radiusText.innerHTML = 'Radius';
radiusButton.appendChild(radiusText);
radiusButton.addEventListener('click', () => toggleRadiusDrawMode(map));
map.controls[window.google.maps.ControlPosition.RIGHT_TOP].push(radiusDiv);
}, [toggleRadiusDrawMode, radiusDrawMode, setRadiusDrawMode]); // Tried different dependencies.. nothing worked
return (
<LoadScript id="script-loader" googleMapsApiKey="GOOGLE_API_KEY">
<div className="google-map">
<GoogleMap id='google-map'
onLoad={(map) => mapInit(map)}>
</GoogleMap>
</div>
</LoadScript>
);
}
The first time the user presses the button on the map, it setss the radiusDrawMode to true and sets the correct cursor for the map (crosshair). Every click of the button after does not update radiusDrawMode and it stays in the true state.
I appreciate any help.
My guess is that it's a cache issue with useCallback. Try removing the useCallbacks to test without that optimization. If it works, you'll know for sure, and then you can double check what should be memoized and what maybe should not be.
I'd start by removing the one from toggleRadiusDrawMode:
const toggleRadiusDrawMode = map => {
map.setOptions({ draggableCursor: (!radiusDrawMode) ? 'crosshair' : 'grab' });
setRadiusDrawMode(!radiusDrawMode);
};
Also, can you access the state of the map options (the ones that you're setting with map.setOptions)? If so, it might be worth using the actual state of the map's option rather than creating your own internal state to track the same thing. Something like (I'm not positive that it would be map.options):
const toggleRadiusDrawMode = map => {
const { draggableCursor } = map.options;
map.setOptions({
draggableCursor: draggableCursor === 'grab' ? 'crosshair' : 'grab'
});
};
Also, I doubt this is the issue, but it looks like you're missing a closing bracket on the <GoogleMap> element? (Also, you might not need to create the intermediary function between onLoad and mapInit, and can probably pass mapInit directly to the onLoad.)
<GoogleMap id='google-map'
onLoad={mapInit}>
This is the solution I ended up using to solve this problem.
I basically had to switch out using a useState(false) for setRef(false). Then set up a useEffect to listen to changes on the ref, and in the actual toggleRadiusDraw I set the reference value which fires the useEffect to set the actual ref value.
import React, { useCallback, useRef } from 'react';
import { GoogleMap, LoadScript } from '#react-google-maps/api';
export default function MyMap(props) {
const radiusDrawMode = useRef(false);
let currentRadiusDrawMode = radiusDrawMode.current;
useEffect(() => {
radiusDrawMode.current = !radiusDrawMode;
});
const toggleRadiusDrawMode = (map) => {
map.setOptions({ draggableCursor: (!currentRadiusDrawMode) ? 'crosshair' : 'grab' });
currentRadiusDrawMode = !currentRadiusDrawMode;
};
const mapInit = (map) => {
var radiusDiv = document.createElement('div');
radiusDiv.index = 1;
var radiusButton = document.createElement('div');
radiusDiv.appendChild(radiusButton);
var radiusText = document.createElement('div');
radiusText.innerHTML = 'Radius';
radiusButton.appendChild(radiusText);
radiusButton.addEventListener('click', () => toggleRadiusDrawMode(map));
map.controls[window.google.maps.ControlPosition.RIGHT_TOP].push(radiusDiv);
});
return (
<LoadScript id="script-loader" googleMapsApiKey="GOOGLE_API_KEY">
<div className="google-map">
<GoogleMap id='google-map'
onLoad={(map) => mapInit(map)}>
</GoogleMap>
</div>
</LoadScript>
);
}
Not sure if this is the best way to handle this, but hope it helps someone else in the future.

Resources