为什么这个状态在它被改变后仍然只停留在这个函数中? – NextJS 13.3.0 / React

huangapple go评论73阅读模式
英文:

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 &#39;react&#39;
function Test() 
{
const [checkboxState, setCheckboxState] = useState(true);
const [someCondition, setSomeCondition] = useState(false);
const [thingsToRender, setThingsTorRender] = useState(&lt;h1&gt;Something&lt;/h1&gt;);
const dialogRef = useRef(null);
const checkboxRef = useRef(null);
const handleCheckboxChange = () =&gt;
{
const checkbox = checkboxRef.current;
if(checkbox.checked)
{
setCheckboxState(false)
}
else
{
setCheckboxState(true);
}
}
const handleRevealButtonClick = () =&gt;
{
if(checkboxState)
{
const dialog = dialogRef.current;
dialog.showModal();
}
}
const handleCloseButtonClick = () =&gt;
{
const dialog = dialogRef.current;
dialog.close();
}
useEffect(() =&gt;
{
// Problem seems to be here
setThingsTorRender(&lt;button onClick={handleRevealButtonClick}&gt;Reveal modal&lt;/button&gt;)
}, [])
return (
&lt;div&gt;
&lt;dialog ref={dialogRef}&gt;
&lt;div&gt;
&lt;input type=&quot;checkbox&quot; ref={checkboxRef} onChange={handleCheckboxChange}/&gt; 
&lt;label&gt;Not reveal again?&lt;/label&gt;
&lt;/div&gt;
&lt;button onClick={handleCloseButtonClick}&gt;Close&lt;/button&gt;
&lt;/dialog&gt;
{someCondition === true ? &lt;h1&gt;Some stuff&lt;/h1&gt; : thingsToRender}
&lt;/div&gt;)
}
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 = () =&gt;
{
// query from Firebase
const grid = query.map((document, index) =&gt; 
&lt;React.Fragment key={index}&gt;
&lt;div className=&#39;grid-item&#39;&gt;{document.data}&lt;/div&gt;
&lt;div className=&#39;grid-item&#39;&gt;{document.data}&lt;/div&gt;
&lt;div className=&#39;grid-item&#39;&gt;
&lt;button onClick={handleRevealButtonClick}&gt;Reveal modal&lt;/button&gt;
&lt;/div&gt;
&lt;/React.Fragment&gt;
)
setThingsTorRender(grid)
}
useEffect(() =&gt;
{
renderGrid();
}, [])
return (
&lt;div&gt;
{/* dialog */}
{someCondition === true ? &lt;h1&gt;Some stuff&lt;/h1&gt; : &lt;div className=&#39;grid-container&#39;&gt; {thingsToRender} &lt;/div&gt;}
&lt;/div&gt;)

答案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:

&lt;div className=&#39;grid-container&#39;&gt;{query.map((doc, index) =&gt; 
  &lt;React.Fragment key={index}&gt;
    &lt;div className=&#39;grid-item&#39;&gt;{doc.data}&lt;/div&gt;
    &lt;div className=&#39;grid-item&#39;&gt;{doc.data}&lt;/div&gt;
    &lt;div className=&#39;grid-item&#39;&gt;
      &lt;button onClick={handleRevealButtonClick}&gt;Reveal modal&lt;/button&gt;
    &lt;/div&gt;
  &lt;/React.Fragment&gt;)
}&lt;/div&gt;

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 &lt;button&gt; 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.

huangapple
  • 本文由 发表于 2023年6月25日 19:02:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76550080.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定