我用这个组件把自己挖进了一个很深的兔子洞,试图使用 React 钩子。
父组件处理最终分发到多个组件的dictionary
状态。
我的问题子组件WordInput
有一个具有单个输入的表单。提交表单时,组件从 API 获取单词的定义,并将单词和定义传递给父级,然后父级以dictionary
的形式设置状态。到目前为止,如果它是dictionary
中的第一个词,那就太好了.我遇到麻烦的部分是提交任何后续单词/定义。
当用户提交后续单词时,我希望组件检查传递给子项的dictionary
中是否已存在该单词。如果不存在,请通过提交功能将其添加到dictionary
。
我认为问题是我试图对useEffect
做太多 我useEffect
: - 设置加载 - 检查和处理字典中的现有单词 - 检查定义和单词是否不为空,并将两者提交给父/字典 - 从 API 获取定义
在未处理的代码中,我有多个console.groups
来帮助我跟踪正在发生的事情。我添加到组件中的越多,子组和子组的子组累积的子组就越多。显然,我采用的方法不是很枯燥,会导致组件/useEffect函数的太多重新渲染。为了简洁起见,我删除了console.log
条目。
导入的fetchWordDefinition
仅处理获取的数据并将其正确排列到数组中。
我不知道如何保持这种干燥和有效,任何帮助都感谢这个相当简单的任务。我的预感是保留提交处理程序中提交单词/定义的所有逻辑,并且仅使用useEffect
在此之前验证数据。
import React, { useState, useEffect } from "react";
import fetchWordDefinition from "./lib/utils";
const WordInput = ({ onSubmit, dictionary }) => {
const [definition, setDefinition] = useState([]);
const [cause, setCause] = useState({ function: "" });
const [error, setError] = useState({});
const [loading, setLoading] = useState(false);
const [word, setWord] = useState("");
const [wordExistsInDB, setWordExistsInDB] = useState(false);
useEffect(() => {
const dictionaryEmpty = dictionary.length === 0 ? true : false;
if (dictionaryEmpty) {
return;
} else {
for (let i = 0; i < dictionary.length; i += 1) {
if (dictionary[i].word === word) {
setWordExistsInDB(true);
setError({ bool: true, msg: "Word already exists in DB" });
break;
} else {
setWordExistsInDB(false);
setError({ bool: false, msg: "" });
}
}
}
}, [dictionary, word]);
useEffect(() => {
const definitionNotEmpty = definition.length !== 0 ? true : false;
const wordNotEmpty = word !== "" ? true : false;
if (wordNotEmpty && definitionNotEmpty && !wordExistsInDB) {
onSubmit(word, definition);
setWord("");
setDefinition([]);
}
}, [definition, word, onSubmit, wordExistsInDB]);
useEffect(() => {
if (cause.function === "fetch") {
async function fetchFunction() {
const fetch = await fetchWordDefinition(word);
return fetch;
}
fetchFunction().then(definitionArray => {
setDefinition(definitionArray);
setCause({ function: "" });
});
}
}, [cause, word]);
const handleSubmit = async e => {
e.preventDefault();
setLoading(true);
setCause({ function: "fetch" });
};
return (
<form onSubmit={handleSubmit}>
{error.bool ? <span>{error.msg}</span> : null}
<input
name='word'
placeholder='Enter Word'
type='text'
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type='submit' />
</form>
);
};
export default WordInput;
确实有比必要的更多的useEffect
发生,以及大部分州。您所需要的只是进行获取的handleSubmit
。
const WordInput = ({ onSubmit, dictionary }) => {
const [word, setWord] = React.useState("");
const handleChange = React.useCallback(e => {
setWord(e.currentTarget.value)
}, [])
const handleSubmit = React.useCallback(() => {
//check if word is in dictionary
const wordIsAlreadyThere = dictionary.map(entry => entry.word).includes(word)
//fetch the definition, wait for it, and call submit
if(!wordIsAlreadyThere && word.length > 0){
fetchWordDefinition(word)
.then(definition => {
onSubmit(word, definition)
setWord('')
}).catch(err => console.log(err))
}
}, [])
return (
<form onSubmit={handleSubmit}>
<input
value={word}
onChange={handleChange}/>
<input type='submit' />
</form>
);
}
我认为您错过了一些清晰度以及useEffect
的用途
每次 prop 或状态更改时,功能组件都会重新运行。useEffect
在创建组件时运行,我们将其用于首次获取或订阅事件处理程序等操作。使用第二个参数(变量数组),因此,如果我们有一篇带有评论等的博客文章,除非 ID 发生变化(这意味着它是一篇新的博客文章),否则我们不会重新获取所有内容
查看您的代码,我们有以程:
-
用户输入内容并点击提交
-
检查该单词是否存在于字典中
一个。如果存在,则显示错误消息
二.如果不存在,请从 API 获取并调用
onSubmit
因此,我们这里唯一的状态就是这个词。您可以根据单词是否在字典中计算错误,并且 API 调用在回调中完成 (useCallback
)。你有很多额外的状态,在状态方式中并不重要
简化版本如下所示
const WordInput = ({ onSubmit, dictionary }) => {
const [word, setWord] = useState("")
const [loading, setLoading] = useState(false)
// `find` will find the first entry in array that matches
const wordExists = !!dictionary.find(entry => entry.word === word)
// Ternary operator,
const error = (wordExists) ? "Word already exists in DB" : null
// When user hits submit
const handleSubmit = useCallback(() => {
if (wordExists || !word.length) return;
setLoading(true)
fetchFunction()
.then(definitionArray => {
onSubmit(word, definitionArray)
})
}, [])
return (
<form onSubmit={handleSubmit}>
{error && <span>{error}</span>}
<input
name='word'
placeholder='Enter Word'
type='text'
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type='submit' onclick={handleSubmit} disabled={wordExists}/>
</form>
);
};
您的组件只需要跟踪word
和loading
标志。
- 当用户更改单词输入时,它会更新单词状态。
- 当用户提交表单时,加载状态会发生变化。这将触发一个
useEffect
,该将首先检查单词是否已存在。如果没有,它继续获取它并将单词及其定义添加到字典中。
const WordInput = ({ onSubmit, dictionary }) => {
const [loading, setLoading] = useState(false);
const [word, setWord] = useState("");
useEffect(() => {
if (!loading) return;
const existing_word = dictionary.find(item => item.word === word);
if (existing_word) return;
const fetchFunction = async () => {
const definition = await fetchWordDefinition(word);
// Update the dictionary
onSubmit(word, definition);
// Reset the component state
setWord("");
setLoading(false);
};
fetchFunction();
}, [loading]);
return (
<form
onSubmit={e => {
e.preventDefault();
if (word.length) {
setLoading(true);
}
}}
>
<input
name="word"
placeholder="Enter Word"
type="text"
value={word}
onChange={({ target: { value } }) => setWord(value)}
/>
<input type="submit" />
</form>
);
};
如果有什么不清楚的地方或我错过了什么,请告诉我。