React memo creates rerender - javascript

I'm having an issue with react memo when using nextjs. In the _app e.g. I have a button imported:
import { ChildComponent } from './ChildComponent';
export const Button = ({ classN }: { classN?: string }) => {
const [counter, setCounter] = useState(1);
const Parent = () => {
<button onClick={() => setCounter(counter + 1)}>Click me</button>
}
return (
<div>
{counter}
<Parent />
<ChildComponent />
</div>
);
};
Child component:
import React from 'react';
export const ChildComponent = React.memo(
() => {
React.useEffect(() => {
console.log('rerender child component');
}, []);
return <p>Prevent rerender</p>;
},
() => false
);
I made one working in React couldn't figure it out in my own app:
https://codesandbox.io/s/objective-goldwasser-83vb4?file=/src/ChildComponent.js

The second argument of React.memo() must be a function that returns true if the component don't need to be rerendered and false otherwise - or in the original definition, if the old props and the new props are equal or not.
So, in your code, the solution should be just change the second argument to:
export const ChildComponent = React.memo(
() => { ... },
// this
() => true
);
Which is gonna tell React that "the props didn't change and thus don't need to rerender this component".

So my issue was that I made a function called Button and returned inside a button or Link. So I had a mouseEnter inside the button which would update the state and handle the function outside the function. Kinda embarrassing. This fixed it. So the only change was I moved usestate and handlemousehover inside the button function.
const Button = () => {
const [hover, setHover] = useState(false);
const handleMouseHover = (e: React.MouseEvent<HTMLElement>) => {
if (e.type === 'mouseenter') {
setHover(true);
} else if (e.type === 'mouseleave') setHover(false);
};
return (
<StyledPrimaryButton
onMouseEnter={(e) => handleMouseHover(e)}
onMouseLeave={(e) => handleMouseHover(e)}
>
<StyledTypography
tag="span"
size="typo-20"
>
{title}
</StyledTypography>
<ChildComponent />
</StyledPrimaryButton>
);
};

Related

React - change value of parent variable from child

I know this could be a noob question but I'm learning React for a few months and now I'm stucked with this problem. I got this code over here:
import React, { useCallback, useEffect, useRef, useState } from 'react'
import ReactTags from 'react-tag-autocomplete'
const TagsHandler = ({ tagPlaceholder, suggestions }) => {
const [tags, setTags] = useState([])
const reactTags = useRef()
const onDelete = useCallback(
(tagIndex) => {
setTags(tags.filter((_, i) => i !== tagIndex))
},
[tags]
)
const onAddition = useCallback(
(newTag) => {
setTags([...tags, newTag])
},
[tags]
)
useEffect(() => {
suggestions.map((suggestion) => {
suggestion.disabled = tags.some((tag) => tag.id === suggestion.id)
})
}, [tags])
return (
<ReactTags
ref={reactTags}
tags={tags}
suggestions={suggestions}
onDelete={onDelete}
onAddition={onAddition}
placeholderText={tagPlaceholder}
/>
)
}
export default TagsHandler
Which implements a tag list inside my parent component. This parent component has a bool value which enables a save button. I should enable this button whenever a user adds or removes a tag to the list. My question is: how can I handle this bool from the child component? I've read about Redux but I'd like to avoid using it. I was thinking about a SetState function or a callback but I can't figure out the syntax.
Any help would be really appreciated, thanks :)
You can simply create a function in your parent component: toggleButton, and pass the function to your child component.
function Parent = (props) => {
const [isToggle, setIsToggle] = useState(false);
const toggleButton = () => {
setIsToggle(!isToggle)
}
return <Child toggled={isToggle} toggle={toggleButton} />
}
So the general approach is as follows:
In the Parent.jsx:
const [childrenActive, setChildrenActive] = useState(false)
// later in the render function
<Children setIsActive={(newActive) => setChildrenActive(newActive)} />
In the Children.jsx:
const Children = ({ setIsActive }) => {
return <button onClick={() => setIsActive(true)}>Click me</button>
}
So, as you have guessed, we pass a callback function. I would avoid passing setState directly because it makes your component less flexible.
In the parent component, the function that is responsible for changing that bool variable, you pass as a prop to the child component. At the child component you get that props and you can update if you want.

Why is a function passed through props into a child component behaving differently on a key press vs. button click?

I have a React project where the parent component (functional) holds state to determine what formatting is applied to a list. When the user clicks a button, a modal is generated - I pass an anonymous function to that modal as props (onMainButtonClick), which when called flips the state and changes the formatting of the list (logic for this is in parent component).
When I use the button in my modal component (onClick={() => onMainButtonClick()}), the code works as expected. However, I would also like an enter press to trigger this. Therefore I have the following code implemented for this, but it doesn't function as expected. The modal closes and the function fires (I know this as I put a console log in there...) but the state that impacts the formatting is not changed.
EDIT: Having made a proposed change below (to memo'ize the onEnterPress function so it gets removed properly), here's the full code for the modal:
import { React, useRef, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const Modal = ({
title,
description,
isOpen,
onClose,
onMainButtonClick,
mainButtonText,
secondaryButtonText,
closeOnly,
}) => {
const node = useRef();
const onEnterPress = useCallback(
(e) => {
if (e.key === "Enter") {
onMainButtonClick();
}
},
[onMainButtonClick]
);
useEffect(() => {
window.addEventListener("keydown", onEnterPress);
return () => {
window.removeEventListener("keydown", onEnterPress);
};
}, [onMainButtonClick]);
const handleClickOutside = (e) => {
if (node.current.contains(e.target)) {
return null;
}
onClose();
};
if (!isOpen) return null;
return ReactDOM.createPortal(
{// JSX here, removed for brevity},
document.body
);
};
export default Modal;
And the code for the parent (which shows the modal and passes down onMainButtonClick):
import { React, useState } from "react";
import { decode } from "html-entities";
import useList from "../../queries/useList";
import useBuyItem from "../../mutations/useBuyItem";
import Header from "../Shared/Header";
import Description from "../Shared/Description";
import ListItem from "./ListItem";
import BuyOverlay from "./BuyOverlay";
import Modal from "../Modal";
import ViewOperations from "./ViewOperations";
const View = (props) => {
const [buyOverlayOpen, setBuyOverlayOpen] = useState(false);
const [viewBuyersOverlayOpen, setBuyersOverlayOpen] = useState(false);
const [viewBuyers, setViewBuyers] = useState(false);
const [buyItemId, setBuyItemId] = useState();
const { isLoading, isError, data, error } = useList(props.match.params.id);
const buyItemMutation = useBuyItem(buyItemId, props.match.params.id, () =>
setBuyOverlayOpen(false)
);
if (isLoading) {
return <div>Loading the list...</div>;
}
if (isError) {
return <div>An error occured, please refresh and try again</div>;
}
return (
<div className="w-full">
<div className="mb-10 text-gray-600 font-light">
<Header text={data.name} />
<Description text={data.description} />
<ViewOperations
toggleViewBuyers={() => setViewBuyers(!viewBuyers)}
toggleBuyersOverlay={() =>
setBuyersOverlayOpen(!viewBuyersOverlayOpen)
}
viewBuyers={viewBuyers}
/>
<div id="list" className="container mt-10">
{data.items.length === 0
? "No items have been added to this list"
: ""}
<ul className="flex flex-col w-full text-white md:text-xl">
{data.items.map((item) => {
return (
<ListItem
item={decode(item.item)}
description={decode(item.description)}
itemId={item._id}
isBought={decode(item.bought)}
boughtBy={decode(item.boughtBy)}
boughtDate={decode(item.boughtDate)}
viewlink={item.link}
handleBuy={() => {
setBuyItemId(item._id);
setBuyOverlayOpen(true);
}}
key={item._id}
viewBuyers={viewBuyers}
/>
);
})}
</ul>
</div>
</div>
<BuyOverlay
isOpen={buyOverlayOpen}
message="Message"
onClose={() => setBuyOverlayOpen(false)}
onConfirmClick={(buyerName) => {
buyItemMutation.mutate(buyerName);
}}
/>
<Modal
title="Title"
description="Description"
isOpen={viewBuyersOverlayOpen}
onClose={() => {
setBuyersOverlayOpen(false);
}}
onMainButtonClick={() => {
setViewBuyers(true);
setBuyersOverlayOpen(false);
}}
mainButtonText="OK"
secondaryButtonText="Cancel"
/>
</div>
);
};
export default View;
Any ideas for why this is happening? I have a feeling it might be something to do with useEffect here, but I'm a bit lost otherwise...
onEnterPress is most probably directly defined in your functional component and that's why its reference changes every time your component re-renders. Your useEffect closes over this newly defined function on each render so your handler is not going to work as you expected.
You can wrap your onEnterPress with useCallback to memoize your handler onEnterPress so its definition stays the same throughout each render just like this:
const onEnterPress = useCallback(e => {
if (e.key === "Enter") {
onMainButtonClick();
}
}, [onMainButtonClick]); // Another function dep. You can also wrap it with useCallback or carry over its logic into the callback here
useEffect(() => {
window.addEventListener('keydown', onEnterPress);
return () => {
window.removeEventListener('keydown', onEnterPress);
};
}, [onEnterPress]);
I figured this out in the end - turns out it was as simple as my enterPress callback needing a preventDefault statement before the if block (e.preventDefault()).
There must have been something else on the page that was capturing the enter press and causing it to behave strangely.

How to re-render a react component after returning something in a function?

I'm creating a react app with a method to draw some arrows among divs. The function doesn't produce arrows right away returns the JSX but just after I manually re-render the DOM by scrolling the page or do a route change. How can I force render the DOM after the return of the function?
drawArrows = () => {
const questionsList = this.state.questionsData;
return questionsList.map(each =>
<Arrow
key={each.order}
fromSelector={`#option${each.order}`}
fromSide={'right'}
toSelector={`#q${each.order}`}
toSide={'left'}
color={'#ff6b00'}
stroke={3}
/>
);
}
render (){
return(
...code
{this.drawArrows()}
)
}
you can do it easy with functional Component with useState and useEffect
import { useState } from 'react';
const ComponentName = () => {
const [arow, setArow] = useState();
const [questionsData, setQuestionsData] = useState([]);
const drawArrows = () => questionsData.map(each =>
<Arrow
key={each.order}
fromSelector={`#option${each.order}`}
fromSide={'right'}
toSelector={`#q${each.order}`}
toSide={'left'}
color={'#ff6b00'}
stroke={3}
/>
);
useEffect(() => {
setArow(drawArrows())
}, [])
return (
{ arow }
)
}

How to set the state using context.provider in react and typescript?

i am using context.provider usecontext reacthook to show a dialog. i set this around MainComponent. For the value attribute of context.provider i get error type {setDialogOpen(Open: boolean) => void} is not assignable to type boolean.
what i am trying to do?
I want to display a dialog when user clicks either a button in home or books component. on clicking hide button in dialog the dialog shouldnt be open.
below is my code,
function MainComponent() {
const DialogContext = React.createContext(false);
let [showDialog, setShowDialog] = React.useState(false);
return (
<DialogContext.Provider value={{
setDialogOpen: (open: boolean) => setShowDialog(open)}}>//get error
{showDialog && <Dialog DialogContext={DialogContext}/>
<Route
path="/items">
<Home DialogContext={DialogContext}/>
</Route>
<Route
path="/id/item_id">
<Books DialogContext={DialogContext}/>
</Route>
</DialogContext.Provider>
)
}
function Home({DialogContext} : Props) {
const dialogContext= React.useContext(DialogContext);
const handleClick = (dialogContext: any) {
dialogContext.setDialogOpen(true);
}
return (
<button onClick={handleClick(dialogContext)}>button1</button>
)
}
function Books({DialogContext} : Props) {
const dialogContext= React.useContext(DialogContext);
const handleClick = (dialogContext: any) {
dialogContext.setDialogOpen(true);
}
return (
<button onClick={handleClick(dialogContext)}>button2</button>
)
}
function Dialog({DialogContext}: Props) {
return(
<div>
//sometext
<button> hide</button>
</div>
)
}
I have tried something like below,
return (
<DialogContext.Provider value={{
setShowDialog(open)}}>//still get a error
{showDialog && <Dialog DialogContext={DialogContext}/>
)
Could someone help me fix this or provide a better approach to show the dialog on clicking a button in home and books component using usecontext hook. thanks.
There are few issues that you have to fix in your code.
You are creating context with the default value of false. Then later you try to override it to an object and hence the type error.
To fix the issue, create & export the context in separate file/helper. Don't pass them down as props.
import the context in parent and child components.
your handleClick fun in child component is missing an arrow.
the button onClick in child component is directly calling the function. You should pass just the function reference.
See the updated code with corrections below.
context helper
...
type ContextProps = {
setDialogOpen?: (open: boolean) => void,
};
export const DialogContext = React.createContext<ContextProps>({});
MainComponent
import {DialogContext} from '../contextHelper';
function MainComponent() {
// const DialogContext = React.createContext(false); //<---- remove this
let [showDialog, setShowDialog] = React.useState(false);
return (
<DialogContext.Provider value={{
setDialogOpen: (open: boolean) => setShowDialog(open)}}>
...
Home & Book Component
import {DialogContext} from '../contextHelper';
function Home() {
const dialogContext= React.useContext(DialogContext);
const handleClick = () => {
dialogContext.setDialogOpen(true);
}
return (
<button onClick={handleClick}>button1</button>
)
}
import {DialogContext} from '../contextHelper';
function Books() {
const dialogContext= React.useContext(DialogContext);
const handleClick = () => {
dialogContext.setDialogOpen(true);
}
return (
<button onClick={handleClick}>button2</button>
)
}

Not able to call the the method of the child component in react

I have following components .
this component accept props which is function that can be called.
deleteIcon = () => {
console.log("deleting the document");
}
export const Parent = (props) => {
return (
<button onclick={() => {deleteIcon()}}
)
}
Now, I have some another component which uses this component. which has its own implementation of the deleteIcon method.
deletechildIcon = () => {
}
export const child = () => {
return (
<Parent deleteIcon={() => {deletechildIcon()}} />
)
}
So, from child still it is calling the parent method and not the child one. can any one help me with this ?
Some notice points:
onClick rather than onclick
no need to use arrow function inside props, which may cause performance loss and it's not the best practice
write your functions inside the component
Child component is the one which been called inside the Parent, you made it the opposite
Try the demo in-text:
const Parent = () => {
const deleteIcon = () => {
console.log("deleting the document");
};
return <Child deleteIcon={deleteIcon} />;
};
const Child = props => {
return <button onClick={props.deleteIcon}>XXX</button>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
You are calling the deleteIcon method in your Parent component, not the props.deleteIcon
It sounds like you want Parent to have a default deleteIcon prop that can be optionally overridden in specific implementations. You could do that by editing Parent like so:
deleteIcon = () => {
console.log("deleting the document");
}
export const Parent = (props) => {
const buttonOnClickHandler = props.deleteIcon || deleteIcon;
return (
<button onClick={() => {buttonOnClickHandler()}}
)
}
Or you could use default arguments:
deleteIconDefault = () => {
console.log("deleting the document");
}
export const Parent = ({
deleteIcon = deleteIconDefault
}) => {;
return (
<button onClick={() => {this.props.deleteIcon()}}
)
}
Hope this points you in the right direction!

Categories

Resources