What's wrong with the code below?
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<h2>{count}</h2>
<button
onClick={() => {
setCount((count) => count + 1);
}}
>
increase
</button>
</div>
);
}
will using the arrow function in the event handler cause rerendering and affect performances?
Someone argued I should do this instead.
const [count, setCount] = useState(0);
const increment = () => setCount((count) => count + 1);
return (
<div className="App">
<h2>{count}</h2>
<button onClick={increment}>increase</button>
</div>
);
To me it's just a matter of preference, it doesn't improve performance, am I right?
https://codesandbox.io/s/purple-breeze-8xuxnp?file=/src/App.js:393-618
React triggers a re-render when state changes, parent (or children) re-renders, context changes, and hooks changes. So in your example above, there's no difference to re-renders whether you extract the function or just simply type it in the html.
Related
I want to update only a single element when using setState in a function
import { useState } from "react";
export default function App(){
const [state, setState] = useState("foo");
return(
<Component1/>
<Component2/>
<Component3/>
);
}
I need some way of updating one some of those elements, but not all.
In functional components, you can wrap your component with React.memo. With this way, React will memorize the component structure and on next render, if the props still the same, React does not render again and use the memorized one. For more information, https://reactjs.org/docs/react-api.html#reactmemo
Basically wrap with React.memo. Below code, when state1 change, Component2's render count won't increase because its props stays same. But when state2 change, both of them will render.
export const Component2 = React.memo(({ state2 }) => {
const renderCount = useRef(0);
renderCount.current = renderCount.current + 1;
return (
<div style={{ margin: 10 }}>
Component2(with React.memo): <b>Rendered:</b> {renderCount.current} times,{" "}
<b>State2:</b> {state2}
</div>
);
});
export default function App() {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(1);
return (
<div className="App">
<div onClick={() => setState1((state) => state + 1)}>
Click to change state1
</div>
<div onClick={() => setState2((state) => state + 1)}>
Click to change state2
</div>
<Component1 state1={state1} />
<Component2 state2={state2} />
</div>
);
}
I created a sandbox for you to play. Click the buttons and see React.memo in action. https://codesandbox.io/s/xenodochial-sound-gug2x?file=/src/App.js:872-1753
Also, with Class Components, you can use PureComponents for the same purpose. https://reactjs.org/docs/react-api.html#reactpurecomponent
React beginner here.
My code 👇
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { useState, useEffect } from 'react';
import Mousetrap from 'mousetrap';
export default function Home() {
const [count, setCount] = useState(0);
const triggerSomething = () => {
console.log(count);
};
useEffect(() => {
Mousetrap.bind(['ctrl+s', 'command+s'], e => {
e.preventDefault();
triggerSomething();
});
return () => {
Mousetrap.unbind(['ctrl+s', 'command+s']);
};
}, []);
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>count: {count}</h1>
<p className={styles.description}>
<button onClick={() => setCount(count + 1)}>increment</button>
<br />
<br />
<button onClick={triggerSomething}>triggerSomething</button>
</p>
</main>
</div>
);
}
I'm having an issue when trying to trigger an event from Mousetrap. The count variable is not reactive when triggered from mousetrap but reactive when triggered from the button with onClick.
To replicate this bug you need to:
click the increment button once
click the triggerSomething button. The console should print out 1 (the current state of count)
push command+s or ctrl+s to trigger the same method. The console prints out 0 (the state of count when the component loaded). That should print 1 (the current state).
What am I doing wrong? What pattern should I use here?
UPDATE:
Stackblitz here
When you change the state, the component is re-rendered, i.e. the function is executed again, but the useState hook returns the updated counter this time. To use this updated value in your MouseTrap, you must create a new handler (and remove the old one). To achieve this, simply remove the dependency array of your useEffect call. It will then use the newly created triggerSomething function.
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { useState, useEffect } from 'react';
import Mousetrap from 'mousetrap';
export default function Home() {
const [count, setCount] = useState(0);
const triggerSomething = () => {
console.log(count);
};
useEffect(() => {
Mousetrap.bind(['ctrl+s', 'command+s'], e => {
e.preventDefault();
triggerSomething();
});
return () => {
Mousetrap.unbind(['ctrl+s', 'command+s']);
};
}); // Notice that I removed the dependency array
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>count: {count}</h1>
<p className={styles.description}>
<button onClick={() => setCount(count + 1)}>increment</button>
<br />
<br />
<button onClick={triggerSomething}>triggerSomething</button>
</p>
</main>
</div>
);
}
In the triggerSomething method you can use setCount(count => count + 1) and this should work.
The issue you have is when you don't put the triggerSomething as a dep in the useEffect, count would be the same as the initial state which is 0. but when passing a function counting on the previous value to setCount, you'll spare this issue.
Inside a Component, I have a Modal from which the user can do an update to some data through an API and then change the state of that main Component. Because there is a change in state, everything will re-render.
I would like to keep the modal open, so that I can show a success message.
The code would be something like this.
const Main = () => {
const [state, setState()] = useState();
return (
<Component state={state}>
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
</Component>
)
}
When user clicks on modal's button, the state changes, and Component is re-rendered. Modal is rendered too, as it's inside.
I have thought of two possible solutions:
Move the modal outside of the component. This is a problem, as my actual code is not as simple as the example I posted. In my code, the modal opens on the click of a button B, which is deep inside Component. So, if I move the modal out from Component, I would have to pass the status and the action to change status (e.g. [open, setOpen]) through several components until button B (prop drilling).
Another solution: On the action onButtonClick I just do the API update, and use a new state updated = true; then, onModalClose, only if updated is true, I run setState so Component is rendered just after the modal is closed. But this solution seems a hack to me. There must be a better way.
Is there any better solution?
Something is obviously going very wrong here, the Modal should not close. As a workaround you could do something like this:
const Main = () => {
const [state, setState()] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => {
updateThroughApi().then(() => setState())} />
), [])
return (
<Component state={state}>{modal}</Component>
)
}
Your Modal is re-rendering because your function passed as onButtonClick is redefined at every render.
So you have 2 options here:
1/ Keep your Modal inside your Component and use useMemo
import { useMemo } from 'react'
const Main = () => {
const [state, setState] = useState();
const modal = useMemo(() => (
<Modal onButtonClick={() => (
updateThroughApi().then(() => setState())
)}
/>
), [])
return (
<Component state={state}>
{modal}
</Component>
)
}
Or 2/ Move your Modal outside your component and use combination of memo and useCallback
import { memo, useCallback } from 'react'
const Main = () => {
const [state, setState] = useState();
const onButtonClick = useCallback(() => updateThroughApi().then(() => setState()), []);
return (
<Component state={state}>
<Modal onButtonClick={onButtonClick} />
</Component>
)
}
const Modal = memo(({onButtonClick}) => {
})
So in this case, at every render, memo will compare if all Modal props are === from previous render, which is now the case, because memoization of onButtonClick with useCallback, and so your Modal component will not re-render
https://reactjs.org/docs/hooks-reference.html#usememo
I recently started using ReactJs and I want to know what is the best practice of defining event handlers in React. This is how I've been using react event handlers:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const handleButtonClick = () => {
setCounter(counter + 1);
};
return (
<div className="App">
<button onClick={() => handleButtonClick()}>
Click Me To increase counter
</button>
<div>
<h4>Counter value is: </h4>
{counter}
</div>
</div>
);
}
I have also heard arguments against this logic. Some say it is better to define event handlers outside the definition of the component (App in our case). This way, it becomes clear, clean and concise, instead of creating a mess of multiple functions (event handlers or not) inside the component. For example:
import React, { useState } from "react";
import "./styles.css";
const handleButtonClick = (setCounter, counter) => () => {
setCounter(counter+1);
};
export default function App() {
const [counter, setCounter] = useState(0);
return (
<div className="App">
<button onClick={handleButtonClick(setCounter, counter)}>
Click Me To increase counter
</button>
<div>
<h4>Counter value is: </h4>
{counter}
</div>
</div>
);
}
CodeSandbox Link for second approach
I want to know which is the best practice of defining functions? Should event handlers be also defined globally above the function component(App Component in this case)?
You don't need to create an extra function inside onClick. Just don't call it. onClick method call when onClick trigger.
const handleButtonClick = () => {
setCounter(counter + 1);
}; // return function
<div onClick={handleButtonClick} />
// it will be call the handleButtonClick
// when onClick is trigger
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const handleButtonClick = () => {
setCounter(counter + 1);
};
return (
<div className="App">
<button onClick={handleButtonClick}>
Click Me To increase counter
</button>
<div>
<h4>Counter value is: </h4>
{counter}
</div>
</div>
);
}
Should event handlers be also defined globally above the function component?
NO.
Defining event handlers, outside the component, that modifies a component's state breaks cohesion.
An event handler, within a component, explains the different interactions of the component. Having a "wrapper" handler simply breaks the single level of abstraction.
My two cents.
Here is a Code Sandbox that contains a test simulating this issue. The test in this Code Sandbox fails as described in this question: https://codesandbox.io/s/react-jest-and-enzyme-testing-c7vng
I'm trying to test the value of an <input /> that gets updated inside a useEffect. This is the code from the Code Sandbox, which is a simplified version of something I'm trying to do in a project.
import React, { useEffect, useRef, useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
ref.current.value = "";
console.log(typeof ref.current.value);
}, [count]);
const ref = useRef(null);
const handleClick = () => {
setCount(count + 1);
console.log(count);
};
return (
<div>
<input ref={ref} type="text" />
<button onClick={handleClick}>click me</button>
</div>
);
};
export default App;
I use useRef to set the value of the <input />.
The useEffect gets called when the <button /> is clicked. The <button /> updates the useState count. useEffect is watching updates to count, and it gets called as a side-effect.
In the useEffect, I set ref.current.value to an empty string, and then I log the typeof this value to verify that it's a string.
In the test, I try to simulate this behavior:
describe("App", () => {
const wrapper = mount(<App />);
wrapper.find("input").props().value = "hello";
act(() =>
wrapper
.find("button")
.props()
.onClick()
);
console.log(wrapper.find("input").debug());
wrapper.update();
console.log(wrapper.find("input").debug());
expect(wrapper.find("input").length).toBe(1);
expect(wrapper.find("input").props().value).toBe("hello");
});
I set the value prop to 'hello'. I then invoke the <button /> onClick prop, effectively clicking it. I then call wrapper.update(), and I also debug() log the <input /> before and after update().
Before, update(), the <input /> has a value prop containing 'hello'. After the update(), the <input /> does not have a value prop. This causes the test to fail, saying that the <input /> value is undefined after the update.
Shouldn't the input's value be '' after the update?
Here is a list of issues with the current way:
<input ref={ref} type="text" /> is describes a React Element and has no value prop
Prop value should be controlled via state and not mutated directly
wrapper.find("input").props().value = "hello";
Setting value on a DOM Node isn't the same as setting a prop value. Using React means that you ceed DOM manipulation to it.
useRef allows for access to the underlying DOM Node when passed an initial value of null and this following line mutates DOM in spite of App state.
ref.current.value = "";
In certain scenarios, it's expedient to manipulate DOM in spite of the App state. The tests should then deal with the DOM Node and check changes to it.
describe("App", () => {
const wrapper = mount(<App />);
wrapper.find("input").getDOMNode().value = "hello";
act(() =>
wrapper
.find("button")
.props()
.onClick()
);
wrapper.update();
expect(wrapper.find("input").length).toBe(1);
expect(wrapper.find("input").getDOMNode().value).toBe("");
});
If you consider that your use case doesn't require this much of a direct control of the DOMNode.
The input element value prop can be controlled with state. For example,
const App = () => {
const [count, setCount] = useState(0);
const [value, setValue] = useState("hello");
useEffect(() => {
setValue("");
}, [count]);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<input type="text" value={value} />
<button onClick={handleClick}>click me</button>
</div>
);
};
export default App;