How to prevent useEffect in customHook being called with every import of custom hook?

Viewed 271

I'm writing chat app using react js and socket.io library.

All the logic where I subscribe to events form server and emit some events is written in useEffect of custom hook.

Then I return all data I need from this custom hook and reuse it in components that I need. However, I realized that logic written in useEffect is called every time I import this custom hook to external component.

If I put all the logic outside of useEffect, it's called even more times than custom hook is imported.

How do I prevent it if it's possible at all?

If it's not possible, what solution could you please suggest? I don't want to use redux for this app, I thought to keep everything in this custom hook component and just reuse data from it where I need.

I can't share working example because it won't work without server part so here is a simple codesandbox example. You can see in console that it's rendered twice.

https://codesandbox.io/s/custom-hook-bfc5j?file=/src/useChat.js

2 Answers

If you want to set up some side effects once but also consume the resulting data in multiple places, one way is to use the context feature.

// ws/context.jsx, or similar

const WsContext = React.createContext(defaultValue);

export const WsProvider = props => {
  const [value, setValue] = useState(someInitialValue);
  useEffect(() => {
    // do expensive things, call setValue with new results
  });
  return (
    <WsContext.Provider value={value}>
      {props.children}
    </WsContext.Provider>
  );
};

export const useCustomHook = () => {
  const value = useContext(WsContext);

  // perhaps do some other things specific to this hook usage

  return value;
};

You can expect the hook to work in any component that is a descendant of <WsProvider> in React's rendered tree of elements.

If you use the hook in a non-descendant of the provider component, the value returned will be the defaultValue we initialized the context instance with.

It renders twice because you call useChat() two times in your app (one in App.js, other in Text.js) What you can do is to create a reference of useChat component in your App.js and pass is as a prop to Text.js like:

App.js

import React from "react";
import useChat from "./useChat";
import Text from "./Text";
import "./styles.css";

export default function App() {
  const myUseChat = useChat();
  const { printMessage } = myUseChat;

  return (
    <div className="App">
      <button onClick={printMessage}>Print</button>
      <Text myUseChat={myUseChat} />
    </div>
  );
}

Text.js

import React from "react";
import useChat from "./useChat";
import "./styles.css";

export default function Text(props) {
  const { text } = props.myUseChat;

  return <div className="App">{text}</div>;
}
Related