我有一个NextJS13应用程序,它使用Tailwind CSS框架来设计UI,使用Boardgame Atlas API根据几个下拉列表项(类别、玩家数量和花费)为用户获取棋盘游戏。
Home组件是应用的主要组件,负责从API获取棋盘游戏并呈现UI,它使用另外三个组件CategoriesSelect、PlayerCount和SpendSelect来呈现下拉列表。
CategoriesSelect组件从API获取可用的类别,并呈现一个下拉列表以允许用户选择类别。PlayerCount组件呈现一个下拉列表以允许用户选择游戏所需的最小玩家数量。SpendSelect组件呈现一个下拉列表以允许用户选择他们的预算。
用户从下拉列表中选择值后,单击“Find my game”按钮将调用fetchOutput函数。此函数使用所选值向API发出GET请求,API将返回匹配的游戏。如果找到任何游戏,则数组中的第一个游戏将记录到控制台,否则将记录错误消息。
代码可以在GitHub here上找到,Vercel here上有一个实时版本。为了方便起见,我还粘贴了下面的代码:
第.js页(主页)
"use client"
import { useState, useEffect } from 'react';
import CategoriesSelect from "@/components/CategoriesSelect";
import PlayerCount from "@/components/PlayerCount";
import SpendSelect from "@/components/SpendSelect";
import axios from 'axios';
export default function Home() {
const[formData, setFormData] = useState({
category: "",
playerCount: 1,
spend: 25,
})
const fetchOutput = async () => {
console.log(formData)
try {
const { data } = await axios.get(
`https://api.boardgameatlas.com/api/search?categories=${formData.category}&min_players=${formData.playerCount}<_price=${formData.spend}&client_id=EsdgqvppMg`
);
if (data.games && data.games.length > 0) {
console.log(data.games[0]);
} else {
console.error('No games found');
}
} catch (error) {
console.error(error);
}
};
// useEffect(() => {
// console.log(formData)
// }, [formData])
return (
<main >
<div className="text-center mt-24 mb-16 text-4xl font-mono">looking for your next boardgame?</div>
<div className="text-center font-mono px-10 mb-4">
<span>I want to play a(n) </span>
<CategoriesSelect
formData={formData.category}
setFormData={setFormData} />
<span> game with at least </span>
<PlayerCount
formData={formData.playerCount}
setFormData={setFormData}
/>
<span> other player(s). My budget is up to: </span>
<SpendSelect
formData={formData.spend}
setFormData={setFormData}
/>
<span> USD</span>
</div>
<div className="flex">
<button type="button"
onClick={fetchOutput}
className="mx-auto px-6 py-2.5 bg-blue-600 text-white font-medium leading-tight rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out text-xl">🎲 Find my game 🎰</button>
</div>
</main>
)
}
类别选择.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
const CategoriesSelect = ({formData, setFormData}) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://api.boardgameatlas.com/api/game/categories?client_id=EsdgqvppMg'
);
setCategories(result.data.categories);
};
fetchData();
}, []);
return (
<div className='inline mb-3 xl:w-96'>
<select className='form-select appearance-none
px-3
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
text-center
ease-in-out
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none" aria-label="Default select example'
value={formData}
onChange={e => setFormData({...formData, category: e.target.value})}
>
{categories.map(category => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
);
};
export default CategoriesSelect;
播放机计数.jsx
"use client"
export default function PlayerCount({formData, setFormData}) {
const options = [1, 2, 3, 4, 5, 6, 7].map(number => (
<option key={number} value={number}>
{number}
</option>
));
return (
<div className='inline mb-3 xl:w-96'>
<select
value={formData}
onChange={e => setFormData({...formData, playerCount: e.target.value})}
className='form-select appearance-none
px-7
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
ease-in-out
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
aria-label='Default select example'
>
{options}
</select>
</div> )
}
支出选择.jsx
"use client"
export default function SpendSelect({formData, setFormData}) {
const options = [10, 25, 50, 75, 100, 200, 300 ].map(number => (
<option key={number} value={number}>
{number}
</option>
));
return (
<div className='inline mb-3 xl:w-96'>
<select
value={formData}
onChange={e => setFormData({...formData, spend: e.target.value})}
className='form-select appearance-none
px-7
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
ease-in-out
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
aria-label='Default select example'
>
{options}
</select>
</div> )
}
我的挑战:
从子组件更新主页组件中formData
的状态时遇到问题。即使更新了从子组件传递的属性,游戏.length仍返回0。有人能帮助我从子组件更新formData状态的正确方法吗?
1条答案
按热度按时间sqyvllje1#
已修复!答案:
之前:
onChange={e => setFormData({...formData, playerCount: e.target.value})}
之后:
onChange={e => setFormData(prevFormData => ({...prevFormData, playerCount: e.target.value}))}
这应该应用于所有使用onChange的组件。看起来我是在传播一个prop而不是一个object数组。