简体   繁体   中英

Why is my component not re-rendering when passed in as a child?

I am testing a bit of code to count rerenders.

This one does not work as I am passing <MyComponent> as a child.

  it("should get the same object when the parent rerenders", async () => {
    jest.useFakeTimers();
    const callback = jest.fn();
    let renderCount = 0;
    let x = 0;
    function MyComponent() {
      const random = Math.random();
      const myRef = useRef({ random })
      if (x === 0) {
        x = myRef.current.random
      }
      ++renderCount;
      callback();
      return (<div data-testid="test">{JSON.stringify(myRef.current)}</div>);
    }

    function MyStateComponent({ children }: PropsWithChildren<{}>) {
      const forceUpdate = useReducer(() => ({}), {})[1] as () => void
      useEffect(() => {
        (async function asyncEffect() {
          await delay(10000);
          forceUpdate()
        })()
      }, [])
      return (<>{children}</>);
    }

    const { getByTestId } = render(<MyStateComponent><MyComponent /></MyStateComponent>)
    expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
    expect(renderCount).toEqual(1);
    expect(callback).toBeCalledTimes(1);
    jest.runAllTimers();
    await waitFor(() => {
      expect(callback).toBeCalledTimes(2);
      expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
      expect(renderCount).toEqual(2);
    });
  })

However, this works but I embed <MyComponent /> into the component.

  it("should get the same object when the parent rerenders with children", async () => {
    jest.useFakeTimers();
    const callback = jest.fn();
    let renderCount = 0;
    let x = 0;
    function MyComponent() {
      const random = Math.random();
      const myRef = useRef({ random })
      if (x === 0) {
        x = myRef.current.random
      }
      ++renderCount;
      callback();
      return (<div data-testid="test">{JSON.stringify(myRef.current)}</div>);
    }

    function MyStateComponent({ children }: PropsWithChildren<{}>) {
      const forceUpdate = useReducer(() => ({}), {})[1] as () => void
      useEffect(() => {
        (async function asyncEffect() {
          await delay(10000);
          forceUpdate()
        })()
      }, [])
      return (<MyComponent />);
    }

    const { getByTestId } = render(<MyStateComponent />)
    expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
    expect(renderCount).toEqual(1);
    expect(callback).toBeCalledTimes(1);
    jest.runAllTimers();
    await waitFor(() => {
      expect(callback).toBeCalledTimes(2);
      expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
      expect(renderCount).toEqual(2);
    });
  })

Since MyComponent is determined to be a Pure functional component and it has no state to speak of, I presume React memoizes it automatically. To get around that and force a re-render the component needs part of itself to change, eg a context.

import { createContext, PropsWithChildren, useContext, useEffect, useReducer } from "react";
import { delay } from "./delay";

type IRendering = {}
const RenderingContext = createContext<IRendering>({})
/**
 * This is a component that rerenders after a short delay
 */
export function RerenderingProvider({ children }: PropsWithChildren<{}>): JSX.Element {
  const forceUpdate = useReducer(() => ({}), {})[1] as () => void

  useEffect(() => {
    (async function asyncEffect() {
      await delay(10000);
      forceUpdate();
    })()
  }, [])
  return (<RenderingContext.Provider value={{}}>{children}</RenderingContext.Provider>);
}
export function useRerendering(): IRendering {
  return useContext(RenderingContext);
}

With the following test...

  it("should get the same object when the parent rerenders using component, but the component will rerender as context has changed", async () => {
    jest.useFakeTimers();
    const callback = jest.fn();
    let x = 0;
    function MyComponent() {
      const _ignored = useRerendering();
      const random = Math.random();
      const myRef = useRef({ random })
      if (x === 0) {
        x = myRef.current.random
      }
      callback();
      return (<>
        <div data-testid="test">{JSON.stringify(myRef.current)}</div>
        <div data-testid="random">{JSON.stringify(random)}</div>
      </>);
    }

    const { getByTestId } = render(<RerenderingProvider><MyComponent /></RerenderingProvider>)
    expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
    expect(callback).toBeCalledTimes(1);
    jest.runAllTimers();
    await waitFor(() => {
      expect(getByTestId("test").textContent).toEqual(JSON.stringify({ random: x }));
      expect(callback).toBeCalledTimes(2);
    });
  })

I put up my scenario here... https://github.com/trajano/react-hooks-tests

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM