英文:
React useCallback - Select component rerendering after selecting option - not wanted
问题
我有一个HTML选择下拉列表(SortBy组件),在选择选项后,组件重新渲染,这不是我想要的,我希望看到我选择的选项,但它一直重置(重新渲染)为第一个选项。我尝试了useCallback,但没有运气。
当选择A-Z或价格:从低到高时,我希望在HTML选择框中保留该值,但它默认返回到相关性。
function App() {
const [items, setItems] = useState(products);
console.log(products);
const onChange = useCallback((e) => {
const { value } = e.target;
let arr = items;
switch (value) {
case "relevance":
arr = saleItemsFirst(items, false);
break;
case "ascend":
arr = sortByName(items);
break;
case "priceLow":
arr = sortByPrice(items);
break;
default:
arr = saleItemsFirst(items);
break;
}
setItems([...arr]);
},
[items]
);
const SortBy = useCallback(() => {
return (
<select name="filters" onChange={onChange}>
<option value="relevance">Relevance</option>
<option value="ascend">A-Z</option>
<option value="priceLow">Price: Low to high</option>
<option value="saleFirst">Sales items first</option>
</select>
);
}, [onChange]);
return (
<div className="App">
<SortBy />
<hr />
<section>
{items.map((val) => (
<div>
<span>{val.productName}</span> <div>${val.price}</div>
<hr />
</div>
))}
</section>
// 其他部分...
</div>
);
}
希望这可以解决你的问题。
英文:
I have a HTML select drop down list (SortBy component), after selecting an option, the component rerenders, this is not wanted, I want to see the option I selected but it keeps reseting (rerendering) to the first option. I've tried useCallback with no luck
When A-Z or Price: Low to high is selected, I want that value retained in the HTML select box but it defaults back to Relevance
function App() {
const [items, setItems] = useState(products);
console.log(products);
const onChange = useCallback((e) => {
const { value } = e.target;
let arr = items;
switch (value) {
case ("relevance", "saleFirst"):
arr = saleItemsFirst(items, false);
break;
case "ascend":
arr = sortByName(items);
break;
case "price":
arr = sortByPrice(items);
break;
default:
arr = saleItemsFirst(items);
break;
}
setItems([...arr]);
},
[items]
);
const SortBy = useCallback(() => {
return (
<select name="filters" onChange={onChange}>
<option value="relevance">Relevance</option>
<option value="ascend">A-Z</option>
<option value="priceLow">Price: Low to high</option>
<option value="saleFirst">Sales items first</option>
</select>
);
}, [onChange]);
return (
<div className="App">
<SortBy />
<hr />
<section>
{items.map((val) => (
<div>
<span>{val.productName}</span> <div>${val.price}</div>
<hr />
</div>
))}
</section>
......
答案1
得分: 2
问题:
SortBy
每当 items
更改时都会被重新定义,因为 items
在您的 useCallback
的依赖数组中,告诉它何时重新运行,重新定义返回的函数引用。当这种情况发生时,它是一个全新的组件实例,并且它会被重新挂载到初始值。
如果您以这种方式定义函数,items
必须在您的依赖数组中,否则当 items
更改时,您的记忆化(使用 useCallback
包装的)组件将变得过时。
解决方案:
与其在 App 组件内部声明 SortBy
,您需要将其声明在外部,并将其需要的任何依赖项作为 props 传递。这样,SortBy 组件将成为模块级常量,它只会在 App 挂载时挂载一次。
要点:
通常在其他组件内部定义组件是一种不好的做法。要避免这样做。唯一可能的例外是非常简单的(比如一行代码)组件,没有状态方面的考虑。即使在这种情况下,我也建议在其他组件之外定义它们。
英文:
The Problem:
SortBy
is getting re-defined whenever items
changes because items
is in the dependency array for your useCallback, which tells it when to re-run, re-defining the returned function reference. When that happens, it's a whole new component instance, and it's being remounted with initial values.
items
has to be in your dependency array if you define a function this way, otherwise, when items
changes, your memoized (useCallback'd) component will be stale.
The Solution:
Rather than declaring SortBy
inside of the App component, you'll need to declare it outside, and pass in any dependencies it needs as props. That way, the SortBy component will be a module-level constant, and it will only mount once when App mounts.
Takeaway:
It's generally a bad practice to define components inside of other components. Avoid it. The only possible exception are very simple (like, one-liner) components that have no state concerns. Even then I'd advise defining them outside other components.
答案2
得分: 1
我建议按照Faust说的去做,并强烈建议您将组件移到App
之外,但我还建议您重新考虑您跟踪排序方式的方式。
不要将排序后的列表项存储在状态中,而是只需跟踪选择的排序选项并使用记忆化技术来维护已排序的项目列表,当所选排序更改时进行更新:
// 我们需要一个要显示的项目列表
const items = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);
// 排序名称到排序函数的映射
const sortOptions = {
ascending: (a, b) => a.localeCompare(b),
descending: (a, b) => b.localeCompare(a),
random: () => Math.random() - 0.5,
}
// 组件
function MyList () {
// 跟踪选择的排序名称
const [sortName, setSortName] = React.useState();
// 当排序名称更改时重新排序项目列表
const sortedItems = React.useMemo(() => {
// 通过名称查找排序函数并传递给array.sort()
return [...items].sort(sortOptions[sortName]);
}, [sortName]);
return (
<div>
{/* 在更改时更新排序名称,并设置select的值 */}
<select onChange={(e) => setSortName(e.target.value)} value={sortName}>
{/* 为sortOptions中的每个项渲染一个选项 */}
{Object.keys(sortOptions).map(sortName => (
<option key={sortName}>{sortName}</option>
))}
</select>
{/* 渲染项目列表 */}
<ol>
{ sortedItems.map(item => <li key={item}>{item}</li>) }
</ol>
</div>
);
}
ReactDOM.render(<MyList />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
如果您需要任何进一步的帮助,请告诉我。
英文:
I'd echo what Faust says and strongly advise you to move your components out of App
, but I'd also suggest that you rethink the way you're keeping track of the sort.
Instead of keeping the sorted list items in state you could just keep track of which sort option is selected and memoize the sorted item list, updating it when the selected sort changes:
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
// we need a list of items to display
const items = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);
// a mapping of sort names to sort functions
const sortOptions = {
ascending: (a, b) => a.localeCompare(b),
descending: (a, b) => b.localeCompare(a),
random: () => Math.random() - 0.5,
}
// the component
function MyList () {
// keep track of which sort name is selected
const [sortName, setSortName] = React.useState();
// re-sort the item list when the sort name changes
const sortedItems = React.useMemo(() => {
// look up the sort function by name and pass it to array.sort()
return [...items].sort(sortOptions[sortName]);
}, [sortName]);
return (
<div>
{/* update the sort name on change, and set the value of the select */}
<select onChange={(e) => setSortName(e.target.value)} value={sortName}>
{/* render an option for each item in sortOptions */}
{Object.keys(sortOptions).map(sortName => (
<option key={sortName}>{sortName}</option>
))}
</select>
{/* render the list of items */}
<ol>
{ sortedItems.map(item => <li key={item}>{item}</li>) }
</ol>
</div>
);
}
ReactDOM.render(<MyList />, document.getElementById('root'))
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论