How to render only the concerned component with a custom hook and useContext

Viewed 524

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

1 Answers

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.

Related