How to prevent re-render of react-router-dom Link component? - reactjs

I am trying to limit the amounts of components that re-render on my app everytime the user clicks something. Given that the Sidebar renders regardless of which page the user is on, it seems to make sense to wrap it inside a React.memo function. This works well and the Sidebar component itself does not seem to re-render. However, the <Link> elements, which I import from react-router-dom do re-render, as do the SidebarAuthButtons and the SidebarCreateButton.
What can I do to prevent this behavior?
import React, { memo } from "react";
import { Link } from "react-router-dom";
import {
SidebarContainer,
SidebarLogo,
SidebarNav,
SidebarMenu,
SidebarListItem,
SidebarButton,
} from "../styles/SidebarStyles";
function Sidebar({ auth }) {
const SidebarAuthButtons = (
<div>
<SidebarButton>
<Link to="/login">Log In</Link>
</SidebarButton>
<SidebarButton outlined={true}>
<Link to="/register">Create Account</Link>
</SidebarButton>
</div>
);
const SidebarCreateButton = (
<SidebarButton>
<Link to="#">Create</Link>
</SidebarButton>
);
return (
<SidebarContainer>
<SidebarLogo>React Project</SidebarLogo>
<SidebarNav>
<SidebarMenu>
<SidebarListItem isHeading={true}>Menu</SidebarListItem>
<SidebarListItem>
<Link to="/">Explore</Link>
</SidebarListItem>
<SidebarListItem>
<Link to="/blogs">Blogs</Link>
</SidebarListItem>
<SidebarListItem>
<Link to="/podcasts">Podcasts</Link>
</SidebarListItem>
<SidebarListItem>
<Link to="/youtube">Youtube</Link>
</SidebarListItem>
</SidebarMenu>
{auth.isAuthenticated ? SidebarCreateButton : SidebarAuthButtons}
</SidebarNav>
</SidebarContainer>
);
}
export default memo(Sidebar);

Move SidebarAuthButtons and SidebarCreateButton outside of the functional component render scope making them into React components (currently they are just jsx saved to a variable). This should fix the rerenders.

Related

Change parent component variable from child component when using routing in react

I am trying to update parent components title from child components url. But the child components are loaded as navigation
Here is my Sandbox
https://codesandbox.io/s/react-typescript-forked-z1ijxi
Here is my layout.tsx
import { createContext, useContext, useState } from "react";
import { Outlet, Link } from "react-router-dom";
const Layout = () => {
const title = "";
return (
<>
<h2>{title}</h2>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/blogs">Blogs</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Outlet />
</>
);
};
export default Layout;
Here I need to change the {title} when each of the child component loads
I tried to follow this answer . But I was not able to accomplish that.. Sorry very new in React
I would recommend using React Redux in your project. This library adds a variable namespace that is available to every component. From this global namespace, you could define a title variable that is accessible to the Layout component and editable by any other component no matter where it is.
Redux takes some time to learn, but it's very useful in all kinds of projects. This article has more information on the concept of Redux, though the exact implementation is a little outdated.

My components are not being rendered when I click a link that should load them

I'm confused as to why nothing happens when I'm clicking links in my app.
In my index.js file, I am loading my main screen called 'Game'. Inside 'Game', I have two links, that when clicked, should render another screen.
In my index.js:
import React from "react";
import ReactDOM from "react-dom";
import Game from "./Game/Game";
ReactDOM.render(
<React.Fragment>
<Game/>
</React.Fragment>,
document.getElementById('gameContainer')
)
In my index.html:
<div>
<div id="gameContainer"></div>
</div>
<div id="root"></div>
My Game.js:
import React from "react";
import CharacterStats from "../CharacterStats";
import DungeonStats from "../DungeonStats";
const characterStatsComponent = () => {
return (
<CharacterStats />
);
}
const dungeonStatsComponent = () => {
return (
<DungeonStats />
);
}
const Game = () => (
<div>
<a id="showCharacter" href="#" onClick={characterStatsComponent}>Show your character</a>
</div>
<br />
<div>
<a id="showDungeon" href="#" onClick={dungeonStatsComponent}>Show current dungeon</a>
</div>
);
export default Game;
The two other components, CharacterStats and DungeonStats are just a few bits of html and reactjs to show some data.
Neither CharacterStats or DungeonStats are loading when I'm clicking the links.
I am also getting no errors in the console.
Nothing happens when the links are clicked.
I also put this inside each onClick event:
console.log('link was clicked');
And it does show the message in the console. So that shows that it knows the link is being clicked.
Is there anything that would prevent them from being loaded?
Thanks!
It wont work because you are returning jsx into the onClick function context, and not into the Game component's return value.
You could define a state using useState, something like showDungeon and showCharacter that defaults to false, change it to true onClick, and in the Game component's return value add:
{ showDungeon && <DungeonStats /> }
React uses something called Synthetic Events to achieve cross browser event handling. If I understood your question correctly than changing the onclick to onClick should do the job for you.

Why this problem happend in my react function?

I'm trying to get information from redux but this error happen and i dont know how could i fix it. That's my first time with react and react Hooks, sorry but i'm lost.
Thank you in advance.
React Hook "useSelector" is called in function "header" which is neither a React function component or a custom React Hook function
My code:
import React from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import Notifications from '../Notifications';
import logo from '~/assets/headerLogo.svg';
import { Container, Profile, Content } from './styles';
export default function header() {
const profile = useSelector(state => state.user.profile);
return (
<Container>
<Content>
<nav>
<img src={logo} alt="GoBarber" />
<Link to="/dashboard">DASHBOARD</Link>
</nav>
<aside>
<Notifications />
<Profile>
<div>
<strong>{profile.name}</strong>
<Link to="/profile">Meu Perfil</Link>
</div>
<img
src={
profile.avatar.url ||
'https://api.adorable.io/avatars/50/abott#adorable.png'
}
alt="profile"
/>
</Profile>
</aside>
</Content>
</Container>
);
}
The rules of hooks lint plugin depends on naming conventions to tell what is a component, what is a hook, and what is any other function. Functions beginning with use (eg, useEffect, useMyCustomStuff) are assumed to be hooks. Functions beginning with a capital letter are assumed to be components. Your code does neither, so it assumes this is just a normal function unrelated to hooks or components.
Rename header to Header to fix this.

How to add js to React components in Gatsby?

I'm trying to add the scroll function in script tags to this header component in Gatsby. I know it could work in html and not in react, but what is the right way to do it? Thanks!
import React from 'react'
import Link from 'gatsby-link'
import './header.css'
const Header = () => (
<div className='Header'>
<div className='HeaderGroup'>
<Link to='/'><img src={require('../img/logo_nav.png')} width='60' /></Link>
<Link to='/index'>Selected Works</Link>
<Link to='/uber'>Uber Thoughts</Link>
<Link to='/awards'>Awards</Link>
<Link to='/about'>About</Link>
</div>
</div>
)
export default Header
<script>
$(window).scroll(function () {
if ($(window).scrollTop() > 10) {
$('.Header').addClass('floatingHeader');
} else {
$('.Header').removeClass('floatingHeader');
}
}
</script>
If you want scripts to load before the DOM is ready you can add your scripts inside html.js file.
From the Gatsby docs:
Gatsby uses a React component to server render the and other
parts of the HTML outside of the core Gatsby application.
Read more about it here.
In your case, what you can do is to write your script inside the componentDidMount react lifecycle method, because you need access to the DOM (as you're using jQuery there) you need to run the script after the body has been loaded, so placing your script in the <head> won't work, you need to add it inside the componentDidMount method by first making your component a class component to get access to the react lifecycle methods.
import React from 'react'
import Link from 'gatsby-link'
import $ from 'jquery'
import './header.css'
class Header extends React.Component {
componentDidMount () {
$(window).scroll(function () {
if ($(window).scrollTop() > 10) {
$('.Header').addClass('floatingHeader');
} else {
$('.Header').removeClass('floatingHeader');
}
})
}
render () {
return (
<div className='Header'>
<div className='HeaderGroup'>
<Link to='/'><img src={require('../img/logo_nav.png')} width='60' /></Link>
<Link to='/index'>Selected Works</Link>
<Link to='/uber'>Uber Thoughts</Link>
<Link to='/awards'>Awards</Link>
<Link to='/about'>About</Link>
</div>
</div>
)
}
}
export default Header
You can also use a Gatsby layout template like the gatsby-starter-blog project and put your script at the bottom of the {children} call as a <script>Your script</script> and it will be available in all your pages, same as using the html.js file but since you need access to the DOM you need to put it inside the body for your script to work (more info about Gatsby layouts here).

react-router connected to redux : works with links but only the URL change when dispatching push

I'm trying to programmatically push an URL to navigate with react-router, redux, and connected-react-router
When clicking on a <Link /> button, it's working great, the URL is changing and the route too.
But when using a dispatch(push(url)), the URL only change and the content is not updated
I've made a minimal example here.
Any help would be really grateful,
Thanks
A lot of anti-pattern code, poor application structured, and mixing of packages is holding your application back.
I rewrote it entirely, here's what I've done:
Reconfigured your application folder's structure to be standard.
Don't mix Router (BrowserRouter) with ConnectedRouter.
Don't place all of your components within the App.js file.
Since the Header is always mounted, you don't need redux, instead you can just use withRouter (it exposes route props to the component).
Your rootReducer is missing a reducer, so I added a dummyReducer that just returns state.
Stick to Link or this.props.history when navigating. For this example, there's no need to use both. Also, you don't need to use ConnectedRouter's push function, because the history is passed as a prop when using withRouter.
Side note: If you want the Header to be a "router" where all route changes pass through here, then you'll need to create an action and a reducer that passes a string and stores it to the redux's store. The Header is then connected to the redux store and updates the route when this string has changed.
Working example: https://codesandbox.io/s/526p7kjqq4
components/Header.js
import React, { PureComponent, Fragment } from "react";
import { withRouter } from "react-router-dom";
class Header extends PureComponent {
goTo = route => {
this.props.history.push(route);
};
render = () => (
<Fragment>
<ul>
<li>
<button onClick={() => this.goTo("/")}> Announcements </button>
</li>
<li>
<button onClick={() => this.goTo("/shopping")}> Shopping </button>
</li>
</ul>
<div>
<button onClick={() => this.goTo("/shopping")}>
Click here to go shopping ! (if you can...)
</button>
</div>
</Fragment>
);
}
export default withRouter(Header);
routes/index.js
import React from "react";
import { Switch, Route } from "react-router-dom";
import Announcements from "../components/annoucements";
import Shopping from "../components/shopping";
export default () => (
<div style={{ padding: "150px" }}>
<Switch>
<Route exact path="/" component={Announcements} />
<Route path="/shopping" component={Shopping} />
</Switch>
</div>
);
components/App.js
import React, { Fragment } from "react";
import Routes from "../routes";
import Header from "./Header";
export default () => (
<Fragment>
<Header />
<Routes />
</Fragment>
);
Here is what you're trying to accomplish: https://codesandbox.io/s/8nmp95y8r2
However, I DO NOT recommend this as it's a bit unnecessary, when history is either already passed as a prop from the Route or can be exposed when using withRouter. According to the Redux docs, it's not recommended either. And instead to either use Link or pass the history prop to the redux action creator instead of programmatic navigation through redux state.
containers/Header.js
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import { push } from "connected-react-router";
class Header extends PureComponent {
goTo = route => this.props.push(route); // this is equivalent to this.props.dispatch(push(route)) -- I'm just appending dispatch to the push function in the connect function below
render = () => (
<Fragment>
<ul>
<li>
<button onClick={() => this.goTo("/")}> Announcements </button>
</li>
<li>
<button onClick={() => this.goTo("/shopping")}> Shopping </button>
</li>
</ul>
<div>
<button onClick={() => this.goTo("/shopping")}>
Click here to go shopping ! (if you can...)
</button>
</div>
</Fragment>
);
}
export default connect(
null,
{ push }
)(Header);
After reading the complete thread react-router-redux's push() not rendering new route, I came across this solution you need to use Router with passing down history as prop down to your app and don't use create from multiple files just import it from a common file.
Here is the working codesandbox: push rendering the new route

Resources