React Router 4 + Code splitting | Component remounts - javascript

I have the next code:
import React from 'react'
import Loadable from 'react-loadable'
import { Route } from 'react-router-dom'
class App extends React.Component {
state = {
kappa: false
}
componentDidMount() {
setTimeout(() => {
setState({ kappa: true })
}, 1000)
}
render() {
return (
<div className="app">
<Route exact path="/:locale/" component={Loadable({
loader: () => import('../views/IndexPage/index.jsx'),
loading: () => "loading"
})} />
<Route exact path="/:locale/registration" component={Loadable({
loader: () => import('../views/Registration/index.jsx'),
loading: () => "loading"
})} />
{this.state.kappa && <p>Hey, Kappa-Kappa, hey, Kappa-Kappa, hey!</p>}
</div>
)
}
}
When state updates (kappa becomes true and p appears), component on active route (no matter what is it - IndexPage or Registration) remounts. If I import component manually in App and pass it to the Route without code-splitting, components on routes doesn't remount (that's so obvious).
I also tried webpack's dynamic import, like this:
<Route path="/some-path" component={props => <AsyncView {...props} component="ComponentFolderName" />
where import(`/path/to/${this.props.component}/index.jsx`) runs in componentDidMount and upfills AsyncView's state, and it behaves simillar to Loadable situation.
I suppose, the problem is that component for Route is an anonymous function
The question is: how to avoid remount of route components?

Well, this behaviour is normal and documented at React Router 4 docs:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
render works fine both with React Loader and webpack's code splitting.

Related

Resolved: OvermindJS doesn't re-render components on simple state change

First, I have my state variable
export const state: State = {
assetPricesInUsd: {
BTC: '20000',
},
supportedAssets: [],
appLoading: true
}
and of course my overmind config as follows:
export const config = merge(
{
state,
actions,
effects,
},
namespaced({
swap,
send,
receive,
wallet,
}),
)
And inside my _app.tsx, since I am using NextJS, I create my app with the Overmind provider as so:
import { config } from '../lib/overmind'
const overmind = createOvermind(config)
function MyApp({ Component, pageProps }: AppProps) {
return (
<OvermindProvider value={overmind}>
<div data-theme='forest'>
<Navbar />
<Component {...pageProps} />
<Toaster position='top-center' />
</div>
</OvermindProvider>
)
}
export default MyApp
And as a super simple example, I created an 'appLoading' property of my state (as seen above) which initializes to true and is set to false at the end of onInitializeOvermind
export const onInitializeOvermind = async ({
effects,
actions,
state,
}: Context) => {
await actions.loadSupportedAssets()
await actions.loadAssetPrices(state.supportedAssets)
console.log('finished initializing')
state.appLoading = false
}
And in my index.tsx page route, I would like the page to respond to the state.appLoading changing like so:
import { useAppState } from 'lib/overmind'
const Home: NextPage = () => {
const { appLoading } = useAppState()
if (appLoading) {
console.log('app is loading')
return <div>loading...</div>
}
return (
<>
<Head>
<title>Create Next App</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<main className='min-h-screen bg-base grid grid-cols-8 font-Josefin'>
<Sidebar />
<Trade />
</main>
</>
)
}
export default Home
But in my app, <div>Loading...</div> is rendered on initial load, but it is not updated when I set state.appLoading to false.
In the devtools, appLoading is set to true, and the 'finished initializing' is printed to the console, but the page does not rerender.
Am I doing something wrong with accessing the state variable in my Home component, because it seems that the state is not linked to my component, and overmind doesn't know to re-render it?
Edit: There isn’t an issue with creating action/state hooks. My app state changes with actions like state.appLoading, and if I interact with a component THEN its state updates, but if I log in through the Navbar (updating the isConnected state) then the Sidebar component, which uses the isComponent property from the useState hook, doesn’t rerender until I change something about its own internal state.
Edit 2: I made a sample app which highlights the issue I'm facing hosted here: https://github.com/MaxSpiro/example-overmind. This example confirms that the state is being updated, but what I found out is that the two components are not being rendered at first. Only when you change the internal state of the components (with the second button) do they begin to react to the global state of overmind changing.
Edit 3: https://github.com/cerebral/overmind/issues/553

React: prevent function component from rerender when parents components className changes [duplicate]

When hiddenLogo changes value, the component is re-rendered. I want this component to never re-render, even if its props change. With a class component I could do this by implementing sCU like so:
shouldComponentUpdate() {
return false;
}
But is there a way to do with with React hooks/React memo?
Here's what my component looks like:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ConnectedSpringLogo from '../../containers/ConnectedSpringLogo';
import { Wrapper, InnerWrapper } from './styles';
import TitleBar from '../../components/TitleBar';
const propTypes = {
showLogo: PropTypes.func.isRequired,
hideLogo: PropTypes.func.isRequired,
hiddenLogo: PropTypes.bool.isRequired
};
const Splash = ({ showLogo, hideLogo, hiddenLogo }) => {
useEffect(() => {
if (hiddenLogo) {
console.log('Logo has been hidden');
}
else {
showLogo();
setTimeout(() => {
hideLogo();
}, 5000);
}
}, [hiddenLogo]);
return (
<Wrapper>
<TitleBar />
<InnerWrapper>
<ConnectedSpringLogo size="100" />
</InnerWrapper>
</Wrapper>
);
};
Splash.propTypes = propTypes;
export default Splash;
As G.aziz said, React.memo functions similarly to pure component. However, you can also adjust its behavior by passing it a function which defines what counts as equal. Basically, this function is shouldComponentUpdate, except you return true if you want it to not render.
const areEqual = (prevProps, nextProps) => true;
const MyComponent = React.memo(props => {
return /*whatever jsx you like */
}, areEqual);
React.memo is same thing as React.PureComponent
You can use it when you don't want to update a component that you think is static so, Same thing as PureCompoment.
For class Components:
class MyComponents extends React.PureCompoment {}
For function Components:
const Mycomponents = React.memo(props => {
return <div> No updates on this component when rendering </div>;
});
So it's just creating a component with React.memo
To verify that your component doesn't render you can just
activate HightlightUpdates in react extension and check your components reaction on
rendering
We can use memo for prevent render in function components for optimization goal only. According React document:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
According to react documentation:- [https://reactjs.org/docs/react-api.html][1]
React. memo is a higher order component. If your component renders the
same result given the same props, you can wrap it in a call to React.
memo for a performance boost in some cases by memoizing the result.
This means that React will skip rendering the component, and reuse the
last rendered result.
For practical understanding I came across these two videos they are very good if you wanna clear concepts also, better to watch so it'll save your time.
Disclaimer:- This is not my YouTube channel.
https://youtu.be/qySZIzZvZOY [ useMemo hook]
https://youtu.be/7TaBhrnPH78 [class based component]

Google Analytics: Warning: Can't perform a React state update on an unmounted component

I am trying to add Google Analytics to React using the guide found here.
I have
import React, { useEffect } from "react";
import ReactGA from "react-ga";
ReactGA.initialize("UA-0000000-0");
export default (WrappedComponent, options = {}) => {
const trackPage = page => {
ReactGA.set({
page,
...options
});
ReactGA.pageview(page);
};
const HOC = props => {
useEffect(() => trackPage(props.location.pathname), [
props.location.pathname
]);
return <WrappedComponent {...props} />;
};
return HOC;
};
Then, I call it like this:
<BrowserRouter className={classes.root}>
<Switch>
<Route path="/login" component={withTracker(Login)} />
<Route path="/signup" component={withTracker(SignUp)} />
</Switch>
</BrowserRouter>
The full error:
react_devtools_backend.js:2273 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SignIn (at GoogleAnalytics.js:64)
in HOC (created by Context.Consumer)
I have tried with the react hooks method they show as well as the other suggested way on that page but still get the same error. I am new to React so struggling to find the issue.
Thanks!
Trying to set the state in ReactGA.set while component is unmounted can give that warning. You can set a variable 'Mounted' to true in useEffect and in the return function set it to false which is executed when component unmounts.
const HOC = props => {
useEffect(() => {
let isMounted = true
isMounted &&
trackPage(props.location.pathname)
return () => isMounted = false
}, [props.location.pathname]);
If you still get the warning move the ReactGA.set inside useEffect and use Mounted && ReactGA.set.....

Higher Order Component (self) wrapping in React

Good Evening !!
My question title might be off, but here is the problem i'm trying to address. I have a component (Content) which listens for DOM event and call Board (instance which facilitate communication between Containers and their consuming Applications).
import Board from '../Board';
class Content extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
window.addEventListener("message", this.handleCheck, false);
}
componentWillUnmount(){
window.removeEventListener("message", this.handleCheck, false);
}
handleCheck =(event) => {
const { board } = this.props;
board.call({
size: event.detail.size,
panel: event.detail.panel,
.....
})
}
render(){
return null
}
}
I can consume/call Content component as mentioned below,
import Manager from '../../Manager'
const Example = () => (
<div>
<Manager>
<Content pageType="A4" />
</Manager>
</div>
);
The Manager component utilizes the Board API to manage call requests, and maintains the state of it's children. The component provided as children to Manager should also support Board prop.
In Example component i would like to call <Content pageType="A4" /> instead of wrapping with <Manager> and somehow use the <Manager> within the Content component definition (inside the Content component to leverage Manager). i.e
const Example = () => (
<div>
<Content pageType="A4" />
</div>
);
Pretty sure you are just looking for the basic HOC implemenation...
function withManager(Component) {
class WithManager extends React.Component {
...withManagerStuff
render() {
return <Component/>
}
}
return WithManager;
}
and then where you want to use your components with the shared HOC (ContentWithManager) you can do something like - module.exports = withManager(Content)
This stuff gets complex quickly.
I may be off, as I am slightly confused with what you are trying to do. However, I think you need to pass the wrapped (child) component to the wrapping (parent) component.
Here are two HOC examples of how to do this:
Note: Examples use redux and react-router, but the same pattern should
work without redux or react-router.
Redirect HOC: redirect.hoc.js
Redirect HOC Example: trans.app.container.js
<!-- Redirect HOC Example Code -->
<Route component={RedirectHoc(MainContentContainer)} />
Authorization HOC: authorization.hoc.js
<!-- Authorization HOC Example Code -->
<Route exact path="/beer/add" component={AUTHORIZE_ADMIN(BeerAddComponent)}/>

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

Categories