Неожиданное поведение после изменения состояния в компоненте React

#reactjs #typescript

#reactjs #typescript

Вопрос:

     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>
        )
    }
  

Когда я изменяю состояние ‘selectedImage’, я ожидаю, что переменная ‘openState’ будет отображаться по-другому в моей функции map (). Но это ничего не делает.

Консоль.журнал показывает, что состояние успешно изменилось.

И что еще более странно, если я запускаю «this.setState({selectedImage:2})» в componentsDidMount() , тогда все отображается точно так, как ожидалось.

Почему это не реагирует на мое изменение состояния?

Обновить

Я попытался установить 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

Для проверки просто нажмите enter в поле поиска. Затем нажмите на 1 из 3 результатов. Когда вы нажимаете «Далее», он должен закрыть панель и открыть следующую. Это то, чего я пытаюсь достичь здесь.

Комментарии:

1. Действительно ли этот компонент является членом компонента класса?

2. Да, это так. Я просто не опубликовал остальное. Если вам нужен код, я могу.

3. Я не буду пытаться его отлаживать, но регистрация нового состояния после вызова setState не будет работать так, как вы хотите. setState является асинхронным, передайте ему обратный вызов в качестве 2-го параметра.

4. @RodrigoAmaral Я знаю об этом, но регистрировать это не проблема, я уже доказал себе, что состояние устанавливается.

5. Я бы не стал голосовать без объяснения причин. Тем не менее, я предлагаю вам отредактировать заголовок на что-то вроде «Неожиданное поведение после изменения состояния в компоненте React», также добавьте typescript к тегам. Приветствия.

Ответ №1:

@Spitz был на правильном пути со своим ответом, хотя и не довел его до полного решения.

Проблема, с которой вы столкнулись, заключается в том, что панель useBoolean не обновляет свое состояние на основе openState переданного значения.

Если вы добавите следующий код в panel.tsx, то все будет работать так, как вы описали:

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

Что это делает, так это настраивает эффект для синхронизации isOpen состояния в RenderPanel с openState , который передается в качестве реквизита для RenderPanel . Таким образом, хотя панель по большей части контролирует себя, если родительский элемент изменит openState, он обновится.

Рабочая изолированная среда

Комментарии:

1. Почему useBoolean не обновляет свое состояние? Это потому, что он не используется внутри компонента или это просто его поведение? СПАСИБО за ваш блеск

2. Это просто его поведение. Просматривая их документы, вы можете видеть, что параметр функции — это просто initialState : learn.microsoft.com/en-us/javascript/api /…

Ответ №2:

Я полагаю, это потому, что вы установили openState в своей функции map после того, как она уже запущена. Я понимаю, что вы считаете, что функция должна быть перезаписана, а затем цикл будет выполняться еще раз, но я думаю, вам нужно установить openState в функции вне рендеринга.

Комментарии:

1. openState зависит от «i», значения перечисления из функции map. Я не уверен, смогу ли я сделать это вне его. Кроме того, почему он ведет себя правильно, когда я устанавливаю состояние в componentsDidMount()?

2. Можете ли вы добавить openState к состоянию класса, а затем вместо этого установить setState({openState:true})? Вместо того, чтобы делать это в троичном, сделайте это в функции onClick .

Ответ №3:

Проблема в том, что, хотя вы можете получить доступ this.state из компонента, который является членом компонента класса, нет ничего, что могло бы заставить компонент повторно отображать. Создание компонентов внутри других компонентов является антишаблоном и приводит к неожиданным эффектам — как вы видели.

Решение здесь состоит в том, чтобы либо полностью переместить RenderImages в отдельный компонент и передать необходимые данные через props или context, либо превратить его в обычную функцию и вызвать ее как функцию в родительском компоненте render() .

Последнее означало бы вместо <RenderImages/> , вы бы сделали this.RenderImages() . А также, поскольку это больше не компонент, а просто функция, которая возвращает JSX, я бы, вероятно, переименовал ее в renderImages .

Комментарии:

1. В настоящее время я уже вызываю его как обычную функцию в своей функции рендеринга. Я называю это так: const images = this . RenderImages();

2. @Raven я вижу. Попробуйте указать <RenderPanel> key .

3. извините, я все еще новичок в этом — что такое ключ и как мне передать его RenderPanel?

4. Смотрите эту часть документации React: reactjs.org/docs/lists-and-keys.html#keys

5. Спасибо. Я только что попробовал это: <RenderPanel key={i} ... где i — перечисленное значение из map. К сожалению, он ведет себя так же.

Ответ №4:

Я устал смотреть на это снова и снова, но не мог понять, почему это не работает с каким-либо чистым подходом.

При этом я смог заставить его работать с помощью «взлома», то есть явного вызова openIt метода selectedImage после завершения рендеринга.

 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>
  );
};
  

взгляните на этот codesandbox.