ReactJs: Prevent Rerender of wrapped component - reactjs

I'm trying to prevent a re-render when using custom hook for hours now -.-, need some help ;O|
(Dont know if I should call this custom hook or functional hoc though)
I have a MessageList component that display a SimpleMessage wrapped in WithAvatarHeader.
Here is my profiler result:
Every time I add a message to the list, all messages are rendered again.
This isn't happening when I only use SimpleMessage in MessageList
Is there a way to memo(WithAvatarHeader) ?
MessageList :
import React from "react";
import SimpleMessage from "./SimpleMessage";
import WithAvatarHeader from "./WithAvatarHeader";
const MessageList = props => {
const Message = WithAvatarHeader(SimpleMessage);
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
SimpleMessage:
import React, { memo } from "react";
const SimpleMessage = props => {
return (
<div className="simple-message">
{props.message}
</div>
);
};
export default memo(SimpleMessage);
WithAvatarHeader:
import React from "react";
const WithAvatarHeader = WrappedComponent => props => {
return <WrappedComponent {...props} />;
};
export default WithAvatarHeader;
Thanks for the help :-)

You should not declare component inside another component.
Once you move declaration outside:
const Message = WithAvatarHeader(SimpleMessage);
const MessageList = props => {
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
you will be fine.
Reason is reconciliation process that decides what's to drop, what to create and what to update.
Besides your JSX says it still same element <Message> React checks component's constructor(it does not work with text representation from JSX). And it will referentially different(since you re-declare this constructor on next render). So React drops every <Message> and create them from scratch. Keeping declaration outside your MessageList means constructor is referentially the same so React will not re-create <Message> till key is the same.

Related

How to prevent component from being re-rendered unnecessarily

I'll start with the code. I have a stateless functional component that resembles this
export const Edit Topic = (_title, _text) {
const [title, setTitle] = useState(_title)
const [text, setText] = useState(_text)
return (
<>
<InputText props={{ fieldName:"Title:", value:title, setValue:setTitle, placeHolder:"Topic Title"}}/>
<InputTextArea props={{ fieldName:"Markdown Text:", text, setText }}/>
<PreviewBox text={text}/>
</>
)
}
I have PreviewBox when it's on, page rendering takes a bit longer because text can be quite long. PreviewBox needs to re-render each time I change text in InputTextArea and that's fine.
The problem I'm having is when I change the value of title it's also updating <PreviewBox/> which is undesired.
How can I make sure that <PreviewBox/> only updates when text changes and not when title changes?
The reason why I believe the re-rendering is occuring is because if I toggle off PreviewBox, there's no lag in when updating title but when PreviewBox is visible the updating the title lags.
import style from "../styles/CreateTopic.module.css"
import { Component } from "react"
import Markdown from "./Markdown";
export class PreviewBox extends Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
toggleShow = () => {
console.log("begin isShow", this.state)
this.setState(state => ({ isShow: !state.isShow}))
}
render() {
return (
<>
<div className={style.wrptoggle}>
<button className={style.btn} onClick={this.toggleShow}>Preview</button>
</div>
{this.state.isShow ?
<div className={style.wrppreviewbox}>
<div className={style.previewbox}>
<Markdown text={this.props.text}/>
</div>
</div>
: null}
</>
)
}
}
Since the above also contains <Markdown/> here's that component:
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({text}) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
}
export default Markdown;
I don't see any complexity in PreviewBox that would cause any rendering delay so I might assume it's the Markdown component that may take some time "working" when it's rerendered since you say "toggle off PreviewBox, there's no lag in when updating title".
Solution
You can use the memo Higher Order Component to decorate the Markdown component and provide a custom areEqual props compare function.
import { memo } from 'react';
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({ text }) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
};
export default memo(Markdown);
By default it will only shallowly compare complex objects in the props
object. If you want control over the comparison, you can also provide
a custom comparison function as the second argument.
const areEqual = (prevProps, nextProps) => {
return prevProps.text === nextProps.text;
};
export default memo(Markdown, areEqual);

Functional Component's props in Higher Order Component

I am trying to understand passing the functional component's props to the returned functional component
**CODE ------------------------------------------------
App Component:
import React from 'react';
import ClickCounter from './ClickCounter';
const App = () => {
return (
<div className="App">
<ClickCounter firstName="John" lastName="Doe"/>
</div>
);
}
export default App;
ClickCounter Component:
import React from 'react'
import withCounter from './withCounter'
const ClickCounter = (props) => {
const { count, incrementCount, name } = props
return (
<div className="click-counter">
<button onClick={incrementCount}>Click Button</button>
<h2>{name}</h2>
<h1>{count}</h1>
</div>
)
}
export default withCounter(ClickCounter, 10)
withCounter Component (HOC)
import React, { useState } from 'react'
const withCounter = (WrappedComponent, incrementNumber) => {
return props => { // ** A **
console.log('props ---- ', props)
const [count, setCount] = useState(0)
return (
<WrappedComponent. // ** B **
count={count}
incrementCount={() => setCount(count + incrementNumber)}
{...props}
/>
)
}
}
export default withCounter
From my understanding, the withCounter return a functional component (A) that use useState Hook and and it return another component (B) which access the state through closure.
** QUESTION -------------------------------------------
My question is why the props in A is firstName="John" lastName="Doe", is it a React thing or Javascript's thing.
Does React pass the parameter's props (i.e wrapped component's props) to the returned functional component? or it is a Javascript's thing?
i read it in another post saying it is related to curry, but i cannot see it is related to curry, below is the post i read
HoC with React Hooks
const useSample = WrappedComponent => props => { // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
{...props}
value={sampleCtx.value}
setValue={sampleCtx.setValue}
/>
);
};
Thank you!!
My question is why the props in A is firstName="John" lastName="Doe", is it a React thing or Javascript's thing.
This is because that's the props you passed to your Counter. Attributes on a component are props passed to functional components. This is a React thing.
<ClickCounter firstName="John" lastName="Doe"/>
When you put the component ClickCounter in your tree, it's actually just withCounter(ClickCounter, 10) being called, since that's your default export.
withCounter is a function that returns another function (curry) that takes props and returns a component. This is the setup for a functional component.
React runs this function and passes the props firstName="John" lastName="Doe" to that function. Then, those props are added to your WrappedComponent via the spread operator {...props}.

Pass state onto {children} React hook

So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;

How to add {props.children} to a React component

i have many components which have {props.children} deeply nested inside.
considery DRY principle is there a way to add this using some React pattern.
example
let's say i have two components,
Comp1.js
import React from "react";
const Comp1 = props => {
return (
<div>
<h1>{props.children}</h1>
</div>
);
};
export default Comp1;
Comp2.js
import React from "react";
const Comp2 = props => {
return (
<div>
<div>
<h1>{props.children}</h1>
</div>
</div>
);
};
export default Comp2;
if you see above code we have both Comp1 and Comp2 have line of code {props.children} repeated inside.
what i want now is some function which will add this line of code, something like below,
const addPropsChildrenToComp = (Comp)=>{
return(
(props)=>{
///do somehting here
}
)
}
const Comp1 = props => {
return (
<div>
<h1></h1>
</div>
);
};
Comp1WithPropsChildren = addPropsChildrenToComp(Comp1)
using HOC doesn't work because, in HOC we never modify passed component.
anyway to aceve this.?
to get more idea of my problem see this demo: https://codesandbox.io/s/trusting-http-pd1yu
in there i woul like to see CompWithPropsChildren component render props.children inside it.
I think I see what you're trying to get to, and you can accomplish this just using another component.
import React from "react";
import ChildComp from "./ChildComp";
const Comp1 = props => {
return (
<div>
<ChildComp {...props} />
</div>
);
};
export default Comp1;
import React from "react";
const ChildComp = props => {
return <h1>{props.children}</h1>
}
Assuming your ChildComp has some complex logic you don't want to duplicate, this will make it reusable for you.

TypeError: Cannot read property 'map' of undefined Reactjs

Newbie to React and I need help again - everything was fine untill i shifted the code from App.js to separate component - and b/c it is the stateless component, i am using props and map function to access the value and state from App.js but it is not happy -- help please
import React from 'react';
const Recipes = props => (
<div>
{props.recipes.map(recipe => (
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
))}
</div>
);
export default Recipes;
This just means that you don't pass in recipes properly as a prop where you render <Recipes />. Either recipes is null or incorrectly formatted.
ex:
// App.js
import React from 'react';
const App = () => {
const recipes = [{
recipe_id: '<id>',
image_url: '<some url>',
title: 'Lé title'
}];
// recipes could be null/undefined or not even passed as a prop
return (
<Recipes recipes={recipes} />
);
}
export default App;
So it's hard to know exactly what's happening without being able to see how you are passing down props, and exactly what data they contain. The error you are getting implies that you aren't actually sending the recipes array correctly.
I honestly never use stateless functions in react anymore, because PureComponent generally preforms better because of it's built in shouldComponentUpdate which prevents unnecessary re-renders. So here's how I would write that component:
import React, { PureComponent } from 'react';
class Recipes extends PureComonent {
recipeList = () => {
const recipes = this.props;
const recipeArray = recipes.map((recipe) => {
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
});
return recipeArray;
}
render () {
return () {
<div>
{this.recipeList()}
</div>
}
}
}
export default Recipes;
That being said, my guess about the way you wrote your component is that if you were to console out props you would find that it was actually equal to recipes, which is why recipes.recipes is undefined.

Resources