【问题标题】:React Warning: Cannot update during an existing state transition. Search componentReact 警告:在现有状态转换期间无法更新。搜索组件
【发布时间】:2020-07-15 14:21:29
【问题描述】:

我在 chrome devtools 控制台中出现警告:

Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
    in div (at Search.jsx:37)
    in Search (at pages/index.jsx:79)
    in main (created by Basic)
    in Basic (created by Context.Consumer)
    in Content (at pages/index.jsx:78)
    in section (created by Context.Consumer)
    in BasicLayout (created by Context.Consumer)
    ...

代码按预期工作。它是 Flexsearch 的 React 实现,是 Web 上最快且内存最灵活的全文搜索库。 但是这个警告让我很烦。

我在这方面做了很多工作,但没有找到合适的解决方案。

Search.jsx

/**
 * Vendor Import
 */
import React from 'react';
import _find from 'lodash/find';
import _map from 'lodash/map';
import _isEmpty from 'lodash/isEmpty';
import _isEqual from 'lodash/isEqual';
import NotFound from '../images/notfound.svg';
import { Col, Row } from 'antd';

/**
 * Component import
 */
import ProductList from '../components/ProductList';

/**
 * Utils import
 */
import { filterData } from '../utils/filterdata';
import ContextConsumer from '../utils/context';

/**
 * Style import
 */
import './search.css';

class Search extends React.Component {
  state = {
    query: '',
    results: this.props.groupedData,
  };

  render() {

    return (
      <div className={this.props.classNames}>
        <ContextConsumer>
          {({ data }) => {
            this.handleSearch(data.query);
          }}
        </ContextConsumer>

        <div className='search__list'>
          {!_isEmpty(this.state.results) ? (
            <ProductList products={this.state.results} />
          ) : (
            <Row>
              <Col span={24} className='no_results'>
               No results corresponding to "<b>{this.state.query}</b>"
              </Col>
              <Col xs={24} sm={12} md={8} lg={6} className='no_results'>
                <NotFound />
              </Col>
            </Row>
          )}
        </div>
      </div>
    );
  }

  /**
   * Handle search
   * @param {String} query 
   */
  handleSearch = (query) => {
    if (!_isEqual(this.state.query, query)) {
      const groupedData = this.props.groupedData;
      const results = this.getSearchResults(groupedData, query);
      this.setState({ results: results, query: query });
    }
  };

  /**
   * Get the data associated to the query
   * @param {Array} data
   * @param {String} query
   */
  getSearchResults(data, query) {
    const index = window.__FLEXSEARCH__.en.index;
    const store = window.__FLEXSEARCH__.en.store;

    if (!query || !index) {
      return data;
    } else {
      let resultingNodesID = [];
      Object.keys(index).forEach((idx) => {
        resultingNodesID.push(...index[idx].values.search(query));
      });
      resultingNodesID = Array.from(new Set(resultingNodesID));

      const resultingNodes = store
        .filter((node) => (resultingNodesID.includes(node.id) ? node : null))
        .map((node) => node.node);

      const resultingGroupedData = [];
      _map(resultingNodes, (node) => {
        resultingGroupedData.push(_find(data, { ref: node.ref }));
      });

      return resultingGroupedData;
    }
  }

  /**
   * Invoked immediately after updating occurs.
   * @param prevProps
   */
  componentDidUpdate(prevProps) {
    const { selectedMenu, groupedData } = this.props;

    if (!_isEqual(prevProps.selectedMenu, selectedMenu)) {
      const filteredData = filterData(groupedData, selectedMenu);
      const results = filteredData;
      this.setState({ results: results });
    }
  }
}

export default Search;

上下文提供者组件:

/**
 * Vendor Import
 */
import React from 'react';

const defaultContextValue = {
  data: {
    // set your initial data shape here
    query: '',
  },
  set: () => {},
};

const { Provider, Consumer } = React.createContext(defaultContextValue);

class ContextProviderComponent extends React.Component {
  constructor() {
    super();

    this.setData = this.setData.bind(this);
    this.state = {
      ...defaultContextValue,
      set: this.setData,
    };
  }

  setData(newData) {
    this.setState((state) => ({
      data: {
        ...state.data,
        ...newData,
      },
    }));
  }

  render() {
    return <Provider value={this.state}>{this.props.children}</Provider>;
  }
}

export { Consumer as default, ContextProviderComponent };

我做错了什么?

ps:如果您看到一些改进或无用的代码,我会全力以赴!

【问题讨论】:

    标签: javascript reactjs search jsx


    【解决方案1】:

    我找到了解决办法。

    @Scotty Jamison 关于问题的根源是正确的。他的回答帮助我重写了我的代码。

    搜索.jsx

    /**
     * Vendor Import
     */
    import React from 'react';
    import _isEmpty from 'lodash/isEmpty';
    import _isEqual from 'lodash/isEqual';
    import NotFound from '../images/notfound.svg';
    import { Col, Row } from 'antd';
    
    /**
     * Component import
     */
    import ProductList from './ProductList';
    
    /**
     * Utils import
     */
    import { filterData } from '../utils/filterdata';
    import { SearchContext } from '../utils/searchcontext';
    import { getSearchResults } from '../utils/getsearchresults';
    
    /**
     * Style import
     */
    import './search.css';
    
    class Search extends React.Component {
      constructor(props) {
        super(props);
        this.state = { results: this.props.groupedData, query: '' };
      }
    
      previousContext = '';
    
      /**
       * Invoked immediately after a component is mounted.
       */
      componentDidMount() {
        //console.log('--- componentDidMount ---');
        this.previousContext = this.context;
      }
    
      /**
       * Invoked immediately after updating occurs.
       * @param prevProps
       */
      componentDidUpdate(prevProps) {
        //console.log('--- componentDidUpdate ---');
    
        const { selectedMenu, groupedData } = this.props;
    
        if (!_isEqual(prevProps.selectedMenu, selectedMenu)) {
          this.setState({ results: filterData(groupedData, selectedMenu) });
        }
    
        if (!_isEqual(this.previousContext, this.context)) {
          let searchQuery = this.context;
          this.setState({ results: getSearchResults(groupedData, searchQuery) });
        }
    
        this.previousContext = this.context;
      }
    
      render() {
        let searchQuery = this.context;
        return (
          <div className={this.props.classNames}>
            <div className='search__list'>
              {!_isEmpty(this.state.results) ? (
                <ProductList products={this.state.results} />
              ) : (
                <Row>
                  <Col span={24} className='no_results'>
                    Pas de résultats correspondants à "<b>{searchQuery}</b>"
                  </Col>
                  <Col xs={24} sm={12} md={8} lg={6} className='no_results'>
                    <NotFound />
                  </Col>
                </Row>
              )}
            </div>
          </div>
        );
      }
    }
    
    Search.contextType = SearchContext;
    
    export default Search;
    

    getseatchresults.js

    /**
     * Vendor Import
     */
    import _find from 'lodash/find';
    import _map from 'lodash/map';
    
    /**
     * Get the results from search
     * @param {Array} data
     * @param {String} query
     */
    export const getSearchResults = (data, query) => {
      const index = window.__FLEXSEARCH__.en.index;
      const store = window.__FLEXSEARCH__.en.store;
    
      if (!query || !index) {
        return data;
      } else {
        let resultingNodesID = [];
        Object.keys(index).forEach((idx) => {
          resultingNodesID.push(...index[idx].values.search(query));
        });
        resultingNodesID = Array.from(new Set(resultingNodesID));
    
        const resultingNodes = store
          .filter((node) => (resultingNodesID.includes(node.id) ? node : null))
          .map((node) => node.node);
    
        const resultingGroupedData = [];
        _map(resultingNodes, (node) => {
          resultingGroupedData.push(_find(data, { ref: node.ref }));
        });
    
        return resultingGroupedData;
      }
    };
    

    searchcontext.js

    /**
     * Vendor Import
     */
    import React from 'react';
    
    /**
     * This context is for the Search query. It provides a query from the search bar in MyLayout.jsx to the Search.jsx component.
     * Due to the impossibility to pass props from the Layout to other components, a context has to be used.
     */
    export const SearchContext = React.createContext('');
    

    这是我所做的:
    前一个上下文组件不是我的。它是来自Gatsby flexsearch plugin integration 的通用样板。我不明白代码的意图。所以我检查了 React Doc 并阅读了all the context section。然后我简化了代码,将搜索逻辑导出到Search.jsx 组件之外,并简化了最后一个。

    【讨论】:

      【解决方案2】:

      当它要求你的渲染函数是纯的时,它希望它不更新状态。它也不应该调用任何更新状态的东西。

      在 search.jsx 中,您在渲染中调用 this.handleSearch()。 handleSearch() 调用 this.setState()。您要么需要在通过上下文提供程序传入数据之前处理此搜索逻辑。 (因此,将搜索处理逻辑移动到 ContextProviderComponent 并将搜索结果放入上下文中),或者您需要在渲染函数之外监听上下文变化。 This 回答提供了多种方法来做到这一点。

      至于代码质量,在我快速查看您的代码时,我没有看到任何明显的危险信号,干得好!你似乎掌握了 React 的要领。

      【讨论】:

      • 感谢您的回答!我发布了我的新代码。现在它就像一个魅力,我借此机会深入研究了关于 React Context 的文档。
      • 很高兴你明白了!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-20
      • 1970-01-01
      • 2017-05-16
      • 2016-09-20
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      相关资源
      最近更新 更多