How to access values from context in a separate functional component - reactjs

I'm trying to build a simple light mode/dark mode into my app I saw this example on Material UI for light/dark mode but I'm not sure how I can get access to the value for when the user clicks toggleColorMode in my Header component if it's being set in toggleColorMode function?
I guess my question is how can I get access to the value of light/dark mode of the context in my Header component if it's in a different function?
Here is my code.
import React, { useState, useEffect } from "react";
import MoreVertIcon from "#mui/icons-material/MoreVert";
import DarkModeIcon from "#mui/icons-material/DarkMode";
import LightModeIcon from "#mui/icons-material/LightMode";
import Paper from "#mui/material/Paper";
import { useTheme, ThemeProvider, createTheme } from "#mui/material/styles";
import IconButton from "#mui/material/IconButton";
import Navigation from "../Navigation/Navigation";
const ColorModeContext = React.createContext({ toggleColorMode: () => {} });
export const Header = (props) => {
const { mode } = props;
const theme = useTheme();
const colorMode = React.useContext(ColorModeContext);
console.log("mode is...", mode);
return (
<div className="header-container">
<Paper
elevation={3}
style={{ backgroundColor: "#1F1F1F", padding: "15px" }}
>
<div
className="header-contents"
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div
className="logo"
style={{ display: "flex", alignItems: "center" }}
>
<img
src="/images/header-logo.png"
alt="URL Logo Shortener"
width={"50px"}
/>
<h1 style={{ color: "#ea80fc", paddingLeft: "20px" }}>
URL Shortener
</h1>
</div>
<div className="settings">
<IconButton
sx={{ ml: 1 }}
onClick={colorMode.toggleColorMode}
color="inherit"
aria-label="dark/light mode"
>
{theme.palette.mode === "dark" ? (
<DarkModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
) : (
<LightModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
)}
</IconButton>
<IconButton aria-label="settings">
<MoreVertIcon style={{ color: "#fff", cursor: "pointer" }} />
</IconButton>
</div>
</div>
</Paper>
{/* Navigation */}
<Navigation />
</div>
);
};
export default function ToggleColorMode() {
const [mode, setMode] = React.useState("light");
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
},
}),
[]
);
const theme = React.useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<Header mode={mode} />
</ThemeProvider>
</ColorModeContext.Provider>
);
}

Read the documentation: createContext, useContext. You need to render a ContextProvider in your parent (or top-level) component, then you can get the data in any component in the tree like const { theme } = useContext(ColorModeContext);.
You don't need to pass the mode as props, put it as one of the values in the context and access it.
Here's how you would render it in your example:
<ColorModeContext.Provider value={{colorMode, theme}}>
<Header />
</ColorModeContext.Provider>

You can pass an object inside the value in the context provider, in other word you can pass the toggle function inside your value to be consumed in the childern. thus you gain an access to change your mode state.
Note that the way changes are determined can cause some issues when passing objects as value, this might trigger unnecessary rerendering see Caveats for more info. or refer to the useContext docs
<ColorModeContext.Provider
value={{ colorMode: colorMode, toggleColorMode: toggleColorMode }}
>
<ThemeProvider theme={theme}>
<Header />
</ThemeProvider>
</ColorModeContext.Provider>

Related

Dynamic URL in React

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

How do I provide a container for popover component?

I decided to use react-simple-popover for my popover needs. Documentation is here: https://github.com/dbtek/react-popover. The problem is that the documentation is for stateful components (class components). The container prop takes in this of the current class component, but I am using stateless component with hooks. What do I pass in place of the "this" keyword in this way?
import React, { useRef, useState } from "react";
import styled from "styled-components";
import Popover from "react-simple-popover";
import { HelpQuestion } from "../UI/Common";
const Wrapper = styled.div`
margin-bottom: 1rem;
`;
export const TextInput = styled.input`
border: 1px solid
${(props) => (props.dark ? "#37394B" : props.theme.lightGray)};
padding: 0.5rem 1rem;
width: 100%;
border-radius: 10px;
color: ${(props) => (props.dark ? "white" : "black")};
background-color: ${(props) => (props.dark ? "#37394B" : "white")};
`;
export default function TextField({
label,
value,
onChange,
onClick,
error,
type,
placeholder,
help,
dark,
disabled,
}) {
const [isPopoverOpen, setPopoverOpen] = useState(false);
const popoverRef = useRef(null);
const componentRef = useRef(null);
return (
<Wrapper ref={componentRef}>
<div style={{ display: "flex", alignItems: "center" }}>
<TextInput
value={value}
onChange={!disabled ? onChange : () => {}}
onClick={onClick}
placeholder={placeholder}
type={type}
dark={dark}
disabled={disabled}
/>
{help && (
<div
style={{ marginLeft: "0.5rem" }}
onClick={() => setPopoverOpen(!isPopoverOpen)}
ref={popoverRef}
>
<HelpQuestion className="fas fa-question" />
</div>
)}
</div>
{error && <Error>{error}</Error>}
<Popover
placement="left"
container={componentRef.current} //doesnt work
target={popoverRef}
show={isPopoverOpen}
onHide={() => setPopoverOpen(false)}
>
<p>{help}</p>
</Popover>
</Wrapper>
);
}
How do I provide a container for popover component?
What do I pass in place of the "this" keyword in this way?
No, I don't think you can. View the source.
const Popover = props => {
if (
ReactDOM.findDOMNode(props.container) &&
ReactDOM.findDOMNode(props.container).parentElement.parentElement !==
document.body
) {
ReactDOM.findDOMNode(props.container).style.position = 'relative';
}
return (
<Overlay
show={props.show}
onHide={props.onHide}
placement={props.placement}
container={props.container}
target={p => ReactDOM.findDOMNode(props.target)}
rootClose={props.hideWithOutsideClick}
>
<PopoverContent
showArrow={props.showArrow}
arrowStyle={props.arrowStyle}
innerStyle={props.style}
style={props.containerStyle}
>
{props.children}
</PopoverContent>
</Overlay>
);
};
It doesn't appear to be well maintained and it's using ReactDOM.findDOMNode which is practically deprecated. I tried this and also tried a small class-based component wrapper. Nothing worked. Each time the reported error referred to the current ref value (componentRef.current) not being a react component.
For what its worth I suggest using a more functional component friendly Popover component. Here's an example using Material-UI's Popover component.
function TextField({
label,
value,
onChange,
onClick,
error,
type,
placeholder,
help,
dark,
disabled
}) {
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const clickHandler = (e) => {
setPopoverOpen((open) => !open);
setAnchorEl(e.target);
};
return (
<Wrapper>
<div style={{ display: "flex", alignItems: "center" }}>
<TextInput
value={value}
onChange={!disabled ? onChange : () => {}}
onClick={onClick}
placeholder={placeholder}
type={type}
dark={dark}
disabled={disabled}
/>
{help && (
<div style={{ marginLeft: "0.5rem" }} onClick={clickHandler}>
<HelpOutlineIcon />
</div>
)}
</div>
{error && <Error>{error}</Error>}
<Popover
anchorEl={anchorEl}
open={isPopoverOpen}
onClose={() => setPopoverOpen(false)}
anchorOrigin={{
vertical: "center",
horizontal: "left"
}}
transformOrigin={{
vertical: "center",
horizontal: "right"
}}
>
<p>{help}</p>
</Popover>
</Wrapper>
);
}

Mui Tabs - How to use custom icons

I'm new to mui and react in general, and I have 2 questions.
I'm trying to use my own SVG files as custom icons in a mui tab, but can't figure out how exactly to do that (with all the effects as using mui icon).
my code:
import { ReactComponent as SecondIcon } from "../../images/tabs/secondTab.svg";
import { ReactComponent as ThirdIcon } from "../../images/tabs/thirdTab.svg";
import { ReactComponent as FourthIcon } from "../../images/tabs/fourthTab.svg";
import FirstTabSvg from "../../images/svg/FirstTabSvg";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
maxWidth: 500,
width: 80,
alignItems: "center",
display: "flex",
flexDirection: "column",
},
margin: {
padding: theme.spacing(2),
},
tab: {
width: 80,
},
imageIcon: {
height: '100%'
},
iconRoot: {
textAlign: 'center'
}
}));
export default function IconTabs() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Paper square className={classes.root}>
<IconButton color="primary" size="small" className={classes.margin}>
<LogoIcon />
</IconButton>
<Divider variant="middle" style={{ alignSelf: "stretch" }} />
<Tabs
value={value}
onChange={handleChange}
// variant="fullWidth"
width="auto"
indicatorColor="primary"
textColor="primary"
aria-label="icon tabs example"
orientation="vertical"
>
<Tab icon={<FirstTabSvg />} aria-label="first" />
<Tab icon={<SecondIcon />} aria-label="second" />
<Tab icon={<ThirdIcon />} aria-label="third" />
<Tab icon={<FourthIcon />} aria-label="fourth" />
</Tabs>
</Paper>
);
}
I've tried 2 options (import as ReactComponent & SvgIcon with src)
FirstTabSvg.js (saw this as an answer in another question):
import React from 'react';
import pure from 'recompose/pure';
import { SvgIcon } from '#material-ui/core';
let FirstTabSvg = (props) => (
<SvgIcon {...props}>
<img src="../tabs/firstTab.svg" />
</SvgIcon>
);
FirstTabSvg = pure(FirstTabSvg);
FirstTabSvg.displayName = 'FirstTabSvg';
FirstTabSvg.muiName = 'SvgIcon';
export default FirstTabSvg;
result:
Tabs
as you can see, the FirstTabSvg does not render, and the ReactComponent does render but I don't know how to change the color for an active tab.
how can I change the tabs width? as you can see in the picture, the indicator is not in the same line as the tabs

I cannot get the updated value from the global state (REDUX) when I use Material-UI

I cannot get the updated state of isAuthenticated when I have Material-UI code.
Is there any way that I can update the state when I have Material-UI code.
import React from "react";
import clsx from "clsx";
import { withStyles } from "#material-ui/core/styles";
import MUILink from "#material-ui/core/Link";
import AppBar from "../UI/components/AppBar";
import Toolbar, { styles as toolbarStyles } from "../UI/components/Toolbar";
import { Link } from "react-router-dom";
import { connect } from 'react-redux';
const styles = theme => ({
title: {
fontSize: 24
},
placeholder: toolbarStyles(theme).root,
toolbar: {
justifyContent: "space-between"
},
left: {
flex: 1
},
leftLinkActive: {
color: theme.palette.common.white
},
right: {
flex: 1,
display: "flex",
justifyContent: "flex-end"
},
rightLink: {
fontSize: 16,
color: theme.palette.common.white,
marginLeft: theme.spacing(3)
},
linkSecondary: {
color: theme.palette.secondary.main
}
});
const Navbar = (props,{isAuthenticated,loading}) => {
const { classes } = props;
const authLinks = (
<div className={classes.right}>
<MUILink
variant="h6"
underline="none"
component={Link} to="/log-out"
className={clsx(classes.rightLink, classes.linkSecondary)}
>
{"Log out"}
</MUILink>
</div>
);
const guestLinks = (
<div className={classes.right}>
<MUILink
color="inherit"
variant="h6"
underline="none"
component={Link} to="/sign-in"
className={classes.rightLink}
>
{"Sign In"}
</MUILink>
<MUILink
variant="h6"
underline="none"
component={Link} to="/sign-up"
className={clsx(classes.rightLink, classes.linkSecondary)}
>
{"Sign Up"}
</MUILink>
</div>
);
return (
<div>
<AppBar position="fixed">
<Toolbar className={classes.toolbar}>
<div className={classes.left} />
<MUILink
variant="h6"
underline="none"
color="inherit"
className={classes.title}
component={Link} to="/"
>
{"buzzer"}
</MUILink>
{!loading && (
<React.Fragment>{isAuthenticated ? authLinks : guestLinks}</React.Fragment>
)}
</Toolbar>
</AppBar>
<div className={classes.placeholder} />
</div>
);
};
const mapStateToProps = state =>({
isAuthenticated:state.auth.isAuthenticated,
loading:state.auth.loading
})
export default connect(mapStateToProps)((withStyles(styles)(Navbar)));
I want that the Navbar to change based on the condition of isAuthenticated. If user is authenticated I want to display only authLink, if the user is not authenticated I want to display the guestLink.
{isAuthenticated,loading} will be injected inside props, and not as a second parameter:
const Navbar = (props) => {
const { classes, isAuthenticated,loading } = props;

React PDF viewer component rerenders constantly

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])

Resources