简体   繁体   English

React 组件中状态更改后的意外行为

[英]Unexpected Behavior After State Change in React Component

    RenderImages = (): React.ReactElement => {
        let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
        console.log(selected)
        return(
            <div className="results_wrapper">
                {selected.map((r,i)=>{
                    let openState = (this.state.selectedImage==i)?true:false;
                    return(
                        <RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
                            
                            <div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
                                <img src={r.url} />
                            </div>
                        )} content={(closeIt)=>(
                            <div className="panel_wrapper">
                                <div className="panel_content">{r.content}</div>
                                {this.RenderPostLink(r.domain,r.parent)}
                                <div onClick={()=>{
                                    closeIt();
                                    this.setState({selectedImage:2})
                                    console.log('wtfff'+this.state.selectedImage)
                                }
                            }>Next</div>
                                <img src={r.url} />
                            </div>
                        )}/>
                    )
                })}
            </div>
        )
    }

When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function.当我更改“selectedImage”的状态时,我希望变量“openState”在我的 map() 函数中呈现不同的效果。 But it does not do anything.但它没有做任何事情。

Console.log shows that the state did successfully change. Console.log 显示状态确实已成功更改。

And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.更奇怪的是,如果我在 componentsDidMount() 中运行“this.setState({selectedImage:2})”,那么一切都完全按预期呈现。

Why is this not responding to my state change?为什么这不响应我的状态变化?

Update更新

I have tried setting openState in my component state variable, but this does not help either:我尝试在组件状态变量中设置 openState ,但这也无济于事:

   RenderImages = (): React.ReactElement => {
        let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
        console.log(selected)
        let html = selected.map((r,i)=>{
                    return(
                        <RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
                            
                            <div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
                                <img src={r.url} />
                            </div>
                        )} content={(closeIt)=>(
                            <div className="panel_wrapper">
                                <div className="panel_content">{r.content}</div>
                                {this.RenderPostLink(r.domain,r.parent)}
                                <div onClick={()=>{
                                    closeIt();
                                    let openState = this.state.openState.map(()=>false)
                                    let index = i+1
                                    openState[index] = true;
                                    this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
                                }
                            }>Next</div>
                                <img src={r.url} />
                            </div>
                        )}/>
                    )
                })
        return(
            <div className="results_wrapper">
                {html}
            </div>
        )
    }

https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx

To test, just hit enter at the search box.要进行测试,只需在搜索框中按 Enter。 Then click on 1 of 3 of the results.然后单击 3 个结果中的 1 个。 When you click 'Next', it should close the pane, and open the next one.当您单击“下一步”时,它应该关闭窗格,然后打开下一个窗格。 That is what I'm trying to accomplish here.这就是我要在这里完成的任务。

I believe it's because you set openState in your map function, after it has already run.我相信这是因为您在 map 函数中设置了 openState,它已经运行了。 I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.我知道您认为该函数应该重新渲染,然后循环将再次运行,但我认为您需要在渲染之外的函数中设置 openState。

The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render.问题是,即使您可以从作为类组件的成员的组件访问this.state ,也没有什么可以使组件重新渲染。 Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.在其他组件中制作组件是一种反模式,会产生意想不到的效果 - 正如您所见。

The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render() .这里的解决方案是将 RenderImages 完全移动到一个单独的组件中并通过 props 或上下文传递所需的数据,或者将其转换为普通函数并在父组件的render()中将其作为函数调用。

The latter would mean instead of <RenderImages/> , you'd do this.RenderImages() .后者意味着代替<RenderImages/> ,你会做this.RenderImages() And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages .而且由于它不再是一个组件而只是一个返回 JSX 的函数,我可能会将它重命名为renderImages

I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.我厌倦了一遍又一遍地看它,但无法理解为什么它不能以任何干净的方法工作。

That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.话虽如此,我能够通过“hack”使其工作,即在渲染完成后为selectedImage显式调用openIt方法。

RenderImages = (): React.ReactElement => {
  let selected = this.state.results.filter((x) =>
    this.state.selectedGroups.includes(x.domain)
  );

  return (
    <div className="results_wrapper">
      {selected.map((r, i) => {
        let openState = this.state.selectedImage === i ? true : false;
        return (
          <RenderPanel
            key={i}
            panelType={PanelType.medium}
            openState={openState}
            title={r.domain + ".TheCommonVein.net"}
            preview={(openIt) => {

              /* This is where I am making explicit call */
              if (openState) {
                setTimeout(() => openIt());
              }
              /* changes end */
    
              return (
                <div
                  className="result"
                  onClick={openIt}
                  style={{ boxShadow: theme.effects.elevation8 }}
                >
                  <img src={r.url} />
                </div>
              );
            }}
            content={(closeIt) => (
              <div className="panel_wrapper">
                <div className="panel_content">{r.content}</div>
                {this.RenderPostLink(r.domain, r.parent)}
                <div
                  onClick={() => {
                    closeIt();
                    this.setState({
                      selectedImage: i + 1
                    });
                  }}
                >
                  [Next>>]
                </div>
                <img src={r.url} />
              </div>
            )}
          />
        );
      })}
    </div>
  );
};

take a look at this codesandbox .看看这个 codeandbox

@Spitz was on the right path with his answer, though didn't follow through to the full solution. @Spitz 的答案是正确的,但没有遵循完整的解决方案。

The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.您遇到的问题是面板的useBoolean不会根据传递的openState值更新其状态。

If you add the following code to panel.tsx, then everything will work as you described:如果您将以下代码添加到 panel.tsx,那么一切都会如您所描述的那样工作:

    React.useEffect(()=>{
        if(openState){
            openPanel()
        }else{
            dismissPanel();
        }
    },[openState, openPanel,dismissPanel])

What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel .这是什么东西做的是建立在同步的效果isOpen状态在RenderPanelopenState是作为一个道具传递RenderPanel That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.这样,当面板在大部分情况下控制自己时,如果父级更改 openState,它将更新。

Working sandbox 工作沙箱

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM