So recently I started a project for fun to mess around with NextJS.
I ran into a problem regarding my count state:
//Core
import React, { useState, useEffect } from 'react';
//Style
import styles from './form.module.scss';
export default function Form({ placeholder }) {
const [count, setCounter] = useState(0);
return (
<form>
<input
type='text'
placeholder={placeholder}
className={styles.error}
></input>
<button onClick={() => setCounter(count + 1)}>{count}</button>
</form>
);
}
This keeps refreshing my count state everytime I click the increment button. What is the correct way to do a increment on click, without rerendering the comoponent?
I found examples like these online:
https://codesandbox.io/s/react-hooks-counter-demo-kevxp?file=/src/index.js:460-465
How come my counter keeps resetting, and theirs is not?
You are inside a form.
Use preventDefault first in order for the form not to be submitted everytime.
<button
onClick={(e) => {
e.preventDefault();
setCounter(count + 1);
}}
>
{count}
</button>
See it in action here
You can also set the button's type to "button" to prevent form submission
<button type="button" onClick={() => setCounter(count + 1)}>{count}</button>
With a useRef you can keep the same data across re-renders
import React, { useState, useRef } from "react";
export const Form = () => {
const counterEl = useRef(0);
const [count, setCount] = useState(counterEl.current);
const increment = () => {
counterEl.current = counterEl.current + 1;
setCount(counterEl.current);
console.log(counterEl);
};
return (
<>
Count: <span>{count}</span>
<button onClick={increment}>+</button>
</>
);
};
I think this is because you wrap your button in form
by default when you press button in form submit trigger
and then whole page render so state reset
Related
I'm trying to build a table component and make one of its cells editable.
I need this cell to be clickable, and if clicked, an input component would replace the button, and it would get focused automatically so that users can decide the text of this cell.
Now in the first rendering, button would be rendered, which leads to the binding ref of Input failing.
Here is my simplified code:
import { Input, InputRef, Button } from 'antd'
import { useRef, useState, useEffect } from 'react'
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef<InputRef>(null)
useEffect(() => {
console.log(inputRef.current)
}, [inputRef, showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => {
setIsShowInput(true)
if (showInput) inputRef.current?.focus()
}}>Edit me</Button>}
</div>
);
}
How can I make the binding of ref takes effect in the first rendering, so when I click the button, Input would get focused.
Or is there any other way to achieve this?
The easiest way to achieve this is to watch the showInput value. If the value is true then call the focus method, otherwise do nothing as the Input component will be unmounted from the App.
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef(null)
useEffect(() => {
if (!showInput) return;
inputRef.current.focus()
}, [showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => setIsShowInput(true)}>Edit me</Button>}
</div>
);
}
This example is given in the react documentation to describe the use of useEffect.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Could this not be re-written without using useEffect as below?
Why is useEffect preferred?
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const handleChange = () => {
setCount(count + 1);
document.title = `You clicked ${count} times`;
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleChange}>
Click me
</button>
</div>
);
}
There are two issues here:
Calling a state setter won't reassign the stateful const variable in the current render - you need to wait until the next render for count to be updated, so
const handleChange = () => {
setCount(count + 1);
document.title = `You clicked ${count} times`;
}
won't work. Your second snippet will result in You clicked 0 times after a single click.
Even if that were possible, when there are multiple places where a state setter may be called, putting something that should happen afterwards after every single one isn't very maintainable - it'd be better to be able to put that code to run afterwards in only one place.
I'm super new to React and building my first ever app which is a url shortening app. Each shortened url has a button next to it whose text is set to 'copy' initially and once the user click on it the link is copied to the clipboard and the button text changes to 'copied'. Everything is working fine except if I have multiple shortened url's and I click on one of the buttons next to any particular url, it still only copies that url to clipboard but the button text changes to copied on all of them.
If anyone can please enlighten me how to single out those buttons individually that'll be of great help. I've tried using the id but maybe I'm not doing that correctly?
P.S - this is first time I'm posting on here so apologies upfront if I missed any crucial bits.
import {useState} from 'react'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid';
function Main() {
const [name, setName] = useState('')
const [list, setList] = useState(initialList);
const handleSubmit = (e) => {
e.preventDefault();
}
const handleAdd = async () => {
const res = await axios.get(`https://api.shrtco.de/v2/shorten?url=${name}`)
const {data: {result: {full_short_link: shortLink}}} = res
const newList = list.concat({name:shortLink, id: uuidv4()});
setList(newList);
setName('');
}
const [buttonText, setButtonText] = useState("Copy");
return (
<form onSubmit={handleSubmit}>
<input type="text"
value= {name}
onChange={(e) => setName(e.target.value)}
placeholder='Shorten a link here'
onClick = {()=> setButtonText('copy')}
/>
<button onClick = {handleAdd}>Shorten it!</button>
</form>
<ul>
{list.map((item, index) => (
<li key={item.id}>{item.name}<button
onClick = {() => { navigator.clipboard.writeText(item.name); setButtonText("Copied")}} >
{buttonText}
</button></li>))}
</ul>
export default Main``
It’s because you are using one state variable for all of your buttons, you need a variable to keep track of state for each individual button. You should refactor the code within your map function into its own component, and declare the buttonText state within that component. That way each button has its’ own state.
Eg (sorry for the capitalisations in my code):
MyButton.js
Const MyButton = ({item}) => {
const [buttonText, setButtonText] = useState(‘Copy’)
Return (
<li key={item.id}>{item.name}
<button
onClick = {() => {
navigator.clipboard.writeText(item.name);
setButtonText("Copied")}
}
>
{buttonText}
</button>
</li>
)
Export default MyButton
Form:
// ……
<ul>
{list.map((item, index) => <MyButton key={item.id} item={item} />)}
</ul>
The "Bad button" only works once, since it's not controlling the same "value" as the "Good button". What am I missing here?
const Test = () => {
const [value, setValue] = useState(0)
const [view, setView] = useState()
const add = () => setValue(value + 1)
const badButton = () => {
return (
<div>
<button onClick={add}>Bad button</button>
</div>
)
}
return (
<div>
{value}
<button onClick={add}>Good button</button>
{view}
<button onClick={() => setView(badButton)}>show bad button</button>
</div>
)
}
Thanks for replying, I'm going to use the flag method as suggested. But I would still like to know why the two buttons don't work the same way in this original case.
Check this sandbox
I feel it's the right way to handle such a usecase. Instead of storing component itself in state. I replaced it with a boolean. By default it will be false and badButton will be hidden, on click of showBadButton, i'm setting the view state true and bad button will come into picture. Actually Its a good buton now. Check it out.
I would use view as a flag to show/hide BadButton component, I have created a demo that showcase the following code snippet:
import React, { useState } from 'react';
import { render } from 'react-dom';
import './style.css';
const Test = () => {
const [value, setValue] = useState(0)
const [view, setView] = useState(false)
const add = () => setValue(value + 1)
const BadButton = () => {
return (
<button onClick={add}>Bad button</button>
)
}
return (
<>
{value}
<button onClick={add}>Good button</button>
{view ? BadButton() : null}
<button onClick={() => setView(!view)}>
{view ? 'hide' : 'show'} bad button
</button>
</>
)
}
render(<Test />, document.getElementById('root'));
Welcome to StackOverflow
After reading through the intro to hooks I have an immediate feeling that it has a performance problem with passing function props.
Consider the following class component, where the function reference is a bound function, so no re-renders happen because of it.
import React from 'react';
class Example extends React.Component {
state = { count: 0 }
onIncrementClicked = () => setState({ count: this.state.count + 1 })
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.onIncrementClicked}>
Click me
</button>
</div>
);
}
}
Now compare it to the hooks-version where we pass a new function on each render to the button. If an <Example /> component renders, there's no way of avoiding the re-rendering of it's <button /> child.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I know it's a small example, but consider a bigger app where many callbacks are passed around that depend on hooks. How could this be optimised?
How would I avoid re-rendering everything that takes a function prop, that depends on a hook?
You can use useCallback to ensure that event handler doesn't change between renders with the same count value:
const handleClick = useCallback(
() => {
setCount(count + 1)
},
[count],
);
For better optimisation you can store count value as an attribute of the button so you don't need access to this variable inside event handler:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
const handleClick = useCallback(
(e) => setCount(parseInt(e.target.getAttribute('data-count')) + 1),
[]
);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick} data-count={count}>
Click me
</button>
</div>
);
}
Also check https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render