5

I'm trying to create a custom hook useFocus based on useContext to set the focus only on the component i select. Its working but other components are rendering even i used useCallback for the function returned by my useFocus custom hook.

i would like to rerender only the components with changing focus.

I know rerender can be minor issue if the code is fast but i can't understand why it's re-render. Could you give me some explanation or a fix.

Expected result :

When clicking on 'set focus' button, I expect to get :

1 render for A/B/D

2 renders for C/E

Expected result in image

Thanks.

Here my code :

import React, { createContext, useCallback, useContext, useState } from "react";
import "./styles.css";

const StepContext = createContext({});

//This is just to display number or render for each Items
function useRenderCounter() {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current.textContent = Number(ref.current.textContent || "0") + 1;
  });
  return (
    <span
      style={{
        backgroundColor: "#ccc",
        borderRadius: 4,
        padding: "2px 4px",
        fontSize: "0.8rem",
        margin: "0 6px",
        display: "inline-block"
      }}
      ref={ref}
    />
  );
}

const useFocus = (property) => {
  const context = useContext(StepContext);

  const bool = context === property;
  //console.log("bool", bool, context, property);

  //return bool
  return useCallback(() => bool, [bool]);
};

const Item = React.memo(({ property }) => {
  const rendercounter = useRenderCounter();
  const isFocus = useFocus(property);
  //Here I expect to got re-render only for property which the focus changed

  const focus = isFocus();

  console.log(property, "render", focus);

  const style = focus ? { borderStyle: "solid", borderColor: "red" } : {};

  return (
    <div style={{ display: "flex", margin: "4px" }}>
      {rendercounter}
      <div style={style}>{property}</div>
    </div>
  );
});

export default function App() {
  const [focusOn, setFocusOn] = useState("E");

  const handleClick = () => setFocusOn("C");

  return (
    <StepContext.Provider value={focusOn}>
      <div style={{ display: "flex", flexDirection: "column" }}>
        <Item key={1} property={"A"} />
        <Item key={2} property={"B"} />
        <Item key={3} property={"C"} />
        <Item key={4} property={"D"} />
        <Item key={5} property={"E"} />
        <button onClick={handleClick}>set focus</button>
      </div>
    </StepContext.Provider>
  );
}

Here the sandbox

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Norbert P
  • 53
  • 3
  • I deleted my answer because I noticed if it doesn't re-render, the style would always be border red because focus changes and it gets re-evaluated, so I'm not sure my answer would apply at any rate. What's the intention? [This thread might be helpful](https://stackoverflow.com/questions/50817672/does-new-react-context-api-trigger-re-renders) – Nikki9696 Jun 30 '21 at 16:49

1 Answers1

2

There is no way to avoid that re-render when Provider gets new value. From official docs on Context API:

All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update.

skyboyer
  • 22,209
  • 7
  • 57
  • 64