Resium: how to prevent map from redraw upon any change of state? - reactjs

I've got a sample Resium project that shows the map. I've added a simple onClick that sets some state which is NOT being used anywhere - I just set it. Still, it causes the entire map to redraw & flicker UNLESS I remove the terrainProvider. Example (terrainProvider is commented out) if you move/click the mouse, the entire Cesium UI flickers and redraws everything. I'm using React 17, Resium 1.14.3 (Cesium 1.86.1). Any idea what's going on?
import React, { useState } from "react";
import { Cartesian3, Color } from "cesium";
import { Viewer, Entity } from "resium";
import * as Cesium from 'cesium';
export default function App() {
const [currentPos, setCurrentPos] = useState(null);
const handleMouseClick = (e, t) => {
setCurrentPos("xxx");
}
return (
<Viewer full
onClick={handleMouseClick}
onMouseMove={handleMouseClick}
// terrainProvider={new Cesium.CesiumTerrainProvider({ url: 'https://api.maptiler.com/tiles/terrain-quantized-mesh-v2/?key=xxxxxxx' })}
>
<Entity
name="Tokyo"
position={Cartesian3.fromDegrees(139.767052, 35.681167, 100)}
point={{ pixelSize: 10, color: Color.RED }}
/>
</Viewer>
);
}

My guess is this. No matters that the currentPos is not used in the component, is still part of the component's state, then the components re-renders.
In every render, the terrainProvider prop is receiving a new instance, so this causes that the entire Viewer re-renders too.
Maybe you can save your instance as state in the component and don't create a new one everytime that the component is re-render.

Related

how do i render component based on width screen?

// import { useState } from 'react'
import Res from './responsiveMenu/Res'
import NormalWidth from './navNormalwidth/NormalWidth'
const Navbar=()=>{
const [click,setClick]=useState(true)
// function to change from true to false
const navBtn=()=>{setClick(!click)}
const screenwidth=window.innerWidth
return(
<>
{screenwidth<'640' ? <Res btnF={navBtn} click={click}/>:screenwidth>'640'?<NormalWidth/>:''}
</>
)
}
export default Navbar
why when the screen is 640 is works but when i make it bigger i the menu btn stays until i press it then it will render the normal component
Please use the viewport unit with width.
eg:
width:100vw;
As mentioned in the comments, your component does NOT rerender when the window size is changing. You need to register a listener, which updates the a react state which causes your component to rerender.
State of the art would be to create this via a hook. As this is a very common functionality people already implemented examples of such a hook for you.
You can find a good one here. Not library needed.
https://usehooks.com/useWindowSize/
If you are not yet familiar with using hooks, the official React documentation is very good.
https://reactjs.org/docs/hooks-intro.html

useState is not changing it's value on useEffect hook [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 months ago.
I am trying to animate a header on a personal site I'm working on.
It's a simple fade in fade out effect as the user scrolls through the page.
To get this effect what i'm doing is using an Intersection Observer to check wether or not a certain element is in view or not.
When that element is in view, i'd like to change the state elementInView to reflect that or not using a boolean value.
when I console.log the isIntersecting value of the element that the observer is listening to, I can see that it changes True or False based on whether the element is in view or not.
so... since that is working, i'm using that value to set state of elementInView.
BUT!!! the state is not changing when the element is in view, even though it should. I'm not sure what went wrong but I cannot figure it out for the life of me.
here's the code:
import React from 'react';
import { useEffect, useRef, useState } from 'react';
import { Link, useRouteMatch, useNavigate } from 'react-router-dom';
import BoulderImage from "../data/nat_pic_boulders_and_tree.jpeg";
import FenceImage from "../data/nat_pic_fence.jpeg";
import GrassField from '../data/nat_pic_grass.jpeg';
export default function HomePage() {
const navigate = useNavigate();
const firstRef = useRef();
const secondRef = useRef();
const [elementInView, setElementInView] = useState();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
setElementInView(entry.isInteresecting);
console.log(elementInView);
})
observer.observe(firstRef.current);
observer.observe(secondRef.current);
}, [elementInView])
return (
<div>
<h1 className="main-intro-header">Welcome Home</h1>
<div className='nav-links'>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</div>
<div className="stop-point-one" ref={firstRef}>stop point</div>
<div className="stop-point-two" ref={secondRef}>stop point</div>
</div>
)}
the list at the end of the useEffect hook. i've tried putting elementInView in there and taking it out and re-running it. no luck.
I think that the thing you are missing is that after calling setElementInView it does not change elementInView on the current render. Only in the next render for that component elementInView will get the new state. And that way you are getting the wrong output to the console.
The way to solve it is to add another useEffect hook with elementInView as a dependency and do you logic there.
You can watch a great video of Jack Herrington regarding this:
https://www.youtube.com/watch?v=RAJD4KpX8LA
Henrys comment links to a better explanation than I can give. Summed up, since setState is performed in an async nature, the value will not be what you set it to immediately after the setState line is ran. If you move your console.log line out of your observer declaration, into the body of the useEffect, it should work as you expect. I put together a little sandbox and spaced some elements apart to put them off screen to show this as well.
https://codesandbox.io/s/observer-test-m5qh88?file=/src/App.js
If you scroll the elements on and off the page with the console open, you'll see it update each time they move off and back onto the screen.
I figured it out!
I saw a simpler way to set the state of these elements as they appear on the screen.
i updated this piece of code:
const [elementInView, setElementInView] = useState();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
setElementInView(entry.isInteresecting);
console.log(elementInView);
})
observer.observe(firstRef.current);
observer.observe(secondRef.current);
}, [elementInView])
to this:
import { useInView } from 'react-intersection-observer';
const { ref: firstRef, inView: firstBreakIsVisibile} = useInView();
const { ref: secondRef, inView: secondBreakIsVisibile} = useInView();
I used npm to install the react-intersection-observer dependency.

React - Bootstrap: Offcanvas with embedded mapped tabs content disappearing on Re-Render

I have a React application that includes a Bootstrap Offcanvas component with embedded Tabs. For the Tabs and Content, they are created from an array of Table components which is mapped to create them dynamically. These Table components take a container as input, which I used a Ref to bind them to the dynamically generated Tabs. The first render works perfectly, however; when I close the Offcanvas and open it again, the Tab content is now empty. I'm assuming this is an issue with the Ref.
Here is the code for the Tab component.
import React, { useEffect, useRef } from 'react';
import Tabs from "react-bootstrap/Tabs";
import Tab from "react-bootstrap/Tab";
function LayerResults({featureTables, mapView, placeFeatureTables}) {
const resultRef = useRef(featureTables.map(() => React.createRef()));
useEffect(()=> {
if (featureTables.length) {
placeFeatureTables(resultRef);
}
}, [mapView])
return (
<Tabs defaultActiveKey={featureTables[0].layer.title} className="tableResults">
{featureTables && featureTables.map((table, i) => {
return (
<Tab
className="TableTab"
title={`${table.layer.title.slice(0,10)}...`}
eventKey={table.layer.title}
key={`${i}`}
>
<div ref={resultRef.current[i]}></div>
</Tab>
)
})}
</Tabs>
)
};
And here's the code for the placeFeatureTables() function from an even higher component that the useEffect uses which updates the featureTables state with the container / Ref.
const placeFeatureTables = (containerRefs) => {
setFeatureTables(prev => {
const placedTables = prev.map((table, i) => {
table.container = containerRefs.current[i].current;
return table;
})
return placedTables;
})
}
I feel like this is most likely the issue area and the Containers / Refs are not being updated properly on re-render and I should be using something other than useRef. The thing that's really bugging me though, is that I swear this was working earlier.
Note1: The FeatureTables come from ESRI ArcGIS Javascript API. Although, it's just the Ref being passed, so I don't think it's necessary to know about them. FeatureTable Documentation
Note2: If I console.log the featureTables. The first time, the container highlights the area where it's at. Once I close and re-open and then click on the new featureTables container, it says the node does not exist.

React 17.0.2 Functional Component Only Plays CSS Animation on First Render

I'd like to be able to display text feedback to a user after they answer a question and then fade out the text each time they answer. I'm trying a very simple situation to start out with where I play static feedback after every question. The text between the div is displayed and then fades after the initial rendering of the component but on subsequent renders the text is not displayed and the animation does not occur (I'm using Chrome). I can confirm the component is being re-rendered with Chrome Dev Tools after each cycle and I can see the text in the DOM. I'm using forwards so that at the end of the animation the text will stay invisible. The problem I'm trying to solve is why does the animation only occur after the first render cycle and what do I need to do in order to animate each time the component renders? Other details are that the app uses Redux and all components are functional.
Here's a sandbox that shows what the issue looks like. In this case I passed in props to the feedback component to force it to re-render. Each time you type into the text input it forces the feedback component to re-render (which I confirmed by logging to the console) but the animation only plays on the first render.
https://codesandbox.io/s/reactcssanimationissue-zwc6l?file=/src/UserFeedBack/UserFeedBackComponent.js
import React from "react";
import classes from "./AnswerFeedBackComponent.module.css";
const AnswerFeedBackComponent = () => {
return (
<div className={classes.CorrectAnswer} >Correct!</div>
);
}
export default AnswerFeedBackComponent;
---
.CorrectAnswer{
color:green;
font-weight: bold;
animation: fade 3s linear forwards;
opacity: 1;
}
#keyframes fade {
0% {
opacity: 1;
}
100%{
opacity: 0;
}
}
During re-render react looks for only the elements which have changed. If not, then it doesn't update the element, even if it rerenders.
So you can either make react know that this element have changed, or unmount and mount the component again. I have provided both the usecases.
You can add a key prop with a random value, so that react knows its different on each re-render and hence remounts the component. This is quick and hacky and it works.
const UserFeedBackComponent = (props) => {
console.log("UserFeedBackComponent rendered");
const rand = Math.random();
return (
<div className={classes.Border} key={rand}>
<div className={classes.CorrectAnswer}>Correct!</div>
</div>
);
};
https://codesandbox.io/s/reactcssanimationissue-forked-9jufd
Another way would be to remove it from the dom after its invisible. The logic will be handled by its parent, InputComponent
import React, { useState } from "react";
import UserFeedBackComponent from "../UserFeedBack/UserFeedBackComponent";
import classes from "./InputComponent.module.css";
const InputComponent = () => {
const [currentInput, setCurrentInput] = useState("");
const [showNotification, setShowNotification] = useState(false);
const inputHandler = (event) => {
setCurrentInput(event.target.value);
setShowNotification(true);
setTimeout(() => {
setShowNotification(false);
}, 3000);
};
return (
<React.Fragment>
<input type="text" value={currentInput} onChange={inputHandler} />
<div className={classes.Output}>{"Output is " + currentInput}</div>
{showNotification && <UserFeedBackComponent text={currentInput} />}
</React.Fragment>
);
};
export default InputComponent;
Every time you need to show the notification, you just need to set showNotification to true, and set it to false again via setTimeout which will fire after the duration of your animation i.e 3s. This is much better because, there's no stale invisible element, polluting your dom. You can still iterate and improve upon it.
https://codesandbox.io/s/reactcssanimationissue-forked-mejre

How do I take a screenshot of a React component without mounting it in the DOM?

My use case is: I have a web site editor and a list of available web pages for users. Each page in this list is represented by a thumbnail. Every time a user makes a change to a page using the editor, the thumbnail of the respective site has to be updated to reflect the change. The way I'm doing is by mounting a ThumbnailSandbox component in the page, passing the props from the Redux store and then using dom-to-png to create the screenshot and use it in the list. But I wanted to do it without mounting the component on the page, because I think it would be a cleaner solution and with less chances of being affected by other interactions happening. So, I created a CodeSanbox to illustrate what I'm trying to achieve.
My logic is this:
import React from "react";
import ReactDOMServer from "react-dom/server";
import html2canvas from "html2canvas";
import MyComp from "./component.jsx";
export const createScrenshot = () => {
const el = (
<div>
test component <MyComp />
</div>
);
const markup = ReactDOMServer.renderToString(el);
let doc = new DOMParser().parseFromString(markup, "text/html");
let target = doc.body.getElementsByClassName("my-comp")[0];
console.log(markup, target);
html2canvas(target, {
useCORS: true,
allowTaint: true,
scale: 1,
width: 500,
height: 500,
x: 0,
y: 0,
logging: true,
windowWidth: 500,
windowHeight: 500
})
.then(function(canvas) {
console.log(">> ", canvas);
})
.catch(error => {
console.log(error);
});
};
So, I'm passing the component to ReactDOM, then creating a DOM node using the string from first step and passing the node to html2canvas. But at this point I get the error Uncaught TypeError: Cannot read property 'pageXOffset' of null. Because the ownerDocument of the element passed to html2canvas is null and it doesn't have the properties: devicePixelRation, innerWidth, innerHeight, pageYOffset, and pageXOffset. As I understand, that's because the node element is not part of the DOM.
Now, my questions are:
1) Is there a way to solve this problem using html2canvas?
2) Is there any other way to take a screenshot of a React component, in the browser, without mounting the component in the DOM?
Thank you in advance!!
For point 1:
Why don't you mount the component and then after your handling delete the component in the ref? (can be done in ComponentDidMount too but ref would come before DidMount) That's the most standard solution to perform downloads (create an a tag do a click and then remove it)
This is a sample untested code using ref call back
export class CreateScrenshot extends React.Component {
constructor() {
super() {
this._reactRef = this._reactRef.bind(this);
this.state = {
removeNode: false
};
}
}
_reactRef(node) {
if(node) {
// your html2Canvas handling and in the returned promise remove the node by
this.setState({removeNode: true});
}
}
render() {
let childComponent = null;
if(!this.state.removeNode) {
{/*pass the ref of the child node to the parent component using the ref callback*/}
childComponent = (
<div>
test component <MyComp refCallBack={this._reactRef}/>
</div>
);
}
return childComponent;
}
}
the limitation however is that this will be asynchronous and might cause a flicker.
So if possible try using a sync library so that the node can be removed on the next render.
For point 2: https://reactjs.org/docs/react-component.html#componentdidmount
From react's componentDidMount() doc section:
"It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position."
This makes it clear that you can only get the nodes' measurements after it has been mounted.
Set the react element to z-index and bottom -9999px

Resources