英文:
Why this state is stuck only in this function even after it gets changed? - NextJS 13.3.0 / React
问题
以下是您要翻译的内容:
"I have a button to show a modal creating using a dialog
html element, this button is stored in a state (thingsToRender
) in consequence to a condition (in this case, I simulated the condition in a useEffect
hook), and then if someCondition
is true, the contents in thingsToRender
are rendered. The problems is that checkboxState
seems always to be true
in handleRevealButtonClick
even if its actual value is changed to false
in handleCheckboxChange
after the checkbox that asks if the modal must not be shown again is checked.
Seems that the problems is in storing the button
with the function handleRevealButtonClick
in the state thingsToRender
, but I didn't find a solution that satisfies my requirements, since I need that button to be rendered that way because it is contained in a grid item in the real example. So how can I make that button to not get stuck in the previous state of checkboxState
?
Here's the minimal reproducible example:
import React, { useEffect, useRef, useState } from 'react'
function Test()
{
const [checkboxState, setCheckboxState] = useState(true);
const [someCondition, setSomeCondition] = useState(false);
const [thingsToRender, setThingsTorRender] = useState(<h1>Something</h1>);
const dialogRef = useRef(null);
const checkboxRef = useRef(null);
const handleCheckboxChange = () =>
{
const checkbox = checkboxRef.current;
if(checkbox.checked)
{
setCheckboxState(false)
}
else
{
setCheckboxState(true);
}
}
const handleRevealButtonClick = () =>
{
if(checkboxState)
{
const dialog = dialogRef.current;
dialog.showModal();
}
}
const handleCloseButtonClick = () =>
{
const dialog = dialogRef.current;
dialog.close();
}
useEffect(() =>
{
// Problem seems to be here
setThingsTorRender(<button onClick={handleRevealButtonClick}>Reveal modal</button>)
}, [])
return (
<div>
<dialog ref={dialogRef}>
<div>
<input type="checkbox" ref={checkboxRef} onChange={handleCheckboxChange}/>
<label>Not reveal again?</label>
</div>
<button onClick={handleCloseButtonClick}>Close</button>
</dialog>
{someCondition === true ? <h1>Some stuff</h1> : thingsToRender}
</div>)
}
EDIT: In order to clarify why the button needs to be on a state, I'll show this example closer to the real example (notice that thingsToRender
contain elements of a grid that are defined from firebase documents):
const renderGrid = () =>
{
// query from Firebase
const grid = query.map((document, index) =>
<React.Fragment key={index}>
<div className='grid-item'>{document.data}</div>
<div className='grid-item'>{document.data}</div>
<div className='grid-item'>
<button onClick={handleRevealButtonClick}>Reveal modal</button>
</div>
</React.Fragment>
)
setThingsTorRender(grid)
}
useEffect(() =>
{
renderGrid();
}, [])
return (
<div>
{/* dialog */}
{someCondition === true ? <h1>Some stuff</h1> : <div className='grid-container'> {thingsToRender} </div>}
</div>)
}
英文:
I have a button to show a modal creating using a dialog
html element, this button is stored in a state (thingsToRender
) in consquence to a condition (in this case, I simulated the condition in a useEffect
hook), and then if someCondition
is true, the contents in thingsToRender
are rendered. The problems is that checkboxState
seems always to be true
in handleRevealButtonClick
even if its actual value is changed to false
in handleCheckboxChange
after the checkbox that asks if the modal must not be shown again is checked.
Seems that the problems is in storing the button
with the function handleRevealButtonClick
in the state thingsToRender
, but I didn't find a solution that satifies my requirements, since I need that button to be rendered that way beacuse is contained in a grid item in the real example. So how can I make that button to not get stucked in the previous state of checkboxState
?
Here's the minimal reproducible example:
import React, { useEffect, useRef, useState } from 'react'
function Test()
{
const [checkboxState, setCheckboxState] = useState(true);
const [someCondition, setSomeCondition] = useState(false);
const [thingsToRender, setThingsTorRender] = useState(<h1>Something</h1>);
const dialogRef = useRef(null);
const checkboxRef = useRef(null);
const handleCheckboxChange = () =>
{
const checkbox = checkboxRef.current;
if(checkbox.checked)
{
setCheckboxState(false)
}
else
{
setCheckboxState(true);
}
}
const handleRevealButtonClick = () =>
{
if(checkboxState)
{
const dialog = dialogRef.current;
dialog.showModal();
}
}
const handleCloseButtonClick = () =>
{
const dialog = dialogRef.current;
dialog.close();
}
useEffect(() =>
{
// Problem seems to be here
setThingsTorRender(<button onClick={handleRevealButtonClick}>Reveal modal</button>)
}, [])
return (
<div>
<dialog ref={dialogRef}>
<div>
<input type="checkbox" ref={checkboxRef} onChange={handleCheckboxChange}/>
<label>Not reveal again?</label>
</div>
<button onClick={handleCloseButtonClick}>Close</button>
</dialog>
{someCondition === true ? <h1>Some stuff</h1> : thingsToRender}
</div>)
}
export default Test;
EDIT: In order to clarify why the button needs to be on a state, I'll show this example closer to the real example (notice that thingsToRender
contain elements of a grid that are defined from firebase documents):
const renderGrid = () =>
{
// query from Firebase
const grid = query.map((document, index) =>
<React.Fragment key={index}>
<div className='grid-item'>{document.data}</div>
<div className='grid-item'>{document.data}</div>
<div className='grid-item'>
<button onClick={handleRevealButtonClick}>Reveal modal</button>
</div>
</React.Fragment>
)
setThingsTorRender(grid)
}
useEffect(() =>
{
renderGrid();
}, [])
return (
<div>
{/* dialog */}
{someCondition === true ? <h1>Some stuff</h1> : <div className='grid-container'> {thingsToRender} </div>}
</div>)
答案1
得分: 1
不要在状态中存储JSX(通常不需要这样做)。相反,将你的JSX放入return
中,以避免将其放入状态,然后使用状态来控制query
,以便在更改时重新渲染你的网格:
<div className='grid-container'>{query.map((doc, index) =>
<React.Fragment key={index}>
<div className='grid-item'>{doc.data}</div>
<div className='grid-item'>{doc.data}</div>
<div className='grid-item'>
<button onClick={handleRevealButtonClick}>Reveal modal</button>
</div>
</React.Fragment>)
}</div>
如果你愿意,你可以将上述内容放入自己的Grid
组件中,以使其更易于阅读,并将query
作为props传递给它。
要理解为什么你当前的方法不起作用,首先需要清楚当你的组件重新渲染时会发生什么。每次重新渲染时,都会再次执行你的Test
组件,创建一个新的作用域,在这个作用域内,你的Test
组件中定义的状态变量、函数等都会被重新创建。这意味着在组件的不同渲染中有多个不同版本的handleRevealButtonClick
回调函数。每个版本的handleRevealButtonClick
只知道它所创建的组件渲染中的状态值,该handleRevealButtonClick
是在首次渲染中创建的。
你的问题是,你在useEffect()
内设置的<button>
只会在组件的初始挂载时创建一次(由于空的依赖数组)。这意味着onClick
回调引用了在第一次渲染时创建的初始handleRevealButtonClick
,它只知道初始渲染时的checkbox
状态的值,而不知道它可能被设置为的新值。
英文:
Don't store JSX in state (there's usually no need to). Instead, put your JSX into your return
to avoid putting it into state, and then use state to control query
so that it rerenders your grid when changed:
<div className='grid-container'>{query.map((doc, index) =>
<React.Fragment key={index}>
<div className='grid-item'>{doc.data}</div>
<div className='grid-item'>{doc.data}</div>
<div className='grid-item'>
<button onClick={handleRevealButtonClick}>Reveal modal</button>
</div>
</React.Fragment>)
}</div>
If you want, you can put the above into its own Grid
component to make this easier to read as pass query
as props to it.
To understand why your current approach doesn't work you first need to be clear about what happens when your component rerenders. Upon each rerender, your Test
component is executed again, creating a new scope where your state variables, functions, etc. defined within your Test
component are recreated again. This means that there are multiple "versions" of the handleRevealButtonClick
callback function across different renders of your component. Each version of handleRevealButtonClick
only has knowledge of the state values for that particular render of your component that the handleRevealButtonClick
was created in.
Your problem is that the <button>
you set inside of your useEffect()
is created once, on the initial mount of your component (due to the empty dependency array). That means that the onClick
callback that you set of handleRevealButtonClick
refers to the initial handleRevealButtonClick
created on the first render which only knows about the value for the checkbox
state from the initial render of your component and not the new value it may be set to.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论