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.
Related
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.
I am updating my theme in my App per useState. This is passed to Topbar-Component per prop. console.log() gets triggered every time it changes. From Topbar theme is passed into a link to AboutMe-Copmponent as state, which works, but when i now change the state of theme it only updates in Topbar. I even tried Useeffect. Only when I refresh the site the change is noticed. I read hours about this but I cant solve it somehow.
AppComponent (not all code just the necessary):
function App() {
const [theme, setTheme] = useState('dark')
return (
<Topbar theme={theme}></Topbar>
<ToggleButton variant='light' onClick={() => setTheme('light')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('dark')}>Dark</ToggleButton>
TopbarComponent:
export default function Topbar({theme}) {
console.log('Topbar',theme)
React.useEffect(()=>{
console.log('changed')
},[theme])
Output when I press the buttons:
Topbar light
changed
Topbar dark
changed
AboutMeComponent:
export default function AboutMe() {
const location = useLocation()
console.log(location.state)
React.useEffect(() => {
console.log('About-Me',location.state)
},[location])
Initial output:
dark
About-Me dark
When I now press the other Button I only get the Topbar Output
Only when refreshing I get the AboutMe Outputs again.
PS
The theme is changed anyway from dark to light but i need this state to change fonts etc.
I would suggest sticking with documentation's recommendation which is to use useContext for very this example of setting theme using context.
Check out: https://beta.reactjs.org/apis/react/useContext
Usage : Passing data deeply into the tree
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
useContext returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.
To pass context to a Button, wrap it or one of its parent components into the corresponding context provider:
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls useContext(ThemeContext), it will receive "dark" as the value.
I have it working now with the useContext hook. Thank you i somehow forgot about it.
App:
export const ThemeContext = React.createContext()
function App() {
const [theme, setTheme] = useState('black')
console.log(theme)
return (
<ThemeContext.Provider value={{backgroundColor:theme}}>
<BrowserRouter>
<div className='App' id={theme}>
<Topbar/>
<div className="position-absolute top-0 start-0">
<ToggleButton variant='light' onClick={() => setTheme('white')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('black')}>Dark</ToggleButton>
</div>
Topbar:
export default function Topbar() {
const {user,logout} = UserAuth()
const [error, setError] = useState('')
const navigate = useNavigate()
const style = useContext(ThemeContext)
console.log(style)
AboutMe:
export default function AboutMe() {
const style = useContext(ThemeContext)
console.log(style)
return (
<>
<div className='d-flex' style={style}>
I had to move my Routing from Index.js to App.js because it had to be wrapped in the Context provider, but now my theme gets passed into every single component.
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.
Why this does not work ?
import React from 'react';
function Room() {
let check = null;
const ibegyouwork = () => {
check = <button>New button</button>;
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
And this works fine ?
import React from 'react';
function Room() {
let check = null;
return (
<div>
<button>No need for this button because in this case the second button is auto-displayed</button>
{check}
</div>
);
}
export default Room;
Basically I try to render a component based on a condition. This is a very basic example. But what I have is very similar. If you wonder why I need to update the check variable inside that function is because in my example I have a callback function there where I receive an ID which I need to use in that new component.
The example that I provided to you is basically a button and I want to show another one when I press on this one.
I am new to React and despite I searched in the past 2 hours for a solution I couldn't find anything to address this issue.
Any tips are highly appreciated !
Your component has no idea that something has changed when you click the button. You will need to use state in order to inform React that a rerender is required:
import React, {useState} from 'react'
function Room() {
const [check, setCheck] = useState(null);
const ibegyouwork = () => {
setCheck(<button>New button</button>);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
When you call setCheck, React basically decides that a rerender is required, and updates the view.
The latter is working because there are no changes to the check value that should appear on the DOM.
If check changes should impact and trigger the React render function, you would want to use a state for show/hide condition.
import React from 'react';
const Check = () => <button>New button</button>;
function Room() {
const [show, setShow] = React.useState(false);
const ibegyouwork = () => {
setShow(true);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{show && <Check />}
</div>
);
}
export default Room;