I have two React functional components: C1 and C2. C2 is nested inside C1:
const C2 = () => {
console.log("C2 Render");
return (
<div>I am Component 2</div>
);
};
const C1 = () => {
const [text, setText] = useState("Hello");
const MC2 = React.memo(C2, () => true);
return (
<div className="box">
<h1>The Button</h1>
<button
onClick={() => {
setText(`${text} b`);
}}
className="button">
Click me
</button>
<div>
{text}
</div>
<MC2 />
</div>
);
}
CodePen here.
The problem
I know that a component gets re-rendered under different situations. Among those is the one when the parent re-renders.
That is why I am using a memoized component around C2. But still I can see the console displaying "C2 Render" every time I click the button.
Why?
C1 rerender because of state chnage, so your memoized component is redeclared every time.
just wrap C2 in a React.memo() & you would not see the rerenders
const MC2 = React.memo(() => {
console.log("C2 Render");
return (
<div>I am Component 2</div>
);
}, () => true);
or if you want to memoize only one useCase put it outside the C1 component and have that component memoized:
const C2 = () => {
console.log("C2 Render");
return (
<div>I am Component 2</div>
);
};
const MC2 = React.memo(C2, () => true);
& used it like this:
const C1 = () => {
const [text, setText] = useState("Hello");
return (
<div className="box">
<h1>The Button</h1>
<button
onClick={() => {
setText(`${text} b`);
}}
className="button">
Click me
</button>
<div>
{text}
</div>
<MC2 />
</div>
);
}
Related
Here is the CodeSandBox link: https://codesandbox.io/s/stale-prop-one-g92sv?file=/src/App.js
I find the child components will not show the correct counter values after two button clicks, though the counter is actually incrementing:
import "./styles.css";
import { useState } from "react";
const MyComponent = ({ value }) => {
const [counter] = useState(value);
return <span>{counter}</span>;
};
export default function App() {
const [counter, setCounter] = useState(0);
const isVisible = counter !== 1;
console.log(counter);
return (
<div className="App">
<div>
<button onClick={() => setCounter((counter) => counter + 1)}>
Click me
</button>
</div>
{isVisible && (
<div>
Message 1 is: <MyComponent value={counter} />
</div>
)}
<div style={isVisible ? { display: "block" } : { display: "none" }}>
Message 2 is: <MyComponent value={counter} />
</div>
</div>
);
}
I try to force child component re-render by assigning counter to its key:
export default function App() {
const [counter, setCounter] = useState(0);
const isVisible = counter !== 1;
console.log(counter);
return (
<div className="App">
<div>
<button onClick={() => setCounter((counter) => counter + 1)}>
Click me
</button>
</div>
{isVisible && (
<div>
Message 1 is: <MyComponent key = {counter} value={counter} />
</div>
)}
<div style={isVisible ? { display: "block" } : { display: "none" }}>
Message 2 is: <MyComponent key = {counter} value={counter} />
</div>
</div>
);
}
It works, but I still have no idea why the previous one does not work, since the props.value in MyComponent has changed...
Thanks in advance.
With this:
const MyComponent = ({ value }) => {
const [counter] = useState(value);
return <span>{counter}</span>;
};
You're telling React to set the initial state to the first value prop passed to the component, on mount.
When the component re-renders, the component has already been mounted, so the value passed to useState is ignored - instead, the counter in that child is taken from the state of MyComponent - which is equal to the initial state in MyComponent, the initial value prop passed.
For what you're trying to do, you only have a single value throughout the app here that you want to use everywhere, so you should only have one useState call, in the parent - and then render the counter in the child from the prop, which will change with the parent state.
const MyComponent = ({ value }) => {
return <span>{value}</span>;
};
I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}
I have a component that receives value 'openDrawer' (bool) and function 'toggleDrawerHandler' in props, the function 'toggleDrawerHandler' changes the value of 'openDrawer' prop.
I would like to test it by simulating a click on div that triggers this function, and check if the component change when the value of 'openDrawer' changes.
The component
const NavigationMobile = (props) => {
const { openDrawer, toggleDrawerHandler } = props;
let navClass = ["Nav-Mobile"];
if (!openDrawer) navClass.push("Close");
return (
<div className="Mobile">
<div className="Menubar" onClick={toggleDrawerHandler}>
{openDrawer ? <FaTimes size="1.5rem" /> : <FaBars size="1.5rem" />}
</div>
<nav className={navClass.join(" ")} onClick={toggleDrawerHandler}>
<Navigation />
</nav>
</div>
);
};
The component that sends these props
const Header = (props) => {
const [openDrawer, setOpenDrawer] = useState(false);
const toggleDrawerHandler = () => {
setOpenDrawer((prevState) => !prevState);
};
return (
<header className="Header">
<NavigationMobile openDrawer={openDrawer} toggleDrawerHandler={toggleDrawerHandler} />
</header>
);
};
my test, but doesn't work
it("changes prop openDrawer when click", () => {
const wrapper = shallow(<NavigationMobile />);
expect(wrapper.find("FaBars")).toHaveLength(1);
expect(wrapper.find("nav").hasClass("Nav-Mobile")).toBeTruthy();
wrapper.find(".Menubar").simulate("click", true); // doesnt work
expect(wrapper.find("FaTimes")).toHaveLength(1);
expect(wrapper.find("nav").hasClass("Nav-Mobile Close")).toBeTruthy();
});
I've created a very simplified code version of my problem to understand the REACT rendering using typescript. When I click a button which changes state in the lowest child element all parent elements are updated by the renderer and their children on other forks. How can I change the below so it doesn't do that.
import * as React from 'react';
import { connect } from 'react-redux';
import './Grid.css';
const RenderPopup = (key: number) => {
const open = () => setShowDialog(true);
const [showDialog, setShowDialog] = React.useState(false);
const close = () => setShowDialog(false);
if (!showDialog) {
return (
<div>
<button onClick={open}>do it</button>
</div>
)
}
else {
return (
<div>
<button onClick={close}>close
</button>
</div>
)
}
}
function Cell(key:number) {
return (
<div key={key}>
{key}
{RenderPopup(key)}
</div>
)
}
const Header = () => {
return (
<div className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Person = (rowNum: number) => {
return (
<div key={rowNum} className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Persons = () => {
return (
<div>
{Person(1)}
{Person(2)}
{Person(3)}
</div>
)
}
const Grid = () => {
return (
<div>
<Header />
<Persons />
</div>
);
}
export default connect()(Grid);
I have a button that opens a div that looks like a modal.
On click of this button, I want to add a class on to the body, then on click of a close button, I want to remove this class.
How would you do this in React functional component with useEffect / useState?
You could use this.setState like this in Components
....
handleClick = () => {
this.setState((state) => {
addClass: !state.addClass
})
}
render () {
const { addClass } = this.state
return (
<>
<button onClick={this.handleClick}>Button</button>
<div className={!!addClass && 'yourClassName'}>hello</div>
<>
)
}
And in function like this
function Example() {
const [addClassValue, addClass] = useState(0);
return (
<div>
<button onClick={() => addClass(!addClassValue)}>
Click me
</button>
<div className={addClass ? 'yourClassName'}>hello</div>
</div>
)
}
But you do not need classes because React works with components and you can show your modal like
function Example() {
const [addClassValue, addClass] = useState(false);
return (
<div>
<button onClick={() => addClass(!addClassValue)}>
Click me
</button>
// this Component will be only rendered if addClassValue is true
{addClassValue && <ModalComponent />}
</div>
)
}