![](/img/trans.png)
[英]setState using its callback inside useEffect causing infinite looping
[英]SetState inside useEffect is causing side effects on select input functionality
每個 select 菜單都帶有一個框內的幫助文本。 類似於工具提示。 用戶可以在單擊“關閉按鈕”或單擊外部時關閉它們。
我的解決方案有效,每次您在它們外面單擊時它們都會關閉。
問題是 useEffect 中的 setState 對 select 菜單有副作用。
問題是當我使用“關閉按鈕”關閉信息框或在信息框內單擊時。 在我用按鈕關閉它或在其中單擊后,如果我嘗試更改一個選項,我會看到選項閃爍並且我無法更改選擇,它只能在第二次工作。
這是我的代碼: https://stackblitz.com/edit/react-61rzle?file=src%2FSelect.js
export default function Select() {
const selectMenus = [
{
Label: 'Select 1',
Name: 'select1',
DefaultValue: '1',
HelpText: 'Help text',
Id: 'select_1',
Options: [
{
Value: '0',
Text: 'All age groups',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '35 - 37 yrs',
},
{
Value: '3',
Text: '38 - 39 yrs',
},
{
Value: '4',
Text: '40 - 42 yrs',
},
{
Value: '5',
Text: '43 - 44 yrs',
},
{
Value: '6',
Text: '45 yrs +',
},
],
},
{
Label: 'Select 2',
Name: 'select2',
DefaultValue: '0',
HelpText: 'Help text',
Id: 'select_2',
Options: [
{
Value: '0',
Text: 'All',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '43 - 44 yrs',
},
],
},
];
const [value, setValue] = useState({
select1: '',
select2: '',
});
// help texts setup
const initialVisibleHelpTexts = {
info0: false,
info1: false,
info2: false,
};
const [visibleHelpText, setVisibleHelpText] = useState(
initialVisibleHelpTexts
);
const showHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...initialVisibleHelpTexts, ...{ [key]: true } });
};
const hideHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...visibleHelpText, ...{ [key]: false } });
};
// close info on click outside
useEffect(() => {
document.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
if (
e.target.parentNode.className !== 'info__content' &&
e.target.parentNode.className !== 'info__content-header-text' &&
e.target.parentNode.className !== 'info__content-header'
) {
setVisibleHelpText(initialVisibleHelpTexts);
}
});
}, []);
const handleOnChange = (e) => {
const valueSelected = e.target.value;
setValue({
...value,
[e.target.name]: valueSelected,
});
};
return (
<form>
{selectMenus.length > 0 && (
<div className="selectors-container">
{selectMenus.map((select, i) => (
<div className="select" key={uuid()}>
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
showHelpText(e, `info${i}`);
}}
>
Show info
</button>
</div>
{visibleHelpText[`info${i}`] && (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">
{select.Label}
</span>
<button
onClick={(e) => {
hideHelpText(e, `info${i}`);
}}
>
Close info
</button>
</div>
<div className="info__content-header-text">
{select.HelpText}
</div>
</div>
</div>
)}
</div>
<div className="select__menu-btn-container">
<div className="select__container">
<select
name={select.Name}
id={select.Id}
value={value[`${select.Name}`]}
onChange={handleOnChange}
>
{select.Options.map((option) => (
<option value={option.Value} key={uuid()}>
{option.Text}
</option>
))}
</select>
</div>
</div>
</div>
))}
</div>
)}
</form>
);
}
發生閃爍是因為您有一個巨大的組件,每次您切換信息文本的可見性時都會重新呈現。 只要您單擊 select,整個組件就會重新呈現,這會導致 select 立即關閉。
要解決這個問題,您必須防止整個組件重新渲染。 將它分成更小的塊,可以單獨重新渲染。 下面是一個簡化的示例,展示了如何將信息部分隔離到一個自我管理的組件中。
function InfoSection({ select }) {
const [isVisible, setIsVisible] = useState(false);
return (
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
setIsVisible(true);
}}
>
Show info
</button>
</div>
{isVisible && <InfoText setIsVisible={setIsVisible} />}
</div>
);
}
function InfoText({ setIsVisible }) {
function handleCLickOutside(e) {
setIsVisible(false);
}
useEffect(() => {
document.addEventListener('click', handleCLickOutside);
//this will remove the event listener, when the component gets unmounted. This is important!
return () => document.removeEventListener('click', handleCLickOutside);
}, []);
return (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">{'label'}</span>
<button onClick={console.log}>Close info</button>
</div>
<div className="info__content-header-text">{'select.HelpText'}</div>
</div>
</div>
);
}
不要忘記刪除您的事件偵聽器,一旦您不再需要它們,例如當組件被卸載時:
return () => document.removeEventListener('click', handleCLickOutside);
否則,這可能會導致錯誤和性能問題。
這是帶有應用示例的 stackblitz。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.