Why do react elements outside of Route re-render? - reactjs

Open the sidebar example in official docs of react router. You can see that ul is rendered without Route since it is supposed to be there on screen irrespective of the url. Open React DevTools, check Highlight updates checkbox and click on any of the menu items in the side bar. You will notice that elements under ul re-render on every click. In my opinion, it is not sane behavior, react elements under ul should not re-render with route change given they are not rendered by react routers Route component. Is there a way I can stop them re-rendering?

A Router component depends on context for the change and whenver a context value is updated, it triggers a re-render of the children to do a match and render the appropriate route. Now since the ul element is directly written as a child of Router, it also gets re-rendered. Although react performs a virtual-dom comparison and the DOM won't be re-rendered, you can avoid it by using a PureComponent and writing ul elements within that Component
const SidebarExample = () => (
<Router>
<div style={{ display: "flex" }}>
<div
style={{
padding: "10px",
width: "40%",
background: "#f0f0f0"
}}
>
<Route component={Elements}/>
{routes.map((route, index) => (
// You can render a <Route> in as many places
// as you want in your app. It will render along
// with any other <Route>s that also match the URL.
// So, a sidebar or breadcrumbs or anything else
// that requires you to render multiple things
// in multiple places at the same URL is nothing
// more than multiple <Route>s.
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.sidebar}
/>
))}
</div>
<div style={{ flex: 1, padding: "10px" }}>
{routes.map((route, index) => (
// Render more <Route>s with the same paths as
// above, but different components this time.
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.main}
/>
))}
</div>
</div>
</Router>
)
class Elements extends React.PureComponent {
render() {
return (
<ul style={{ listStyleType: "none", padding: 0 }}>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/bubblegum">Bubblegum</Link>
</li>
<li>
<Link to="/shoelaces">Shoelaces</Link>
</li>
</ul>
)
}
}

Related

How to handle: Check the render method of `ForwardRef`

I have this Next.js / React component:
const AppBarContent = (props: Props) => {
// ** Props
const {
horizontalAppBarContent: userHorizontalAppBarContent,
horizontalAppBarBranding: userHorizontalAppBarBranding,
} = props
// ** Hooks
const theme = useTheme()
return (
<div
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{userHorizontalAppBarBranding ? (
userHorizontalAppBarBranding(props)
) : (
<Link href="/">
<Image src={'/images/logo.svg'} width={48} height={48} />
</Link>
)}
Got this warning:
next-dev.js?3515:25 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? Check the render method of ForwardRef.
If I put const AppBarContent = React.forwardRef((props: Props, ref) => { around component as one suggest: https://stackoverflow.com/a/66423250/239219
I still get the error, but if I but between and warning disappear.
<Link href="/">
<a>
<Image src={'/images/logo.svg'} width={48} height={48} />
</a>
</Link>
what can I do. I think my solution is not based on Next.js doc, it is a shortcut. But the proper solution does not work. Why?
return (
<HorizontalLayoutWrapper className="layout-wrapper">
{/* Navbar (or AppBar) and Navigation Menu Wrapper */}
<AppBar
color="default"
elevation={skin === 'bordered' ? 0 : 3}
className="layout-navbar-and-nav-container"
position={appBar === 'fixed' ? 'sticky' : 'static'}
>
{/* Navbar / AppBar */}
<div className="layout-navbar">
<div className="navbar-content-container">
<AppBarContent

How can I render one component conditionally twice and not lose internal states/unmounts?

I have one component which needs to be rendered conditionally. Renders the same component with different styles. So, I did like this
import ComponentToRender from '../../ComponentToRender'
const Main =()=> {
const [expand,setExpand] =useState(false)
return (
<div>
{!expand && <ComponentToRender {...someProps} />}
{expand && <div>
<ComponentToRender {...otherProps} />
</div>
}
<button onClick={()=>setExpand(pre => !pre)}>Expand</button>
</div>
)
}
For the above code, I get what I want in terms of UI. But, all the internal states are lost. I must render two components like that and keep the internal states. Is that possible to do that in React?
You can achieve this by keeping the component rendered unconditionally and hiding it with CSS.
You get to preserve Component‘s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:
You mount the component on startup, even if the user never accesses it.
You update the component even when it’s invisible.
import ComponentToRender from "../../ComponentToRender";
const Main = () => {
const [expand, setExpand] = useState(false);
return (
<div>
<div style={{ display: expand ? null : "none" }}>
<ComponentToRender {...someProps} />
</div>
<div style={{ display: !expand ? null : "none" }}>
<div>
<ComponentToRender {...otherProps} />
</div>
</div>{" "}
<button onClick={() => setExpand((pre) => !pre)}>Expand</button>
</div>
);
};
The reconciliation algorithm is such that when on next render you move from one component to component of different type (assuming they have same spot in component hierarchy), instance of old component is destroyed.
Since you have <ComponentToRender/> and another one is <div><ComponentToRender/></div>, they are different components (because one is inside a div).
Read about reconciliation.
What you can do is move the state of ComponentToRender to Main and pass it as props. Now even if the component unmounts the state will not be lost.

React onClick routing ONLY works SOMETIMES

Here is the video proof.
https://dsc.cloud/leonardchoo/Screen-Recording-2022-03-08-at-17.25.58.mov
I'm running into a mysterious error where I click the button for navigation, "onClick" event is fired but it does not redirect and render the target component.
As you can see in the screenshot, the onClick event is logged, but the redirect does not happen.
I reproduced the situation here in CodeSandbox.
Stack
React TS
Mantine UI
React Router V5
How can I solve this issue?
First thing I noticed in your code was that is is rendering a WrapperPage component around each routed component with the navigation logic. I tried simplifying the WrapperPage code as much as possible.
Steps Taken:
Refactored the header and navbar props into standalone components in case there was issue generating JSX
Wrapped the Switch component in App with a single WrapperPage instead of each routed component
The issue persisted.
I next removed the UnstyledButton from #mantine/core so only the Link components were rendered, and could not reproduce. I then tried vanilla HTML buttons instead of the UnstyledButton and they again reproduced the issue.
So it seems it is an issue with rendering an interactive element (i.e. anchor tag from Link) within another interactive element (i.e. button from UnstyledButton) that is an issue. Swapping the element order, i.e. Link wrapping the UnstyledButton, appears to reduce the issue. I can't seem to reproduce the issue with the DOM structured this way.
Header
const CustomHeader = ({
opened,
setOpened
}: {
opened: boolean;
setOpened: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const theme = useMantineTheme();
return (
<Header height={70} padding="md">
{/* Handle other responsive styles with MediaQuery component or createStyles function */}
<div style={{ display: "flex", alignItems: "center", height: "100%" }}>
<MediaQuery largerThan="sm" styles={{ display: "none" }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
mr="xl"
/>
</MediaQuery>
<Group>
<ThemeIcon variant="light" color="orange">
🎙
</ThemeIcon>
<Text>Mantine AppShell with React Router</Text>
</Group>
</div>
</Header>
);
};
Navbar
const CustomNavbar = ({ opened }: { opened: boolean }) => {
const location = useLocation();
const { classes } = useStyles();
return (
<Navbar
padding="md"
// Breakpoint at which navbar will be hidden if hidden prop is true
hiddenBreakpoint="sm"
// Hides navbar when viewport size is less than value specified in hiddenBreakpoint
hidden={!opened}
// when viewport size is less than theme.breakpoints.sm navbar width is 100%
// viewport size > theme.breakpoints.sm – width is 300px
// viewport size > theme.breakpoints.lg – width is 400px
width={{ sm: 300, lg: 400 }}
>
<Link
to="/dashboard"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/dashboard"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light">
<DashboardIcon />
</ThemeIcon>
<Text size="sm">Dashboard</Text>
</Group>
</UnstyledButton>
</Link>
<Link
to="/new-recording"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/new-recording"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light" color="red">
<RadiobuttonIcon />
</ThemeIcon>
<Text size="sm">New Recording</Text>
</Group>
</UnstyledButton>
</Link>
<Link
to="/calendar"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/calendar"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light" color="orange">
<CalendarIcon />
</ThemeIcon>
<Text size="sm">Calendar</Text>
</Group>
</UnstyledButton>
</Link>
</Navbar>
);
};
WrapperPage
const WrapperPage = ({ children }: Props): JSX.Element => {
const [opened, setOpened] = useState(false);
return (
<AppShell
// navbarOffsetBreakpoint controls when navbar should no longer be offset with padding-left
navbarOffsetBreakpoint="sm"
// fixed prop on AppShell will be automatically added to Header and Navbar
fixed
header={<CustomHeader opened={opened} setOpened={setOpened} />}
navbar={<CustomNavbar opened={opened} />}
>
{children}
</AppShell>
);
};

How do I share state between routes in React-Router?

I've seen a few questions similar to this on SO but none that quite matched my needs. I'm using React and Material-UI to make a dashboard. I'm using Material-UI's mini variant drawer as a sidebar, with links that should display routes when clicked. The sidebar can be opened by clicking a button, which updates a state variable and adjusts the CSS className of the sidebar. This causes the sidebar/drawer to "slide" open.
If I click a link on the sidebar, I can easily display a desired route. However, I can't get the route to also "slide" to the side when the sidebar/drawer opens. It will probably be easier to understand by looking at the code, so I've included a link to a codesandbox below:
https://codesandbox.io/s/appbar-with-react-router-bkogj?file=/src/App.js
I basically copy and pasted everything from the Material-UI website (using v4 I believe), then added the route myself. Would appreciate any feedback on how to solve this issue.
For this I think the MiniDrawer component needs to render the content since it necessarily is aware of the space the appbar and drawer components occupy.
MiniDrawer
Take and render a children prop.
export default function MiniDrawer({ children }) {
...
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
...
>
...
</AppBar>
<Drawer
...
>
...
</Drawer>
<main className={classes.content}>{children}</main>
</div>
);
}
App
Render the Outlet as a child component.
export default function App() {
return (
<div className="App">
<AppBar>
<Outlet />
</AppBar>
</div>
);
}
RejectTable
Remove the excess margin so it fills the content area the parent component allows.
const useStyles = makeStyles((theme) => ({
content: {
flexGrow: 1,
padding: theme.spacing(3),
height: "100%",
// marginLeft: "4em" // <-- remove
}
}));

which is the best way to implement React router modal that allows navigation inside the own modal

I'm following this code, which allow use some routes inside your modal, this works ok although the code is a bit unclear to me,
https://reacttraining.com/react-router/web/example/modal-gallery
basically I understand that before clicking in some modal link we save the location, also we toggle some backgroud state which allow to our routes know that the modal must be displayed, our routes then get the previous route and render that component (which would be the page where the link to our modal live) and render the modal (because the background state is not null)
so far so good, but now I need move between modals, basically I need display some modal with different links inside it (pointing to another modal) and when the user click some link keep the modal open and display the new content
not sure how create a fork from this code
https://reacttraining.com/react-router/web/example/modal-gallery
but you can replace the code inside that windows with it and get the idea
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useHistory,
useLocation,
} from 'react-router-dom';
// This example shows how to render two different screens
// (or the same screen in a different context) at the same URL,
// depending on how you got there.
//
// Click the "featured images" and see them full screen. Then
// "visit the gallery" and click on the colors. Note the URL and
// the component are the same as before but now we see them
// inside a modal on top of the gallery screen.
export default function ModalGalleryExample() {
return (
<Router>
<ModalSwitch />
</Router>
);
}
function ModalSwitch() {
const location = useLocation();
// This piece of state is set when one of the
// gallery links is clicked. The `background` state
// is the location that we were at when one of
// the gallery links was clicked. If it's there,
// use it as the location for the <Switch> so
// we show the gallery in the background, behind
// the modal.
const background = location.state && location.state.background;
console.log('brackground is', background);
console.log('background is ', background);
return (
<div>
<Switch location={background || location}>
<Route path="/" children={<Gallery />} />
</Switch>
{/* Show the modal when a background page is set */}
{background && <Route path="/modal1" children={Modal1(Hello)} />}
{background && <Route path="/modal2" children={Modal1(Bye)} />}
</div>
);
}
function Gallery() {
const location = useLocation();
console.log('location inside Gallery ', location);
return (
<div>
<div>
<Link
to={{
pathname: '/modal1',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
hello Modal
</Link>
<div>
<Link
to={{
pathname: '/modal2',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
bye Modal
</Link>
</div>
</div>
</div>
);
}
function Hello() {
const location = useLocation();
console.log('location inside hello ', location);
return (
<>
<div>Hola</div>
<div>
<Link
to={{
pathname: '/modal2',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
GO TO MODAL 2
</Link>
</div>
</>
);
}
function Bye() {
const location = useLocation();
console.log('location inside bye ', location);
return (
<>
<div>Modal2: Bye</div>
<div>
<Link
to={{
pathname: '/modal1',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
GO TO MODAL 1
</Link>
</div>
</>
);
}
function Modal1(component) {
const history = useHistory();
const back = e => {
e.stopPropagation();
history.goBack();
};
return (
<div
onClick={back}
style={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
background: 'rgba(0, 0, 0, 0.15)',
}}
>
<div
className="modal"
style={{
position: 'absolute',
background: '#fff',
top: 25,
left: '10%',
right: '10%',
padding: 15,
border: '2px solid #444',
}}
>
rendered
{component()}
<button type="button" onClick={back}>
Close
</button>
</div>
</div>
);
}
right now my possible solution is use redux and save the current page in the state, when the user moves through the modals keep that state intact so I keep displaying the background component, and the link must be the modal link...but I'd like to know if is there some great solution using plain hooks and that doesnt require redux?...thank you
NOTE: please let me know if there is a more clear question title and I'll change this
thank you so much

Resources