[英]How to test state update and component rerender after async call in react
[英]How to get react component with useReducer to rerender after axios call?
我正在嘗試使用 useReducer 掛鈎學習狀態管理,因此我構建了一個調用 pokeAPI 的簡單應用程序。 該應用程序應顯示隨機口袋妖怪,並在按下“捕獲另一個”按鈕時向屏幕添加更多口袋妖怪。
但是,它會在從 axios 調用填充 Card 之前使用初始化的空 Card 對象重新渲染組件。 我已經嘗試了至少 3 種基於 stackoverflow 帖子的不同解決方案。
在每次嘗試中,我都得到了相同的結果:應用程序顯示一個未定義的卡片,即使狀態是更新的而不是未定義的,它只是在重新渲染后稍微更新。 再次單擊時,先前的未定義會正確呈現,但現在有一張新卡顯示為未定義。
我仍然掌握了 React 鈎子(沒有雙關語!)、異步編程和一般的 JS 的竅門。
這是應用程序: https : //stackblitz.com/edit/react-ts-mswxjv?file=index.tsx
這是我第一次嘗試的代碼:
//index.tsx
const getRandomPokemon = (): Card => {
var randomInt: string;
randomInt = String(Math.floor(898 * Math.random()));
let newCard: Card = {};
PokemonDataService.getCard(randomInt)
.then((response) => {
//omitted for brevity
})
.catch((error) => {
//omitted
});
PokemonDataService.getSpecies(randomInt)
.then((response) => {
//omitted
})
.catch((error) => {
//omitted
});
return newCard;
};
const App = (props: AppProps) => {
const [deck, dispatch] = useReducer(cardReducer, initialState);
function addCard() {
let newCard: Card = getRandomPokemon();
dispatch({
type: ActionKind.Add,
payload: newCard,
});
}
return (
<div>
<Deck deck={deck} />
<CatchButton onClick={addCard}>Catch Another</CatchButton>
</div>
);
};
//cardReducer.tsx
export function cardReducer(state: Card[], action: Action): Card[] {
switch (action.type) {
case ActionKind.Add: {
let clonedState: Card[] = state.map((item) => {
return { ...item };
});
clonedState = [...clonedState, action.payload];
return clonedState;
}
default: {
let clonedState: Card[] = state.map((item) => {
return { ...item };
});
return clonedState;
}
}
}
//Deck.tsx
//PokeDeck and PokeCard are styled-components for a ul and li
export const Deck = ({ deck }: DeckProps) => {
useEffect(() => {
console.log(`useEffect called in Deck`);
}, deck);
return (
<PokeDeck>
{deck.map((card) => (
<PokeCard>
<img src={card.image} alt={`image of ${card.name}`} />
<h2>{card.name}</h2>
</PokeCard>
))}
</PokeDeck>
);
};
我還嘗試使調用 Axios 的函數成為一個 promise,這樣我就可以將調度調用與 .then 鏈接起來。
//index.tsx
function pokemonPromise(): Promise<Card> {
var randomInt: string;
randomInt = String(Math.floor(898 * Math.random()));
let newCard: Card = {};
PokemonDataService.getCard(randomInt)
.then((response) => {
// omitted
})
.catch((error) => {
return new Promise((reject) => {
reject(new Error('pokeAPI call died'));
});
});
PokemonDataService.getSpecies(randomInt)
.then((response) => {
// omitted
})
.catch((error) => {
return new Promise((reject) => {
reject(new Error('pokeAPI call died'));
});
});
return new Promise((resolve) => {
resolve(newCard);
});
}
const App = (props: AppProps) => {
const [deck, dispatch] = useReducer(cardReducer, initialState);
function asyncAdd() {
let newCard: Card;
pokemonPromise()
.then((response) => {
newCard = response;
console.log(newCard);
})
.then(() => {
dispatch({
type: ActionKind.Add,
payload: newCard,
});
})
.catch((err) => {
console.log(`asyncAdd failed with the error \n ${err}`);
});
}
return (
<div>
<Deck deck={deck} />
<CatchButton onClick={asyncAdd}>Catch Another</CatchButton>
</div>
);
};
我還嘗試使用 useEffect 鈎子讓它有副作用地調用它
//App.tsx
const App = (props: AppProps) => {
const [deck, dispatch] = useReducer(cardReducer, initialState);
const [catchCount, setCatchCount] = useState(0);
useEffect(() => {
let newCard: Card;
pokemonPromise()
.then((response) => {
newCard = response;
})
.then(() => {
dispatch({
type: ActionKind.Add,
payload: newCard,
});
})
.catch((err) => {
console.log(`asyncAdd failed with the error \n ${err}`);
});
}, [catchCount]);
return (
<div>
<Deck deck={deck} />
<CatchButton onClick={()=>{setCatchCount(catchCount + 1)}>Catch Another</CatchButton>
</div>
);
};
所以你的代碼有幾件事,但最后一個版本最接近正確。 通常,您希望在 useEffect 中調用 promise。 如果您希望它被調用一次,請使用空的 [] 依賴項數組。 https://reactjs.org/docs/hooks-effect.html(ctrl+f “一次”並閱讀注釋,它不是那么可見)。 任何時候 dep 數組發生變化,代碼都會運行。
注意:您必須更改對 Pokemon 服務的調用,因為您正在運行兩個異步調用而不等待它們中的任何一個。 您需要使getRandomPokemon
異步並等待兩個調用,然后返回您想要的結果。 (此外,您正在返回 newCard 但未在通話中為其分配任何內容)。 首先通過在像我的示例代碼這樣的承諾中返回假數據來測試這一點,然后在遇到問題時集成 api。
在您的承諾中,它返回一個 Card,您可以直接在調度中使用它(從響應中,您不需要額外的步驟)。 您的 onclick 也用括號錯誤地書寫。 這是我編寫的一些示例代碼,似乎可以正常工作(使用占位符函數):
type Card = { no: number };
function someDataFetch(): Promise<void> {
return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
async function pokemonPromise(count: number): Promise<Card> {
await someDataFetch();
console.log("done first fetch");
await someDataFetch();
console.log("done second fetch");
return new Promise((resolve) =>
setTimeout(() => resolve({ no: count }), 1000)
);
}
const initialState = { name: "pikachu" };
const cardReducer = (
state: typeof initialState,
action: { type: string; payload: Card }
) => {
return { ...state, name: `pikachu:${action.payload.no}` };
};
//App.tsx
const App = () => {
const [deck, dispatch] = useReducer(cardReducer, initialState);
const [catchCount, setCatchCount] = useState(0);
useEffect(() => {
pokemonPromise(catchCount)
.then((newCard) => {
dispatch({
type: "ActionKind.Add",
payload: newCard
});
})
.catch((err) => {
console.log(`asyncAdd failed with the error \n ${err}`);
});
}, [catchCount]);
return (
<div>
{deck.name}
<button onClick={() => setCatchCount(catchCount + 1)}>
Catch Another
</button>
</div>
);
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.