How to use shouldComponentUpdate with React Hooks? - javascript

I've been reading these links:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate
https://reactjs.org/blog/2018/10/23/react-v-16-6.html
In the first link it says (https://reactjs.org/docs/hooks-faq.html#from-classes-to-hooks):
shouldComponentUpdate: See React.memo
The second link also states that:
Class components can bail out from rendering when their input props are the same using PureComponent or shouldComponentUpdate. Now you can do the same with function components by wrapping them in React.memo.
What is desired:
I want Modal to render only when the Modal is visible (managed by this.props.show)
For class component:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.show !== this.props.show;
}
How can I use memo instead in a functional component - here, in Modal.jsx?
The related code:
Functional component Modal.jsx (I don't know how to check for props.show)
import React, { useEffect } from 'react';
import styles from './Modal.module.css';
import BackDrop from '../BackDrop/BackDrop';
const Modal = React.memo(props => {
useEffect(() => console.log('it did update'));
return (
<React.Fragment>
<BackDrop show={props.show} clicked={props.modalClosed} />
<div
className={styles.Modal}
style={{
transform: props.show ? 'translateY(0)' : 'translateY(-100vh)',
opacity: props.show ? '1' : '0'
}}>
{props.children}
</div>
</React.Fragment>
);
});
export default Modal;
The part of class component PizzaMaker jsx that renders Modal:
return (
<React.Fragment>
<Modal show={this.state.purchasing} modalClosed={this.purchaseCancel}>
<OrderSummary
ingredients={this.state.ingredients}
purchaseCancelled={this.purchaseCancel}
purchaseContinued={this.purchaseContinue}
price={this.state.totalPrice}
/>
</Modal>
...
</React.Fragment>
);

Here is the documentation for React.memo
You can pass a function to control the comparison :
const Modal = React.memo(
props => {...},
(prevProps, nextProps) => prevProps.show === nextProps.show
);
when the function returns true, the component will not be re-rendered

Also you can use in export statement like:
export default memo(Modal, (prevProps, nextProps) => prevProps.show === nextProps.show) ;

Related

findDOMNode is deprecated in StrictMode. Warning - react-transition-group + react v17 + Javascript (Not Typescript)

I'm trying to get rid of a warning message in the project I'm working on.
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node
at div
at Transition (http://localhost:3000/static/js/vendors~main.chunk.js:47483:30)
at CSSTransition (http://localhost:3000/static/js/vendors~main.chunk.js:46600:35)
at div
at TransitionGroup (http://localhost:3000/static/js/vendors~main.chunk.js:48052:30)
at Contacts (http://localhost:3000/static/js/main.chunk.js:1623:96)
at div
at div
at Home (http://localhost:3000/static/js/main.chunk.js:2549:88)
at AuthCheck (http://localhost:3000/static/js/main.chunk.js:2705:5)
at Routes (http://localhost:3000/static/js/vendors~main.chunk.js:45749:5)
at div
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:45682:15)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:45198:5)
at ContactState (http://localhost:3000/static/js/main.chunk.js:3743:85)
at AuthState (http://localhost:3000/static/js/main.chunk.js:3243:85)
at AlertState (http://localhost:3000/static/js/main.chunk.js:2844:85)
at App
The problematic code:
import React, { Fragment, useEffect } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useContactContext } from '../../context/contact/contactContext';
import { useAuthtContext } from '../../context/auth/authContext';
import ContactItem from './ContactItem';
import Spinner from '../layout/Spinner';
const Contacts = () => {
const { contacts, filtered, getContacts, loading } = useContactContext();
const { isAuthenticated } = useAuthtContext();
useEffect(() => {
if (isAuthenticated) {
getContacts();
}
// eslint-disable-next-line
}, [isAuthenticated]);
if (!loading && contacts !== null && contacts.length === 0) {
return <h4>Please add a contact</h4>;
}
return (
<Fragment>
{contacts === null || loading ? (
<Spinner />
) : (
<TransitionGroup>
{(filtered || contacts).map((contact) => (
<CSSTransition timeout={1000} classNames="item" key={contact._id}>
<ContactItem contact={contact} />
</CSSTransition>
))}
</TransitionGroup>
)}
</Fragment>
);
};
export default Contacts;
I've spent a few hours looking for answers, but I feel like I'm running around in an endless loop.
To get rid of the warning, I need to use useRef hooks on each CSSTransition element, to connect it with (it's children?).
I can't use useRef() inside the render function of a component, so I defined a new component to display each TransitionItem:
...
const TransitionItem = ({ contact, ...props }) => {
const ref = useRef(null); // Had to use this ref to go around a warning
return (
<CSSTransition nodeRef={ref} timeout={1000} classNames="item" {...props}>
<div ref={ref}>
<ContactItem contact={contact} />
</div>
</CSSTransition>
);
};
return (
<Fragment>
{contacts === null || loading ? (
<Spinner />
) : (
<TransitionGroup>
{(filtered || contacts).map((contact) => (
<TransitionItem key={contact._id} contact={contact} />
))}
</TransitionGroup>
)}
</Fragment>
);
...
Now every time I try to click on a button, to remove an item from the list, I see a "flashing" effect, you can check out in this Sandbox: (Click on the red buttons to remove an item)
https://codesandbox.io/s/kind-feather-2psuz
The "flashing" problem only starts when I move the CSSTransition component into the new TransitionItem component, but I can't use useRef hooks on each item if I don't move it there.
Help pls! :)
PS:
Removing <React.StrictMode> from the index.js is not a solution to the root problem.
I have the same warning in my project and i can fix it with this solution, thank pixel-fixer !
Issue #668 on repo react-transition-group
From 4.4.0 release notes:
react-transition-group internally uses findDOMNode, which is
deprecated and produces warnings in Strict Mode, so now you can
optionally pass nodeRef to Transition and CSSTransition, it's a ref
object that should point to the transitioning child:
You can fix this like this
import React from "react"
import { CSSTransition } from "react-transition-group"
const MyComponent = () => {
const nodeRef = React.useRef(null)
return (
<CSSTransition nodeRef={nodeRef} in timeout={200} classNames="fade">
<div ref={nodeRef}>Fade</div>
</CSSTransition>
)
}
I hope it works for you, have a nice day !

HOC for JSX element - rendering jsx with wrapped element

I want to call a ReactJS HOC to wrap a tooltip around JSX.
The call should be able like this:
withTooltip(JSX, "very nice")
Therefor I have created this function:
import React from "react";
import MUITooltip from "#material-ui/core/Tooltip";
import useStyles from "./index.styles";
const withTooltip = (Component, text: string) => (props) => {
const classes = useStyles();
return (
<MUITooltip className={classes.root} title={text}>
<Component {...props} />
</MUITooltip>
);
};
export default withTooltip;
The call:
import withTooltip from "commons/withTooltip/withTooltip";
const dialogBtn =
isOk &&
withTooltip(
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>,
"Very nice",
);
return (
<Fragment>
{dialogBtn}
</Fragment>
);
It says:
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it
How to solve it ?
Your HOC accepts a Component argument while you are passing in JSX. Try wrapping the JSX with a function or pass in a component which renders the Button.
However, in your case, you probably want to have control over the toolTip text in your component. If this is the case, I would not use a HOC for this, but rather a wrapping Component.
function WithTooltip({ classes, text, children }) {
return (
<MUITooltip className={classes.root} title={text}>
{children}
</MUITooltip>
);
}
export default WithTooltip;
const dialogBtn = isOk && (
<WithTooltip text="Very nice">
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>
</WithTooltip>
);

Too many re-renders with React Hooks and Redux

I have a component that displays a list of Cards. I'm trying to sort the table rows but running into some issues. When i go to the page i'm getting the following error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
and it's pointing to this line
setData(_.sortBy(filteredData.reverse()));
here's my full component code. can anyone see a problem with what I'm trying to do?
import React, { useState } from "react";
import Search from "./Search";
import TimeAgo from "react-timeago";
import { useSelector, useDispatch, connect } from "react-redux";
import { Table } from "semantic-ui-react";
import { searchChange } from "../reducers/searchReducer";
import _ from "lodash";
// import { useField } from "../hooks";
const searchCards = ({ baseball, search }) => {
return search
? baseball.filter(a =>
a.title[0].toLowerCase().includes(search.toLowerCase())
)
: baseball;
};
const Cards = props => {
const [column, setColumn] = useState(null);
const [direction, setDirection] = useState(null);
const [filteredData, setData] = useState(props.cardsToShow);
const handleSort = clickedColumn => {
if (column !== clickedColumn) {
setColumn(clickedColumn);
setData(_.sortBy(filteredData, [clickedColumn]));
setDirection("ascending");
return;
}
setData(_.sortBy(filteredData.reverse()));
direction === "ascending"
? setDirection("descending")
: setDirection("ascending");
};
return (
<>
<div>
<Search />
<h3>Vintage Card Search</h3>
<Table sortable celled fixed striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell
sorted={column === "title" ? direction : null}
onClick={handleSort("title")}
>
Card Title
</Table.HeaderCell>
<Table.HeaderCell># Bids</Table.HeaderCell>
<Table.HeaderCell>Watchers</Table.HeaderCell>
<Table.HeaderCell>Price</Table.HeaderCell>
<Table.HeaderCell>Time Left</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{props.cardsToShow.map(card => (
<>
<Table.Row key={card.id}>
<Table.Cell>{card.title}</Table.Cell>
<Table.Cell>
{card.sellingStatus[0].bidCount
? card.sellingStatus[0].bidCount
: 0}
</Table.Cell>
<Table.Cell>
{card.listingInfo[0].watchCount
? card.listingInfo[0].watchCount
: 0}
</Table.Cell>
<Table.Cell>
$
{card.sellingStatus &&
card.sellingStatus[0].currentPrice[0]["__value__"]}
</Table.Cell>
<Table.Cell>
<TimeAgo
date={new Date(
card.listingInfo && card.listingInfo[0].endTime
).toLocaleDateString()}
/>
</Table.Cell>
</Table.Row>
</>
))}
</Table.Body>
</Table>
</div>
</>
);
};
const mapStateToProps = state => {
return {
baseball: state.baseball,
search: state.search,
cardsToShow: searchCards(state)
};
};
const mapDispatchToProps = {
searchChange
};
export default connect(mapStateToProps, mapDispatchToProps)(Cards);
// export default Cards;
Yachaka has already pointed out the incorrect line, but their answer doesn't explain what the issue is.
When you pass props in React with prop={expression}, the expression in the brackets gets evaluated, much like function arguments are evaluated when they are passed. Hence, whenever the component is rendered, handleSort("title") is called. This function then causes the props to be updated, and the component is re-rendered, causing the cycle to repeat indefinitely.
So the problem is that, instead of passing a function that should be called when the button is clicked, you call that function (with handleSort("title")), which results in undefined, and causes a feedback loop.
Instead you should use an expression that returns a function. The most concise way of doing that in JavaScript is an arrow function, as Yachaka mentioned () => handleSort("title"). This evaluates to a function that calls handleSort.
Change this line:
onClick={handleSort("title")}
by
onClick={() => handleSort("title")}
EDIT: Reinis has written a nice explanation below!

Passing a variable between non-nested components using Context API

Suppose I have two components which aren't nested: a button and a panel. When the button is clicked, the panel will show or hide depending on the previous state (like an on/off switch). They aren't nested components, so the structure looks like this:
<div>
<Toolbar>
<Button />
</Toolbar>
<Content>
...
<ButtonPanel />
</Content>
</div>
I can't change the structure of the DOM. I also can't modify any other component other than the button and panel components.
The Button and ButtonPanel components are related, however, and will be used together throughout the solution. I need to pass a property to the panel to let it know when to show or when to hide. I was thinking about doing it with Context API, but I think there's something I'm doing wrong and the property never updates.
This is my code:
Context
import React from 'react';
export const ButtonContext = React.createContext({
showPanel: false,
});
Button
import React, { Component } from 'react';
import { ButtonContext } from './ButtonContext';
class Button extends Component {
constructor() {
super();
this.state = {
showPanel: false,
};
}
render() {
return (
<ButtonContext.Provider value={{ showPanel: this.state.showPanel }}>
<li>
<a
onClick={() => this.setState({ showPanel: !this.state.showPanel }, () => console.log('Changed'))}
>
<span>Button</span>
</a>
</li>
</ButtonContext.Provider>
);
}
}
export { Button };
Panel
import React, { Component } from 'react';
import { Panel, ListGroup, ListGroupItem } from 'react-bootstrap';
import { ButtonContext } from './ButtonContext';
class ButtonPanel extends Component {
static contextType = ButtonContext;
render() {
return (
<ButtonContext.Consumer>
{
({ showPanel }) => {
if (showPanel) {
return (
<Panel id="tasksPanel">
<Panel.Heading >Panel Heading</Panel.Heading>
<ListGroup>
<ListGroupItem>No Items.</ListGroupItem>
</ListGroup>
</Panel>
);
}
return null;
}
}
</ButtonContext.Consumer>
);
}
}
export { ButtonPanel };
I've also tried simply accessing the context in the ButtonPanel component like so:
render() {
const context = this.context;
return context.showPanel ?
(
<Panel id="tasksPanel">
<Panel.Heading >Tasks</Panel.Heading>
<ListGroup>
<ListGroupItem className="tasks-empty-state">No tasks available.</ListGroupItem>
</ListGroup>
</Panel>
)
:
null;
}
What am I doing wrong?
Thanks
From the React docs:
Accepts a value prop to be passed to consuming components that are descendants of this Provider.
So this means that <ButtonContext.Provider> has to wrap <ButtonContext.Consumer> or it has to be higher up in the component hierarchy.
So based on your use case, you could do:
// This app component is the div that wraps both Toolbar and Content. You can name it as you want
class App extends Component {
state = {
showPanel: false,
}
handleTogglePanel = () => this.setState(prevState => ({ togglePanel: !prevState.togglePanel }));
render() {
return (
<ButtonContext.Provider value={{ showPanel: this.state.showPanel, handleTogglePanel: this.handleTogglePanel }}>
<Toolbar>
<Button />
</Toolbar>
<Content>
<ButtonPanel />
</Content>
</ButtonContext.Provider>
);
}
}
class Button extends Component {
...
<ButtonContext.Consumer>
{({ handleTogglePanel }) => <a onClick={handleTogglePanel} />}
</ButtonContext.Consumer>
}
class ButtonPanel extends Component {
...
<ButtonContext.Consumer>
{({ showPanel }) => showPanel && <Panel>...</Panel>}
</ButtonContext.Consumer>
}

Using a functional component within a class

I'm wondering how I can create a stateless component within a class. Like if I use these functions outside the class, my page renders, but when I put them in the class. My page doesn't render. I want them to be inside the class so I can apply some class props to them.
class helloClass extends React.Component {
state = {
};
Hello =({ items}) => {
return (
<ul>
{items.map((item, ind) => (
<RenderHello
value={item.name}
/>
))}
</ul>
);
}
RenderHello = ({ value }) => {
return (
<div>
{open && value && (
<Hello
value={value}
/>
)}
</div>
);
}
render() {
}
}
export default (helloClass);
I have a setup like this. But not actually like this. And I keep getting the error that Hello and RenderHello do not exist. However, if I turn these into functions outside of the class, they work and everything renders on my page. I just want to know how I can achieve the same but within a class. If that's even possible.
Several ways of doing it, but the cleanist is to separate the stateless functions into it's their own files and have a single container that handles state and props and passes them down to the children:
Hello.js (displays the li items)
import React from 'react';
export default ({ items }) => (
<ul>
{items.map((item, ind) => (
<li key={ind}>
{item.name}
</li>
))}
</ul>
);
RenderHello.js (only returns Hello if open and value are true)
import React from 'react';
import Hello from './Hello';
export default ({ open, value, items }) => (
open && value
? <Hello items={items} />
: null
);
HelloContainer.js (contains state and methods to update the children nodes)
import React, { Component } from 'react';
import RenderHello from './RenderHello';
class HelloContainer extends Component {
state = {
items: [...],
open: false,
value: ''
};
...methods that update the state defined above (ideally, these would be passed down and triggered by the child component defined below)
render = () => <RenderHello {...this.state} />
}
Its strange because you have a recursive call that will end up in a infinite loop, but syntactically, it would be something like that:
class helloClass extends React.Component {
state = {
};
Hello(items) {
return (
<ul>
{items.map((item, ind) => (
{this.RenderHello(item.name)}
))}
</ul>
);
}
RenderHello(value) {
return (
<div>
{open && value && (
{this.Hello(value)}
)}
</div>
);
}
render()
{
}
}
export default (helloClass);

Categories

Resources