How to prevent component from being re-rendered unnecessarily - reactjs

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

Related

Map through two arrays of components and strings and render in one component

I have two arrays that I want to map through:
const social = ["Snapchat", "TikTok", "Dribbble", "Discord", "Facebook"];
const socialIcons = [<SnapchatIcon />, <DribbbleIcon />];
The socialIcons array are all components
How can I send both values as props into my DummyRectangle component? Here is my current code:
{social.map((s, index) => (
<div className="dummy_buttonsWrapper">
<DummRectangle social={s} socialIcons={i} />
</div>
))}
And here is DummyRectangle component:
function DummRectangle({ social, socialIcons }) {
// console.log("---->", socialIcons);
return (
<div>
<p>{social}</p>
{<socialIcon/>} // render social icon component
</div>
);
}
To do so, you don't need to wrap tags around your socialIcon in your DummRectangle. Also, it doesn't seem that you are passing the socialIcon component at all. If I were you, I would do something like this:
The following two are the components as an example that you would like to render (in your case - socialIcons)
// Comp1.js
import React from "react";
const Comp1 = () => <div>actual Comp1</div>;
export default Comp1;
// Comp2.js
import React from "react";
const Comp2 = () => <div>actual Comp2</div>;
export default Comp2;
Now, in your main Parent component, you would simply get the current component of the componentName (in your case - social) by accessing your component's array with an index. Then, you would pass this currentComponent as props to your Child component where you want to render it.
// App.js
import React from "react";
import Comp1 from "./Comp1";
import Comp2 from "./Comp2";
import DummyComponent from "./DummyComponent";
export default function App() {
const componentNames = ["Comp1", "Comp2"];
const components = [<Comp1 />, <Comp2 />];
return (
<div className="App">
{componentNames.map((name, index) => {
const currentComponent = components[index];
return (
<div>
<DummyComponent componentName={name} component={currentComponent} />
</div>
);
})}
</div>
);
}
In your Child component, you can simply render it by enclosing it into the brackets - no need to add tags. React will do all the rendering for you. In your case it would be { socialIcon }
// DummyComponent.js
import React from "react";
const DummyComponent = ({ componentName, component }) => {
return (
<div>
<p>{componentName}</p>
{component}
</div>
);
};
export default DummyComponent;
Link to Codesandbox with the above code for reference: click here

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.

How to detect when toggle state has been changed in another component in react

I am researching render props in React. I have a small test project build solely for learning. Favorite component toggles the heart from empty to full when the state of "on
" changes. Toggler component handles the state of on and changes /sets state of on with the toggle function. I'm now on FavoriteText component. What I would like this component to do is when state changes in Favorite component to say a full heart, I want the text in the FavoriteText component to reflect the change with text saying "full heart" or "empty heart". I realize this could've easily been done by including the text in Favorite, but again i am looking to acquire more knowledge on render props in React.
FavoriteText.js
import React, {Component} from 'react'
import Toggler from "./Toggler"
function FavoriteText(props) {
return(
<Toggler defaultOnValue={false} render={
(on)=>(
<h1>{on } ? "Full Heart": "Empty Heart"}</h1>
)
}/>
)
}
export default FavoriteText
Toggler.js
import React, {Component} from "react"
class Toggler extends Component {
state = {
on: this.props.defaultOnValue
}
toggle = (e) => {
this.setState(prevState => {
return {
on: !prevState.on
}
})
}
render() {
return (
<div>{this.props.render(this.state.on, this.toggle)}</div>
)
}
}
export default Toggler
Favorite.js
import React, {Component} from "react"
import Toggler from "./Toggler"
function Favorite(props) {
return (
<Toggler defaultOnValue={false} render={
(on, toggle)=> (
<div>
<h3>Click heart to favorite</h3>
<h1>
<span
onClick={toggle}
>
{on ? "❤️" : "♡"}
</span>
</h1>
</div>
)} />
)
}
export default Favorite

Get ref from connected redux component withStyles

I have this export of a working component:
export default connect(
mapStateToProps, actions,
null, { withRef: true, forwardRef: true }
)(withTheme()(withStyles(styles)(MainMenu)));
And its call:
<MainMenu
ref={(connectedMenu) => this.menuRef = connectedMenu.getWrappedInstance()}
user={user}
/>
I've expected to get a MainMenu ref, but I keep getting WithTheme object instead.
I've also tried to get through innerRef, but got the following errors:
TypeError: connectedMenu.getWrappedInstance is not a function
TypeError: Cannot read property 'getWrappedInstance' of null
Before all of that I've tried that React.createRef() format, but it didn't worked.
How do I get this ref?
Assuming you are using v4 of Material-UI, your syntax for withTheme is incorrect. In v4 the first set of parentheses was removed.
Instead of
withTheme()(YourComponent)
you should have
withTheme(YourComponent)
Below is code from a modified version of the react-redux todo list tutorial that shows the correct syntax. I've included here the two files that I changed (TodoList.js and TodoApp.js), but the sandbox is a fully working example.
In TodoApp, I use the ref on TodoList to get and display its height. The displayed height will only get updated if TodoApp re-renders, so I've included a button to trigger a re-render. If you add a couple todos to the todo list, and then click the re-render button, you will see that the new height of the list is displayed (showing that the ref is fully working).
In TodoList, I'm using withStyles to add a blue border around the todo list to show that withStyles is working, and I'm displaying the primary color from the theme to show that withTheme is working.
TodoList.js
import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { withStyles, withTheme } from "#material-ui/core/styles";
import clsx from "clsx";
const styles = {
list: {
border: "1px solid blue"
}
};
const TodoList = React.forwardRef(({ todos, theme, classes }, ref) => (
<>
<div>theme.palette.primary.main: {theme.palette.primary.main}</div>
<ul ref={ref} className={clsx("todo-list", classes.list)}>
{todos && todos.length
? todos.map((todo, index) => {
return <Todo key={`todo-${todo.id}`} todo={todo} />;
})
: "No todos, yay!"}
</ul>
</>
));
const mapStateToProps = state => {
const { visibilityFilter } = state;
const todos = getTodosByVisibilityFilter(state, visibilityFilter);
return { todos };
};
export default connect(
mapStateToProps,
null,
null,
{ forwardRef: true }
)(withTheme(withStyles(styles)(TodoList)));
TodoApp.js
import React from "react";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
import VisibilityFilters from "./components/VisibilityFilters";
import "./styles.css";
export default function TodoApp() {
const [renderIndex, incrementRenderIndex] = React.useReducer(
prevRenderIndex => prevRenderIndex + 1,
0
);
const todoListRef = React.useRef();
const heightDisplayRef = React.useRef();
React.useEffect(() => {
if (todoListRef.current && heightDisplayRef.current) {
heightDisplayRef.current.innerHTML = ` (height: ${
todoListRef.current.offsetHeight
})`;
}
});
return (
<div className="todo-app">
<h1>
Todo List
<span ref={heightDisplayRef} />
</h1>
<AddTodo />
<TodoList ref={todoListRef} />
<VisibilityFilters />
<button onClick={incrementRenderIndex}>
Trigger re-render of TodoApp
</button>
<div>Render Index: {renderIndex}</div>
</div>
);
}

How to check whether React updates dom or not?

I wanted to check how to react does reconciliation so I updated the inner HTML of id with the same text. Ideally, it shouldn't update the dom but it is paint reflashing in chrome.
I have tried paint reflashing in chrome it is showing green rectangle over that same text
import React from 'react';
function App() {
return (
<div >
<p id="abc" key="help">abc is here</p>
<button onClick={function () {
// document.getElementById("may").innerHTML = "";
document.getElementById("abc").innerHTML = "abc is here";
}} > Btn</button>
</div>
);
}
export default App;
Expected result should be that paint reflashing shouldn't happen but it is happening.
You are not using React here to update the text of your p tag but directly updating the DOM with JavaScript.
So React reconciliation algorithm doesn't even run here.
In React, the output HTML is a result of the state and the props of your component.
When a change in state or props is detected, React runs the render method to check if it needs to update the DOM. So, in order to do this check, you need to store the parameters that determine your view in state or props.
Given your example, we could save the text you want to show in the p tag in the state of your component (using hooks):
import React, { useState } from 'react';
function App () {
const [text, setText] = useState('abc is here');
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => setText('abc is here') }>Btn</button>
</div>
);
}
}
export default App;
If you are using a version of React that does not support hooks, you will need to transform your functional component into a class to use state:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = { text: 'abc is here' };
}
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => this.setState({ text: 'abc is here' }) }>Btn</button>
</div>
);
}
}
export default App;

Resources