简体   繁体   中英

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. But it does not do anything.

Console.log shows that the state did successfully change.

And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.

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:

   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

To test, just hit enter at the search box. Then click on 1 of 3 of the results. 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. 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.

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. 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() .

The latter would mean instead of <RenderImages/> , you'd do 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 .

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.

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 .

@Spitz was on the right path with his answer, though didn't follow through to the full solution.

The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.

If you add the following code to panel.tsx, then everything will work as you described:

    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 . That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.

Working sandbox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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