DeckGL & Mapbox: How access ref element on map movement? - reactjs

I'm trying to console.log the bounds of a Mapbox map in my React app every time I move around the map.
Here's my current attempt:
return (
<>
<DeckGL
layers={layers}
initialViewState={INITIAL_VIEW_STATE}
controller={true}
onViewStateChange={stateChangeFunc}
>
<ReactMapGL
reuseMaps
mapStyle={mapStyle}
preventStyleDiffing={true}
ref={(ref) => console.log(ref.getMap().getBounds())}
mapboxApiAccessToken={token}
/>
</DeckGL>
</>
);
The bounds are printed when the map loads, but I'm having trouble printing when moving around the map. Is there a prop that I should use to access this functionality? Putting ref={(ref) => console.log(ref.getMap().getBounds())} in DeckGL doesn't work. Is there a prop equivalent to onViewStateChange for ReactMapGL? That might allow me to create a function that prints out the ref.

You can try:
Listen onViewStateChange directly from deck (recommended using some debounce)
Access viewState
Use getBounds method from WebMercatorViewport class
import DeckGL, {WebMercatorViewport} from 'deck.gl';
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, ms);
};
}
function handleViewStateChange({ viewState }) {
const bounds = new WebMercatorViewport(viewState).getBounds();
console.log(bounds);
};
<DeckGL
...
onViewStateChange={debounce(handleViewStateChange, 500)}
/>

Related

React Native maps re-render

I have a progress bar(react-native-elements) that is animated using useEffect(). This component has MapView(react-native-maps) and it's re-rendered on every tick thus I can't move the map.
How can I still have that progress bar without re-rendering the map?
Home
const Home = ({ navigation }) => {
...some code above...
useEffect(() => {
//setProgressValue(0);
var timer;
if (progressValue != 0) {
timer = setInterval(() => {
if (progressValue <= 1) {
setProgressValue(progressValue + 0.040);
}
else {
setProgressValue(0);
setTimeOver(true);
}
}, 1000);
return () => {
clearInterval(timer);
}
}
}, [progressValue]);
... code below ...
return (
...
<MainMap notification={notification} />
...
)
MainMap component
<View style={StyleSheet.absoluteFillObject}>
<MapView
ref={mapRef}
provider={PROVIDER_GOOGLE}
region={geo.location}
style={[StyleSheet.absoluteFillObject]}
loadingEnabled={true}
minZoomLevel={16}
maxZoomLevel={20}
>
{/* Current users location */}
{geo.location &&
<Marker identifier={'me'} pinColor={"red"} key={(Date.now()-1).toString()} title={"Это я"} coordinate={geo.location} />
}
{/* Location where help is needed */}
{notification &&
<Marker identifier={'target'} pinColor={COLORS.primary} key={Date.now.toString()} title={"Помогите!"} coordinate={notification.request.content.data.loc} />
}
</MapView>
</View>
I used the useMemo hook for the map and passed params that will trigger its rerender making sure that other state changes are not triggering it.

React PDFDownloadLink - call onclick event through code explicitly

PDFDownloadLink from react-pdf library downloads a pdf when someone clicks on it.
I want to trigger this click event via code based on some condition .
How do I explicitly invoke the click of PDFDownloadLink through code?
A bit late, but you can pass a ref to the render function's return value and use it to call click() on imperatively. For that to work you need to use a separate component wrapper:
const DownloadLink = memo(function () {
const linkRef = useRef(null)
const onLoadingFinished = useCallback(function () {
// When this function is called the first time it is safe to initiate the download
const elem = linkRef?.current
if (elem !== null) {
elem.click()
}
}, [])
return (
<PDFDownloadLink document={<MyDoc />} fileName={'my-file.pdf'}>
{({ blob, url, loading, error }) => (
// You shouldn't call setState() here, so we need to use a separate component to keep track of whether the document has finished rendering
<WorkaroundContainer ref={linkRef} loading={loading} onLoadingFinished={onLoadingFinished} />
)}
</PDFDownloadLink>
)
})
const WorkaroundContainer = forwardRef(function ({ loading, onLoadingFinished }, ref) {
useEffect(() => {
if (!loading) {
onLoadingFinished()
}
}, [loading])
// If you only want to initiate the download imperatively, hide the element via CSS (e.g. `visibility: hidden`)
return (
<div ref={ref}>
{loading ? 'Loading...' : 'Download PDF'}
</div>
)
})

ReactJS sending ref to global useContext state (Konva)

I am using useContext as a global state solution. I have a Store.jsx which contains my state, and a reducer.jsx which reduces. I am using Konva to create some shapes on an HTML5 Canvas. My goal is when I click on a shape I want to update my global state with a reference to what is active, and when I click again, to clear the reference.
My Full Code can be found here:
https://codesandbox.io/s/staging-platform-2li83?file=/src/App.jsx
Problem:
The problem is when I update the global state via the onClick event of a shape, its says that the reference is 'null', but when I console.log the reference in the onClick I can see the correct reference.
I think I am missing an important point to how useRef works.
This is how the flow appears in my head when I think about this:
I create a canvas, and I map an array of rectangle properties. This creates 4 rectangles. I use a wrapper component that returns a rectangle.
{rectArray.map((rectangle, index) => {
return (
<RectWrapper key={index} rectangle={rectangle} index={index} />
);
})}
Inside the RectWrapper, I create a reference, pass it to the ref prop of the Rect. In the onclick function, when I console log 'shapeRef' I see the refence ONLY when dispatch is commented out. If I uncomment dispatch then it shows as null, and if I console log the state, the reference is always null.
const RectWrapper = ({ rectangle, index }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef); // This correctly identifies the rect only when dispatch is uncommented
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
perhaps I am going about this to wrong way with hooks. I am just trying to keep a global state of whats been clicked on because components in another file would rely on this state.
The problem is happening because you are creating RectWrapper component as a functional component within your App component causing a new reference of the component to be created again and again and thus the reference is lost
Move your RectWrapper into a separate component declared outside of App component and pass on dispatch as a prop to it
import React, { useEffect, useContext, useState, Component } from "react";
import { Stage, Layer, Rect, Transformer } from "react-konva";
import { Context } from "./Store.jsx";
import "./styles.css";
const RectWrapper = ({ rectangle, index, dispatch }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef);
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
export default function App() {
const [state, dispatch] = useContext(Context);
console.log("Global State:");
console.log(state);
const rectArray = [
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 }
];
return (
<div className="App">
<Stage height={500} width={500}>
<Layer>
{rectArray.map((rectangle, index) => {
return (
<RectWrapper
dispatch={dispatch}
key={index}
rectangle={rectangle}
index={index}
/>
);
})}
</Layer>
</Stage>
</div>
);
}
Working demo
I don't think you need to create a ref in RectWrapper, because onClick has one event parameter. And the ref of the element that was clicked can be found in the event:
onClick={(e) => {
const thisRef = e.target;
console.log(thisRef );
...
Here is a working version without useRef: https://codesandbox.io/s/peaceful-brook-je8qo

Live stream HTML5 video draw-to-canvas not working

I use ReactJS to display a live stream (from my webcam) using the HTML5 video element. The OpenVidu media server handles the backend.
I would like to use the canvas element to draw the video live stream onto the canvas using the drawImage() method.
I have seen other examples, but in them the video element always has a source. Mine does not have a source - when I inspect the video element all I see is:
<video autoplay id="remote-video-zfrstyztfhbojsoc_CAMERA_ZCBRG"/>
This is what I have tried, however the canvas does not work.
export default function Video({ streamManager }) {
const videoRef = createRef()
const canvasRef = createRef()
useEffect(() => {
if (streamManager && !!videoRef) {
//OpenVidu media server attaches the live stream to the video element
streamManager.addVideoElement(videoRef.current)
if (canvasRef.current) {
let ctx = canvasRef.current.getContext('2d')
ctx.drawImage(videoRef.current, 0, 0)
}
}
})
return (
<>
//video element displays the live stream
<video autoPlay={true} ref={videoRef} />
// canvas element NOT working, nothing can be seen on screen
<canvas autoplay={true} ref={canvasRef} width="250" height="250" />
</>
)
}
UPDATE: after further investigation I realised I needed to use the setInterval() function and therefore provided the solution below.
The solution is to extract the canvas logic in a self contained component, and use a setInterval() so that it will draw the video element on the canvas, every 100 ms (or as needed).
Video Component
import React, { useEffect, createRef } from 'react'
import Canvas from './Canvas'
export default function Video({ streamManager }) {
const videoRef = createRef()
useEffect(() => {
if (streamManager && !!videoRef) {
//OpenVidu media server attaches the live stream to the video element
streamManager.addVideoElement(videoRef.current)
}
})
return (
<>
//video element displays the live stream
<video autoPlay={true} ref={videoRef} />
//extract canvas logic in new component
<Canvas videoRef={videoRef} />
</>
)
}
Canvas Component
import React, { createRef, useEffect } from 'react'
export default function Canvas({ videoRef }) {
const canvasRef = createRef(null)
useEffect(() => {
if (canvasRef.current && videoRef.current) {
const interval = setInterval(() => {
const ctx = canvasRef.current.getContext('2d')
ctx.drawImage(videoRef.current, 0, 0, 250, 188)
}, 100)
return () => clearInterval(interval)
}
})
return (
<canvas ref={canvasRef} width="250" height="188" />
)
}
You are using useEffect without parameter which mean your useEffect will call in every render
useEffect(() => {
// every render
})
If you want to run your drawImage at only mount time then use useEffect with []
useEffect(() => {
// run at component mount
},[])
Or if you want to run drawImage when any parameter change then pass your parameter to it
useEffect(() => {
// run when your streamManager change
},[streamManager])
use it as per your requirement

React ES 6 Classes refs

I am very new in react native and I tried to refactor a source code from old react into react using ES 6 Class, but I got an error 'Cannot read property 'close' of undefined'. Can anyone help me why this.refs.drawer in closeDrawer is undefined?
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}
Regards
EDIT I did not notice that you were using arrow functions in your component's member functions, so you do not need to bind them. There were some other issues, though
This is a binding issue. This should work:
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true)}
onClose={() => this.setDrawerState(false)}
content={<DrawerScene closeDrawer={this.closeDrawer} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer}
openDrawer={this.openDrawer}
/>
</Drawer>
);
}
The problem with your code is that you are applying bind to the result of a function call. For instance, when you do this.setDrawerState(true).bind(this), the function is called, returns the appropriate value, and then bind is applied to it. This usually would result in an error, but here you are also trying to access a ref that has not yet been set up (because before that happens all prop values have to be evaluated before passed to the new component, which is exactly the problem here, the function is called before the component is instantiated).
Just so you know a bit more about bind: it is a property of a function object, so you need to access it from the reference to that function (in this case, its name). The result of bind is a new function with the same behaviour of the original one, save for the new this value or any other parameters you pass.
Try to set ref like this instead of a string:
drawer = null;
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref={((component)=> this.drawer=component)}
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}

Resources