I am trying to create an onClick event on an image in a functional component which is the grand-child of the component where I have my Konva Stage.
However nothing seems to fire.
I have even tried putting the onClick on a div in the component with the stage. Still nothing.
The only way I can handle clicks is if I put an onMouseDown on my stage. But that is not practical in this instance, since I need the click to trigger a change in the grandchild component.
Why will nothing trigger?
Here is the code. I have cut away irrelevant code and functions.
Look in the PanelRect.js component to find the onClick() that is supposed to trigger the select() function.
First off, this is the grandparent, with the Konva stage.
Map.js
import React, { useState } from 'react';
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { keyHandler, KEYPRESS } from 'react-key-handler';
import MapRoomRect from './MapRoomRect';
import { PanelRect } from './PanelRect';
const Map = () => {
return (
<section className='map'>
<h1>Projekt X</h1>
<div className='map_box'>
<div className='map_box_container' >
<div className='map_box_container_stage'>
<Stage name='Stage' width={stageWidth} height={stageHeight} onMouseDown={handleStageMouseDown} onKeyPress={handleKeyDown}>
<Layer
onMouseEnter={handleLayerMouseEnter}
onMouseLeave={handleLayerMouseLeave}
>
<MapRoomRect
name='MapRoomRect'
draggable
visible={false}
/>
<Image name='IconImage' draggable visible={false}/>
</Layer>
</Stage>
</div>
</div>
</div>
</section>
);
}
export default Map;
This is the parent component
MapRoomRect.js
import React from 'react';
import { Rect, Group, Image } from 'react-konva';
import useImage from 'use-image';
import { PanelRect } from './PanelRect';
const MapRoomRect = (props) => {
return (
<Group
name='RoomGroup'
roomType='RoomType'
id='0002'
onDragEnd={() => { }}
position={{ x: 200, y: 200 }}
draggable
visible={false}
size={{ width: roomSize, height: roomSize }}
>
<Rect
name='RoomGroupBackground'
size={{ width: roomSize, height: roomSize }}
opacity={0.3}
fill='red'
/>
<PanelRect
name='PanelGroup'
onDragEnd={() => { }}
size={{ width: iconSize, height: iconSize2 }}
x={0}
y={0} />
</Group>
)
}
export default MapRoomRect;
The child / grand-child component:
PanelRect.js
import React, { useState } from 'react';
import { Rect, Group, Image } from 'react-konva';
import useImage from 'use-image';
// Images
import backgroundImage from '../../images/tp6icon.png';
import iconTestImage from '../../images/lo1.png';
import iconTestImage2 from '../../images/lo2.png';
import iconTestImage3 from '../../images/lo3.png';
import iconTestImage4 from '../../images/lo4.png';
import iconTestImage5 from '../../images/lo5.png';
import MapContext from './MapContext';
const PanelRect = (props) => {
// Hooks
const [mapState, setMapState] = useState(mapContext);
let los = [0, 0, 0, 3, 4];
// Hooks
const [testImage] = useImage(iconTestImage);
const [testImage2] = useImage(iconTestImage2);
const [testImage3] = useImage(iconTestImage3);
const [testImage4] = useImage(iconTestImage4);
const [testImage5] = useImage(iconTestImage5);
var images = [testImage,testImage2,testImage3,testImage4,testImage5];
// Constants that does not change
const roomSize = 5;
const roomSize2 = 7;
const iconSize = 2;
const iconSize2 = 2;
const select = (e) => {
console.log('Hi! "e" is now: ' + e + ' hmmm');
}
return (
<Group
name='PanelGroup'
roomType='PanelType'
id='0003'
onDragEnd={() => { }}
position={{ x: 0, y: 0 }}
draggable
size={{ width: roomSize, height: roomSize2 }}
>
<Rect
name='PanelGroupBackground'
size={{ width: roomSize, height: roomSize2 }}
opacity={0.9}
fill='blue'
/>
<Image name='IconImage' lid='LO1' lo='0' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[0]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={0} y={0} />
<Image name='IconImage' lid='LO2' lo='1' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[1]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={2} y={0} />
<Image name='IconImage' lid='LO3' lo='2' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[2]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={0} y={2} />
<Image name='IconImage' lid='LO4' lo='3' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[3]]} size={{ width: iconSize, height: iconSize }} draggable={false} x={2} y={2} />
<Image name='IconImage' lid='LO5' lo='4' className='imageAnchor' onDragEnd={() => { }} onClick={() => {select()}} image={images[los[4]]} size={{ width: iconSize, height: iconSize2 }} draggable={false} x={0} y={4} />
</Group>
)
}
export { PanelRect }
I don't get any error messages or warnings. It's just that nothing happens when I click. No matter where I put the onClick().
Edit 7th aug 2019
I have created a sandbox that shows the problem.
Click "create red square"
Click on the red square.
Click "edit red square"
Click on the blue square inside
Click "edit blue square"
Click on the tiles with numbers on in the blue square.
It is the onClick() on these that I want to fire, and get access to the synthetic event object so I can access the target.
https://codesandbox.io/s/quirky-ramanujan-p53b9?fontsize=14
Related
I added swipeable drawer based on the documentation:
Swipeable You can make the drawer swipeable with the SwipeableDrawer
component.
This component comes with a 2 kB gzipped payload overhead. Some
low-end mobile devices won't be able to follow the fingers at 60 FPS.
You can use the disableBackdropTransition prop to help.
{(['left', 'right', 'top', 'bottom'] as const).map((anchor) => (
<React.Fragment key={anchor}>
<Button onClick={toggleDrawer(anchor, true)}>{anchor}</Button>
<SwipeableDrawer
anchor={anchor}
open={state[anchor]}
onClose={toggleDrawer(anchor, false)}
onOpen={toggleDrawer(anchor, true)}
>
{list(anchor)}
</SwipeableDrawer>
</React.Fragment>
))}
The problem with this is that it's only draggable on mobile device. Is it possible to make it draggable on desktop?
Like the user clicks on the edge with the cursor and drags the drawer to close or open it.
Especially, by adding an edge that the user can click on with the cursor:
Swipeable edge You can configure the SwipeableDrawer to have a visible
edge when closed.
If you are on a desktop, you can toggle the drawer with the "OPEN"
button. If you are on mobile, you can open the demo in CodeSandbox
("edit" icon) and swipe.
import * as React from 'react';
import PropTypes from 'prop-types';
import { Global } from '#emotion/react';
import { styled } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { grey } from '#mui/material/colors';
import Button from '#mui/material/Button';
import Box from '#mui/material/Box';
import Skeleton from '#mui/material/Skeleton';
import Typography from '#mui/material/Typography';
import SwipeableDrawer from '#mui/material/SwipeableDrawer';
const drawerBleeding = 56;
const Root = styled('div')(({ theme }) => ({
height: '100%',
backgroundColor:
theme.palette.mode === 'light' ? grey[100] : theme.palette.background.default,
}));
const StyledBox = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'light' ? '#fff' : grey[800],
}));
const Puller = styled(Box)(({ theme }) => ({
width: 30,
height: 6,
backgroundColor: theme.palette.mode === 'light' ? grey[300] : grey[900],
borderRadius: 3,
position: 'absolute',
top: 8,
left: 'calc(50% - 15px)',
}));
function SwipeableEdgeDrawer(props) {
const { window } = props;
const [open, setOpen] = React.useState(false);
const toggleDrawer = (newOpen) => () => {
setOpen(newOpen);
};
// This is used only for the example
const container = window !== undefined ? () => window().document.body : undefined;
return (
<Root>
<CssBaseline />
<Global
styles={{
'.MuiDrawer-root > .MuiPaper-root': {
height: `calc(50% - ${drawerBleeding}px)`,
overflow: 'visible',
},
}}
/>
<Box sx={{ textAlign: 'center', pt: 1 }}>
<Button onClick={toggleDrawer(true)}>Open</Button>
</Box>
<SwipeableDrawer
container={container}
anchor="bottom"
open={open}
onClose={toggleDrawer(false)}
onOpen={toggleDrawer(true)}
swipeAreaWidth={drawerBleeding}
disableSwipeToOpen={false}
ModalProps={{
keepMounted: true,
}}
>
<StyledBox
sx={{
position: 'absolute',
top: -drawerBleeding,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
visibility: 'visible',
right: 0,
left: 0,
}}
>
<Puller />
<Typography sx={{ p: 2, color: 'text.secondary' }}>51 results</Typography>
</StyledBox>
<StyledBox
sx={{
px: 2,
pb: 2,
height: '100%',
overflow: 'auto',
}}
>
<Skeleton variant="rectangular" height="100%" />
</StyledBox>
</SwipeableDrawer>
</Root>
);
}
SwipeableEdgeDrawer.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* You won't need it on your project.
*/
window: PropTypes.func,
};
export default SwipeableEdgeDrawer;
I'm working on a React project with Redux and I'm consuming a Rest API, I need to implement a functionality where when I select a project from a list and I need to load the project ID in the URL and direct to another screen where a sidebar with the options is loaded. navigation of this project.
Example: Layout
I managed to load the project's Id in the URL and retrieve this ID in the project's home screen, the problem is to store the project's Id and set this ID in the next selected URLs, for example:
path: '/project/:id/companies'
path: '/project/:id/currencies'
path: '/project/:id/settings'
List of projects:
Capture the project id and arrow the url:
href={`#/project/${row.id}/main`}
Routes:
path: '/project/:id/main',
exact: true,
name: 'projectMain',
component: RequireAuth(ProjectMain),
Retrieve ID in main
import { useParams } from 'react-router-dom';
...
const { id } = useParams();
The problem is in the sidebar, where I load a list of items with the path, I'm not able to pass the project id in this list.
Complementando a pergunta
In Sidebar I'm using useHistory(), the problem is that the path comes static by 'props' through importing a file into my template, as you can see below:
Template
import React from 'react';
import { Grid, makeStyles } from '#material-ui/core';
import {
AppContent,
AppHeader,
SidebarApp,
} from '../components/index';
import itemsProject from '../components/itemsSidebar/itemsProject';
const useStyles = makeStyles(theme => ({
appContent: {
paddingLeft: 240,
width: '100%',
backgroundColor: theme.palette.background.paper,
},
}));
const ProjectLayout = () => {
const classes = useStyles();
return (
<div className={classes.appContent}>
<AppHeader />
<Grid container direction="row">
<SidebarApp items={itemsProject} />
<AppContent />
</Grid>
</div>
);
};
export default ProjectLayout;
Sidebar:
/* eslint-disable react/jsx-no-duplicate-props */
import React from 'react';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import Divider from '#material-ui/core/Divider';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import ExpandLessIcon from '#material-ui/icons/ExpandLess';
import Collapse from '#material-ui/core/Collapse';
import {
alpha,
Box,
Card,
ListSubheader,
makeStyles,
Typography,
} from '#material-ui/core';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import translate from '../providers/i18n/translate';
const useStyles = makeStyles(theme => ({
sidebar: {
background: theme.palette.background.dark,
width: 240,
height: '100vh',
border: '1px solid rgba(0, 0, 0, 0.1)',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
paddingTop: 64,
top: 0,
left: 0,
},
sidebarItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
sidebarItemContent: {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
width: '100%',
},
sidebarItemIcon: {
marginRight: 6,
},
sidebarItemText: {
width: '100%',
},
sidebarItemExpandArrow: {
fontSize: '1.2rem !important',
},
sidebarItemExpandArrowExpanded: {
fontSize: '1.2rem !important',
color: theme.palette.primary.main,
fontWeight: 'bold',
},
active: {
background: alpha(theme.palette.primary.light, 0.2),
},
}));
function SidebarItem({ depthStep = 10, depth = 0, expanded, item, ...rest }) {
const [collapsed, setCollapsed] = React.useState(true);
const { label, items, Icon, onClick: onClickProp } = item;
const classes = useStyles();
const history = useHistory();
const location = useLocation();
function toggleCollapse() {
setCollapsed(prevValue => !prevValue);
}
function onClick(e) {
if (Array.isArray(items)) {
toggleCollapse();
}
if (onClickProp) {
onClickProp(e, item);
history.push(item.path);
}
}
let expandIcon;
if (Array.isArray(items) && items.length) {
expandIcon = !collapsed ? (
<>
<ExpandLessIcon className={classes.sidebarItemExpandArrowExpanded} />
</>
) : (
<ExpandMoreIcon className={classes.sidebarItemExpandArrow} />
);
}
return (
<>
<ListItem
className={classes.sidebarItem}
onClick={onClick}
button
dense
className={location.pathname === item.path ? classes.active : null}
{...rest}
>
<div
style={{ paddingLeft: depth * depthStep }}
className={classes.sidebarItemContent}
>
{Icon && (
<Icon
className={classes.sidebarItemIcon}
fontSize="small"
color="primary"
/>
)}
<div className={classes.sidebarItemText}>{label}</div>
</div>
{expandIcon}
</ListItem>
<Collapse in={!collapsed} timeout="auto" unmountOnExit>
{Array.isArray(items) ? (
<List disablePadding dense>
{items.map((subItem, index) => (
<React.Fragment key={`${subItem.name}${index}`}>
{subItem === 'divider' ? (
<Divider style={{ margin: '6px 0' }} />
) : (
<SidebarItem
depth={depth + 1}
depthStep={depthStep}
item={subItem}
/>
)}
</React.Fragment>
))}
</List>
) : null}
</Collapse>
</>
);
}
function Sidebar({ items, depthStep, depth, expanded }) {
const classes = useStyles();
const { key } = useParams();
return (
<Card elevation={0} className={classes.sidebar}>
<List
disablePadding
dense
subheader={
<ListSubheader component="div" id="nested-list-subheader">
{translate('sidebarMenuSettings')}
<Typography>
<Box>{key}</Box>
</Typography>
</ListSubheader>
}
>
{items.map((sidebarItem, index) => (
<React.Fragment key={`${sidebarItem.name}${index}`}>
{sidebarItem === 'divider' ? (
<Divider style={{ margin: '6px 0' }} />
) : (
<SidebarItem
depthStep={depthStep}
depth={depth}
expanded={expanded}
item={sidebarItem}
/>
)}
</React.Fragment>
))}
</List>
</Card>
);
}
export default Sidebar;
Sidebar list items
function onClick(e, item) {}
const itemsProject = [
{
name: 'companies',
label: translate('sidebarProjectCompanies'),
Icon: CompanyIcon,
path: '/project/:id/companies',
onClick,
}
{
name: 'currencies',
label: translate('sidebarProjectCurrencies'),
Icon: CurrencyIcon,
path: '/project/:id/currencies',
onClick,
}
];
export default itemsProject;
How can I pass the ID variable on the Sidebar list items?
I thank you for your help!
You can use ES6 template literals as follows.
path: `/project/${id}/companies`
Since you already defined your path, you just need to use useHistory and navigate to the new link
import { useHistory } from 'react-router';
...
const history = useHistory();
...
// call this whenever you want to navigate
history.push(`/project/${id}/currencies`);
Anybody has experience or idea how to implement an edit photo feature like in LinkedIn edit photo or background in React.js?
Here's the functionalities I wanted.
Can upload image
Show image
Edit uploaded image
zoom-in/zoom-out
rotate
delete
Use react-cropper package which is a react version of cropper.js. You can perform all the functionalities you need using that. There are some changes required in this code also add rotate functionality from cropper.js.
import React, { useState, useRef } from "react";
import Cropper from "react-cropper";
import Button from "#material-ui/core/Button";
import "cropperjs/dist/cropper.css";
import Grid from '#material-ui/core/Grid';
import IconButton from '#material-ui/core/IconButton';
import Tooltip from '#material-ui/core/Tooltip';
import CheckCircleIcon from '#material-ui/icons/CheckCircle';
import ArrowForwardIcon from '#material-ui/icons/ArrowForward';
import ArrowBackIcon from '#material-ui/icons/ArrowBack';
import ArrowDownwardIcon from '#material-ui/icons/ArrowDownward';
import ArrowUpwardIcon from '#material-ui/icons/ArrowUpward';
import CancelIcon from '#material-ui/icons/Cancel';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
button: {
backgroundColor: "#2774b8",
'&:hover': {
backgroundColor: "#2774b8",
}
},
toolsCtr: {
display: 'flex',
padding: '0.5rem'
},
tooltip: {
marginRight: '0.5rem'
},
icnBtn: {
padding: '8px',
borderRadius: '3px',
backgroundColor: '#f0f8ff'
},
icn: {
fontSize: '1.5rem',
color: '#2774b8'
},
cropperCtr: {
border: '2px solid #d9e5f0'
}
}))
export default function Crop(props) {
const cropperRef = useRef();
const classes = useStyles();
const [uploadedImg, setUploadedImg] = useState(props && props.image)
const [croppedLiveUrl, setCoppedLiveUrl] = useState("");
const [croppedData, setCoppedData] = useState({ url: "", data: {} });
const selForProcessing = () => {
props.useImage(croppedLiveUrl);
}
const _onMoveCropBox = () => {
if (null !== cropperRef.current) {
setCoppedLiveUrl(cropperRef.current.cropper.getCroppedCanvas().toDataURL());
setCoppedData({ url: "", data: {} });
}
};
const _onMoveImageLeft = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(-10, 0);
}
}
const _onMoveImageRight = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(10, 0);
}
}
const _onMoveImageTop = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, -10);
}
}
const _onMoveImageBottom = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, 10);
}
}
return (
<React.Fragment>
<div className={classes.cropperCtr}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<Cropper
ref={cropperRef}
src={uploadedImg}
style={{ height: 400, width: "100%", overflow:
'scroll' }}
guides={false}
crop={() => { _onMoveCropBox() }}
crossOrigin={"true"}
autoCrop={false}
movable={true}
move={() => { _onMoveImageTop() }}
/>
</Grid>
</Grid>
<div className={classes.toolsCtr}>
<Tooltip title="Use Selection" aria-label="use" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
selForProcessing()
}}>
<CheckCircleIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Left" aria-label="left" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageLeft()
}}>
<ArrowBackIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Right" aria-label="right" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageRight()
}}>
<ArrowForwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Top" aria-label="top" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageTop()
}}>
<ArrowUpwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Down" aria-label="down" className={classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageBottom()
}}>
<ArrowDownwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
</div>
</div>
</React.Fragment >
);
}
import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<Marker key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></Marker>
)
)
}
{
positions.map(positioning => (
<InfoWindow key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindow>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
I am using the code above to render markers and infowindows for locations that I'm fetching from Firebase. Currently when an update is made in Firebase, the whole mapview is rendered again in order to update the position of the markers and infowindows. How do I go about re-rendering just the marker when an update is made in Firebase.
In your example WrappedMap gets re-created every time. One solution (to prevent Google Maps API reload and maps re-render) would be to place the instantiation of WrappedMap component outside of Maps:
const WrappedMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={5}
defaultCenter={{ lat: -24.9929159, lng: 115.2297986 }}
>
{props.places.map(
position =>
props.isMarkerShown && (
<Marker
key={position.id}
position={{
lat: position.lat,
lng: position.lng
}}
></Marker>
)
)}
</GoogleMap>
))
);
and then just pass positions prop to reflect updated positions on map:
function Map() {
return (
<div>
<WrappedMap
isMarkerShown
positions={positions}
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
You can prevent re-rendering by making use of PureComponent.
In your case positioning is re-rendering the Marker and InfoWindow.
The Updated Code below will only re-render the updated/newly-added Markers.
import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
class MarkerPureComponent extends React.PureComponent {
render () {
return (
<Marker
position = {this.props.position}
>
</Marker>
)
}
}
class InfoWindowPureComponent extends React.PureComponent {
render () {
return (
<InfoWindow defaultPosition = {this.props.defaultPosition}>
{this.props.children}
</InfoWindow>
)
}
}
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<MarkerPureComponent key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></MarkerPureComponent>
)
)
}
{
positions.map(positioning => (
<InfoWindowPureComponent key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindowPureComponent>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
I am using a React PDF viewer in my project. I have a react mui dialog component that I use with react draggable to drag it around.
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import makeStyles from "#material-ui/core/styles/makeStyles";
import DialogContent from "#material-ui/core/DialogContent";
import IconButton from "#material-ui/core/IconButton";
import ClearIcon from "#material-ui/icons/Clear";
import Draggable from "react-draggable";
import Paper from "#material-ui/core/Paper";
import Dialog from "#material-ui/core/Dialog";
import PDFViewer from "./PDFViewer";
function PaperComponent({...props}) {
return (
<Draggable
>
<Paper {...props} />
</Draggable>
);
}
const StyledDialog = withStyles({
root: {
pointerEvents: "none"
},
paper: {
pointerEvents: "auto"
},
scrollPaper: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
marginRight: 20
}
})(props => <Dialog hideBackdrop {...props} />);
const useStyles = makeStyles({
dialog: {
cursor: 'move'
},
dialogContent: {
'&:first-child': {
padding: 10,
background: 'white'
}
},
clearIcon: {
position: 'absolute',
top: -20,
right: -20,
background: 'white',
zIndex: 1,
'&:hover': {
background: 'white'
}
},
paper: {
overflowY: 'visible',
maxWidth: 'none',
maxHeight: 'none',
width: 550,
height: 730
}
});
const PDFModal = (props) => {
const classes = useStyles();
const {open, onClose, pdfURL} = props;
return (
<StyledDialog
open={open}
classes={{root: classes.dialog, paper: classes.paper}}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog"
>
<DialogContent classes={{root: classes.dialogContent}} id="draggable-dialog">
<IconButton className={classes.clearIcon} aria-label="Clear" onClick={onClose}>
<ClearIcon/>
</IconButton>
<PDFViewer
url={pdfURL}
/>
</DialogContent>
</StyledDialog>
);
};
export default PDFModal;
And this is the PDFViewer component:
import React from 'react';
import { Viewer, SpecialZoomLevel, Worker } from '#react-pdf-viewer/core';
import { defaultLayoutPlugin } from '#react-pdf-viewer/default-layout';
import '#react-pdf-viewer/core/lib/styles/index.css';
import '#react-pdf-viewer/default-layout/lib/styles/index.css';
import ArrowForward from "#material-ui/icons/ArrowForward";
import ArrowBack from "#material-ui/icons/ArrowBack";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import RemoveCircleOutlineIcon from '#material-ui/icons/RemoveCircleOutline';
import AddCircleOutlineIcon from '#material-ui/icons/AddCircleOutline';
import './PDFViewer.css';
const PDFViewer = ({url}) => {
const renderToolbar = (Toolbar) => (
<Toolbar>
{
(slots) => {
const {
CurrentPageLabel, CurrentScale, GoToNextPage, GoToPreviousPage, ZoomIn, ZoomOut,
} = slots;
return (
<div
style={{
alignItems: 'center',
display: 'flex',
}}
>
<div style={{ padding: '0px 2px' }}>
<ZoomOut>
{
(props) => (
<IconButton aria-label="delete" onClick={props.onClick}>
<RemoveCircleOutlineIcon />
</IconButton>
)
}
</ZoomOut>
</div>
<div style={{ padding: '0px 2px' }}>
<CurrentScale>
{
(props) => (
<span>{`${Math.round(props.scale * 100)}%`}</span>
)
}
</CurrentScale>
</div>
<div style={{ padding: '0px 2px' }}>
<ZoomIn>
{
(props) => (
<IconButton aria-label="delete" onClick={props.onClick}>
<AddCircleOutlineIcon />
</IconButton>
)
}
</ZoomIn>
</div>
<div style={{ padding: '0px 2px', marginLeft: 'auto' }}>
<GoToPreviousPage>
{
(props) => (
<Button
style={{
cursor: props.isDisabled ? 'not-allowed' : 'pointer',
height: '30px',
width: '30px'
}}
disabled={props.isDisabled}
disableElevation
disableFocusRipple
onClick={props.onClick}
variant="outlined">
<ArrowBack fontSize="small"/>
</Button>
)
}
</GoToPreviousPage>
</div>
<div style={{ padding: '0px 2px' }}>
<CurrentPageLabel>
{
(props) => (
<span>{`${props.currentPage + 1} av ${props.numberOfPages}`}</span>
)
}
</CurrentPageLabel>
</div>
<div style={{ padding: '0px 2px' }}>
<GoToNextPage>
{
(props) => (
<Button
style={{
cursor: props.isDisabled ? 'not-allowed' : 'pointer',
height: '30px',
width: '30px'
}}
disabled={props.isDisabled}
disableElevation
disableFocusRipple
onClick={props.onClick}
variant="outlined">
<ArrowForward fontSize="small"/>
</Button>
)
}
</GoToNextPage>
</div>
</div>
)
}
}
</Toolbar>
);
const defaultLayoutPluginInstance = defaultLayoutPlugin({
renderToolbar,
sidebarTabs: defaultTabs => [defaultTabs[1]]
});
// constantly called
console.log('entered')
return (
<div
style={{
height: '100%',
}}
>
<Worker workerUrl="https://unpkg.com/pdfjs-dist#2.5.207/build/pdf.worker.min.js">
<Viewer
fileUrl={url}
defaultScale={SpecialZoomLevel.PageFit}
plugins={[
defaultLayoutPluginInstance
]}
/>
</Worker>
</div>
);
};
export default PDFViewer;
I can see in the console that PDFViewer is being constantly called. I am not sure what is causing this rerenders the whole time?
Isn't it make sense to re-render when you have a new fileUrl passed to PDFModal? The following sequence should be how the app is executed.
PDFModal, PDFViewer and other related components init
When a file is dragged into the PaperComponent context, the upper level component handles it and passing pdfURL as props
const PDFModal = (props) => {
const { ......., pdfURL } = props;
//...skipped code
return (
<StyledDialog
PaperComponent={PaperComponent}
>
//...skipped code
<PDFViewer
url={pdfURL}
/>
</StyledDialog>
);
};
PDFViewer updated because there is a new prop.
const PDFViewer = ({ url }) => {
//...skipped code
return (
//...skipped code
<Viewer
fileUrl={url}
/>
);
}
I agree what #LindaPaiste said, putting Toolbar maybe an option since it doesn't use the url props passed in. For the re-render problem, I suggest that useCallback can be used to wrap the whole PDFViewer component. Only update the component when the url has changed.
This link provide some insights on when to use useCallback which can be a reference.
const PDFViewer = useCallback(
({ url }) => {
//...skipped code
return (
//...skipped code
<Viewer
fileUrl={url}
/>
)
}, [url])