reactjs - useState(new Map()) is not working, but object does
问题描述
I honestly have no idea what is going on here. I have this code, on first render it should fetch popular repos and set them to the repos state, which should cause a re-render and paint the new repos on the DOM. The reason I use Map/obj is because I'm caching the repos to avoid re-fetch. The code doesn't work as expected, it's not setting any new state, and I can verify it in the react dev tools. For some reason if I click around on Components in the devtools, the state updates(?!), but the DOM is still not painted (stuck on Loading), which is a very strange behavior.
export default () => {
const [selectedLanguage, setSelectedLanguage] = useState('All');
const [error, setError] = useState(null);
const [repos, setRepos] = useState(() => new Map());
useEffect(() => {
if (repos.has(selectedLanguage)) return;
(async () => {
try {
const data = await fetchPopularRepos(selectedLanguage);
setRepos(repos.set(selectedLanguage, data));
} catch (err) {
console.warn('Error fetching... ', err);
setError(err.message);
}
})();
}, [selectedLanguage, repos]);
const updateLanguage = useCallback(lang => setSelectedLanguage(lang), []);
const isLoading = () => !repos.has(selectedLanguage) && !error;
return (
<>
<LanguagesNav
selected={selectedLanguage}
updateLanguage={updateLanguage}
/>
{isLoading() && <Loading text="Fetching repos" />}
{error && <p className="center-text error">{error}</p>}
{repos.has(selectedLanguage)
&& <ReposGrid repos={repos.get(selectedLanguage)} />}
</>
);
};
However, if I change up the code to use object instead of a Map, it works as expected. What am I missing here? For example, this works (using obj instead of a Map)
const Popular = () => {
const [selectedLanguage, setSelectedLanguage] = useState('All');
const [error, setError] = useState(null);
const [repos, setRepos] = useState({});
useEffect(() => {
if (repos[selectedLanguage]) return;
(async () => {
try {
const data = await fetchPopularRepos(selectedLanguage);
setRepos(prev => ({ ...prev, [selectedLanguage]: data }));
} catch (err) {
console.warn('Error fetching... ', err);
setError(err.message);
}
})();
}, [selectedLanguage, repos]);
const updateLanguage = useCallback(lang => setSelectedLanguage(lang), []);
const isLoading = () => !repos[selectedLanguage] && !error;
return (
<>
<LanguagesNav
selected={selectedLanguage}
updateLanguage={updateLanguage}
/>
{isLoading() && <Loading text="Fetching repos" />}
{error && <p className="center-text error">{error}</p>}
{repos[selectedLanguage]
&& <ReposGrid repos={repos[selectedLanguage]} />}
</>
);
};
解决方案
repos.set()
mutates the current instance and returns it. Since setRepos()
sees the same reference, it doesn't trigger a re-render.
Instead of
setRepos(repos.set(selectedLanguage, data));
you can use:
setRepos(prev => new Map([...prev, [selectedLanguage, data]]));
推荐阅读
- python - 在 Python 中解析 JSON 并转换为 Excel
- sql - 潜在销售损失查询
- javascript - jQuery将数据属性传递给函数
- java - 将新的客户端证书添加到现有的 HttpClient
- jquery - 在 PHP 端读取 JSON 对象时获取 NULL
- c# - .NET5 WebApi 项目在 IIS 上无限加载
- database - 实例如何在帐户创建中工作?
- c# - 无法延长数据集中的字段
- javascript - 允许 XML/Javascript 工具提示中的多行字符串
- python - 如何设置 Django 和 Python 电报机器人部署到 Heroku?