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

1
  • 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 Commented Jun 30, 2021 at 16:49

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.