简体   繁体   English

如何在 axios 调用后使用 useReducer 获取反应组件以重新渲染?

[英]How to get react component with useReducer to rerender after axios call?

I am trying to learn state management with the useReducer hook so I have built a simple app that calls the pokeAPI.我正在尝试使用 useReducer 挂钩学习状态管理,因此我构建了一个调用 pokeAPI 的简单应用程序。 The app should display a random pokemon, and add more pokemons to the screen as the 'capture another' button is pressed.该应用程序应显示随机口袋妖怪,并在按下“捕获另一个”按钮时向屏幕添加更多口袋妖怪。

However, it rerenders the component with the initialized and empty Card object before populating the Card from the axios call.但是,它会在从 axios 调用填充 Card 之前使用初始化的空 Card 对象重新渲染组件。 I've tried at least 3 different solutions based on posts from stackoverflow.我已经尝试了至少 3 种基于 stackoverflow 帖子的不同解决方案。

In each attempt I have gotten the same result: the app displays an undefined card on, even though the state is updated and not undefined, it just was updated slightly after the rerendering.在每次尝试中,我都得到了相同的结果:应用程序显示一个未定义的卡片,即使状态是更新的而不是未定义的,它只是在重新渲染后稍微更新。 When clicked again that prior undefined gets properly rendered but there is now a new card displayed as undefined.再次单击时,先前的未定义会正确呈现,但现在有一张新卡显示为未定义。

I am still getting the hang of react hooks (no pun intended!), async programming, and JS in general.我仍然掌握了 React 钩子(没有双关语!)、异步编程和一般的 JS 的窍门。

Here is the app: https://stackblitz.com/edit/react-ts-mswxjv?file=index.tsx这是应用程序: https : //stackblitz.com/edit/react-ts-mswxjv?file=index.tsx

Here is the code from my first try:这是我第一次尝试的代码:

//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>
  );
};

I also experimented with making the function that calls Axios a promise so I could chain the dispatch call with a .then.我还尝试使调用 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>
  );
};

I also tried to have it call it with a side effect using useEffect hook我还尝试使用 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>
  );
};

So there are a couple of things with your code, but the last version is closest to being correct.所以你的代码有几件事,但最后一个版本最接近正确。 Generally you want promise calls inside useEffect.通常,您希望在 useEffect 中调用 promise。 If you want it to be called once, use an empty [] dependency array.如果您希望它被调用一次,请使用空的 [] 依赖项数组。 https://reactjs.org/docs/hooks-effect.html (ctrl+f "once" and read the note, it's not that visible). https://reactjs.org/docs/hooks-effect.html(ctrl+f “一次”并阅读注释,它不是那么可见)。 Anytime the dep array changes, the code will be run.任何时候 dep 数组发生变化,代码都会运行。

Note: you'll have to change the calls to the Pokemon service as you're running two async calls without awaiting either of them.注意:您必须更改对 Pokemon 服务的调用,因为您正在运行两个异步调用而不等待它们中的任何一个。 You need to make getRandomPokemon async and await both calls, then return the result you want.您需要使getRandomPokemon异步并等待两个调用,然后返回您想要的结果。 (Also you're returning newCard but not assigning anything to it in the call). (此外,您正在返回 newCard 但未在通话中为其分配任何内容)。 First test this by returning a fake data in a promise like my sample code then integrate the api if you're having issues.首先通过在像我的示例代码这样的承诺中返回假数据来测试这一点,然后在遇到问题时集成 api。

In your promise, it returns a Card which you can use directly in the dispatch (from the response, you don't need the extra step).在您的承诺中,它返回一个 Card,您可以直接在调度中使用它(从响应中,您不需要额外的步骤)。 Your onclick is also incorrectly written with the brackets.您的 onclick 也用括号错误地书写。 Here's some sample code that I've written and seems to work (with placeholder functions):这是我编写的一些示例代码,似乎可以正常工作(使用占位符函数):

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.

相关问题 如何在异步调用反应后测试 state 更新和组件重新渲染 - How to test state update and component rerender after async call in react 在ajax调用之后反应组件状态更改但不重新呈现组件 - react component state change after ajax call but does not rerender component 如何在没有forceupdate的情况下让反应组件重新渲染? - How to get a react component to rerender without forceupdate? 在组件之外修改其道具后如何重新渲染React组件 - How to rerender React component after modifying its props outside of the component 在Axios调用后设置状态后无法使React组件更新 - Can't get React component to update after setting state after Axios call 如何避免在 React 中重新渲染组件? - How to avoid rerender of a component in React? State 在 axios 调用后第一次在 React 的功能组件中没有得到更新 - State does not get updated in functional component in React for the first time after axios call 使用 axios 不会重新呈现我的 React Native 组件 - Using axios doesn't rerender my React Native component 更新状态后,react 组件不会重新渲染 - react component does not rerender after update state React:在 axios 调用后调用了不正确的 Modal 组件 - React: Incorrect Modal component gets rendered called after axios call
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM