【问题标题】:React: setting a state Hook causes error - Too many re-renders. React limits the number of renders to prevent an infinite loopReact:设置状态 Hook 会导致错误 - 重新渲染过多。 React 限制渲染次数以防止无限循环
【发布时间】:2020-06-06 01:42:43
【问题描述】:

在 React 中,我创建了一个组件来允许用户提交一本书以添加到书籍列表中。该站点在视觉上看起来像这样(AddBook 组件用红色框起来):

我将在下面进一步分享我的完整代码,但首先我只需要解释问题。在AddBook 组件中,“选择作者”下拉菜单(您在上面的屏幕截图中看到)使用<select> <option> 元素,它允许用户在提交表单以添加书籍之前从作者列表中进行选择.作者列表是从 GraphQL API 调用中获取的。

sn-p 代码:


function displayAuthors() {
        if (loading) return (<option disabled>'Loading authors...'</option>);
        if (error) return (`Error: ${error.message}`);
        return data.authors.map((author) => {
            return (
                <option key={author.id} value={author.id}>{author.name}</option>
            );
        });
    }

return (
        <form id="add-book" onSubmit={submitBook}>
            <h1>Add a new book to Wiki Books</h1>
            <div className="field">
                <label>Book name: </label>
                <input type="text" onChange={handleChange} name="name" required value={bookEntry.name}/>
            </div>

            <div className="field">
                <label>Genre: </label>
                <input type="text" onChange={handleChange} name="genre" required value={bookEntry.genre}/>
            </div>

            <div className="field">
                <label>Author (select author): </label>
                <select onChange={handleChange} name="authorId">
                    {displayAuthors()}
                </select>
            </div>

            <button type="submit">+</button>
        </form>

我遇到的障碍是确保下拉菜单 &lt;select&gt; 元素是必需的,因此它确保在提交图书信息之前需要选择作者。现在,通过论坛搜索后,我得出结论,React 似乎没有为&lt;select&gt; REQUIRED 验证实现本机解决方案。取而代之的是复杂的黑客解决方案作为解决方法。

为了缓解这个问题,我只是删除了第一个 &lt;option&gt; Select author &lt;/option&gt;(您在共享的代码中看不到它,因为我已经删除了它)。因此,默认情况下,下拉菜单设置在列表中的第一作者,在页面的初始呈现上。从而强制用户选择默认选择的作者。当然,他们总是可以更改选项以选择不同的作者。但关键是默认情况下已经强制执行作者选择。

现在我遇到这种方法的下一个问题是 - 在页面的初始呈现时,即使默认情况下已经在下拉列表中选择了作者,该作者的相应 &lt;option&gt; 元素,其值为 @987654333 @ 在组件的初始渲染时没有被 React 检测到(来自 option 元素的这个 authorId 值是图书提交所必需的,调用 API 将图书信息提交到数据库)。 您必须首先更改&lt;select&gt; 元素中onChange 属性事件侦听器的菜单选项,以检测所选&lt;option&gt;authorId)的值。这意味着我确保始终选择作者(即使在初始页面呈现时)的解决方案现在毫无意义,因为 React 不会获取初始选定作者的 authorID 值。

<option key={author.id} value={author.id}>{author.name}</option> // value={author.id} doesn't get detected in initial render of page, unless menu option is changed for `onChange` to detect value={authorId}

.

为了解决这个问题,我的解决方案是创建一个状态,该状态将设置为从 API 调用获取的作者列表中的第一作者。所以应该是Patrick Rothfuss - 下拉菜单中默认选择的作者。所以Patrick RothfussauthorId设置为这个状态。如果用户在提交表单时根本没有更改下拉菜单,那么这个状态可以用于书籍提交。

const [firstAuthorId, setFirstAuthorId] = useState("");

function displayAuthors() {
        if (loading) return (<option disabled>'Loading authors...'</option>);
        if (error) return (`Error: ${error.message}`);
        return data.authors.map((author, index) => {
            if (index === 0) {
                setFirstAuthorId(author.id);
            }
            return (
                <option key={author.id} value={author.id}>{author.name}</option>
            );
        });
    }

现在我面临的问题是,当我将状态设置为第一作者 ID 时,出现以下错误:

事实上,从解决问题来看,问题的原因似乎与特定的状态设置器setFirstAuthorId有关。无论我将它设置为什么,以及从我在组件中的哪个位置设置它,它都会引发屏幕截图中显示的错误。

setFirstAuthorId("anything") // will throw an error

因此,作为一种解决方法(这并不理想),我创建了一个条件(三元表达式),它会在图书提交时(bookEntry 对象状态提交给 API)检查是否没有 authorId ,然后将 bookEntry 状态的 authorId 属性设置为第一作者 (Patrick Rothfuss) 的硬编码 authorId。理想情况下,firstAuthorId 状态应该设置为此而不是硬编码的 ID。

function submitBook(event) {
        const filledAuthorId = bookEntry.authorId? bookEntry.authorId : "5ed44e015ecb7c42a0bf824d";
        addBook({ variables: { 
            name: bookEntry.name,
            genre: bookEntry.genre,
            authorId: filledAuthorId
            }, 
            refetchQueries: [{ query: GET_ALL_BOOKS_QUERY }]
        })

完整代码:

import React, { useState } from 'react';
import { useQuery, useMutation } from '@apollo/react-hooks';
import { GET_ALL_AUTHORS_QUERY, ADD_BOOK_MUTATION, GET_ALL_BOOKS_QUERY } from '../queries/queries';

function AddBook() {
    const { loading, error, data } = useQuery(GET_ALL_AUTHORS_QUERY);
    // to call more than one query using the useQuery, call useQuery hook but give alias names to loading, error, data to avoid queries overwriting each other's returned fields
    // const { loading: loadingAddBook, error: errorAddBook, data: dataAddBook} = useQuery(A_SECOND_QUERY)
    const [ addBook ] = useMutation(ADD_BOOK_MUTATION);

    const [bookEntry, setBookEntry] = useState({
        name: "",
        genre: "",
        authorId: ""
    });

    const [firstAuthorId, setFirstAuthorId] = useState("");

    function handleChange(event) {
        const { name, value } = event.target;
        console.log(`handleChange event: ${event.target.value}`);

        setBookEntry(preValue => {
            return {
                ...preValue,
                [name]: value
            }
        });
    }

    function submitBook(event) {
        const filledAuthorId = bookEntry.authorId? bookEntry.authorId : "5ed44e015ecb7c42a0bf824d";
        addBook({ variables: { 
            name: bookEntry.name,
            genre: bookEntry.genre,
            authorId: filledAuthorId
            }, 
            refetchQueries: [{ query: GET_ALL_BOOKS_QUERY }]
        })
        .then(data => {
            console.log(`Mutation executed: ${JSON.stringify(data)}`);
            setBookEntry(prevValue => ({
                ...prevValue,
                name: "",
                genre: ""
            }));
        })
        .catch(err => console.log(err));
        event.preventDefault();
    } 

    function displayAuthors() {
        if (loading) return (<option disabled>'Loading authors...'</option>);
        if (error) return (`Error: ${error.message}`);
        return data.authors.map((author, index) => {
            if (index === 0) {
                setFirstAuthorId(author.id);
            }
            return (
                <option key={author.id} value={author.id}>{author.name}</option>
            );
        });
    }

    return (
        <form id="add-book" onSubmit={submitBook}>
            <h1>Add a new book to Wiki Books</h1>
            <div className="field">
                <label>Book name: </label>
                <input type="text" onChange={handleChange} name="name" required value={bookEntry.name}/>
            </div>

            <div className="field">
                <label>Genre: </label>
                <input type="text" onChange={handleChange} name="genre" required value={bookEntry.genre}/>
            </div>

            <div className="field">
                <label>Author (select author): </label>
                <select onChange={handleChange} name="authorId">
                    {displayAuthors()}
                </select>
            </div>

            <button type="submit">+</button>
        </form>
    );
}

export default AddBook;

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    鉴于您只需要使用从查询返回的data.authors 数组的第一个索引来更新firstAuthorId 状态,因此您应该在挂载组件时执行此操作,而不是调用 displayAuthors 方法并更新每次重新渲染组件的状态。

    这可以使用useEffect 挂钩来实现。

    useEffect(() => {
      setFirstAuthorId(data.authors[0].id)
    }, []);
    

    或者,您可以将data.authors 设置为依赖数组的一部分,这样firstAuthorId 的状态将在每次data.authors 发生变化时更新。

    useEffect(() => {
      setFirstAuthorId(data.authors[0].id)
    }, [data.authors]);
    

    更好的是,实际上不需要维护一个单独的状态(firstAuthorId)来跟踪data.authors 的第一个对象。您可以在代码本身的任何需要的地方简单地引用它。

    【讨论】:

    • 我想补充一点,也许你不需要firstAuthorId 作为状态; state 用于控制组件在事情发生变化时的重新渲染。如果您只是将其用作便利变量,则应考虑使用成本较低的替代方案,例如仅在需要时计算它或仅使用常规变量。
    • @Jacob 你有一个很好的观点。再次查看代码,我没有看到firstAuthorId 状态实际在哪里以及如何使用?我认为在代码中需要的任何地方引用data.authors[0].id 会更方便..
    • Jacobwentjun,如果没有像你这样出色的人花时间在 stackoverflow 上阅读我冗长的问题并提供解决方案,我会在哪里。非常感谢你们俩。 wentjun,我阅读了有关 useEffectuseState 的文档。但是如果没有实际构建一个项目,解决问题并让像你这样的人澄清应该如何使用这些概念,那么我永远不会学习。一切都说得通。 Jacob,您的解决方案很棒、简单且有意义,尤其是在性能方面。根据你的建议,我已经这样实现了:
    • const filledAuthorId = bookEntry.authorId? bookEntry.authorId : data.authors[0].id;
    猜你喜欢
    • 2023-03-08
    • 2021-11-18
    • 2021-09-05
    • 2020-09-17
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 2020-04-05
    • 2021-12-25
    相关资源
    最近更新 更多