skip to Main Content

Suppose I have 4 files in React JS, which are: 1 external file, 2 pages and 1 component:

  1. Login.js (1 page)
  2. counter.cache.js (1 file)
  3. Landing.js(1 page)
  4. Counter.js (1 component)

1- In Login.js, I have 3 buttons:
1-1. A button to login in using email and password.
1-2. A button to sign in with third party.
1-3. A button to navigate from Login.js to Landing.js.

Here is the code of Login.js:

import { useNavigate } from "react-router-dom";
import { queryClient } from "../App";

const Login = () => {
const navigate = useNavigate();
return(
    <div
      style={{
        minHeight: "100vh",
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
        <button
          onClick={() => {
              // Save 0 into query with queryKey: 'counter'

              queryClient.setQueryData(["counter"], {
                  counter: 0,
              });
           }}
        >
        Login with email and password
      </button>

      <button
        style={{ marginTop: "20px" }}
        onClick={() => {
          // Save 0 into query with queryKey: 'counter'

          queryClient.setQueryData(["counter"], {
            counter: 0,
          });
        }}
      >
        Sign in with third party
      </button>

      <button
        style={{ marginTop: "20px" }}
        onClick={() => {
          // Navigate to Landing page
          navigate("/landing");
        }}
      >
        Go to landing page
      </button>

    </div>
);
}

export default Login;

2- In counter.cache.js, I have only one ‘useQuery’ which is useCounter:

Here is the code of counter.cache.js:

// Here I don't need to fetch anything, just to get data from this **useQuery**
export const useCounter = () =>
  useQuery({
    queryKey: ["counter"],
    queryFn: () => null,
  });

3- In Counter.js, I’m increasing the counter,then, updating the value using useState hook and into cache. In addition, to implement useEffect on the counter from useCounter if it’s changing.

Here is the code of Counter.js:

import { useEffect, useState } from "react";
import { useCounter } from "../cache/counter.cache";

const Counter = () => {
  const { data, isLoading } = useCounter();
  const [counter, setCounter] = useState(-1);

  // Put the value of initial counter from cache which is 0
  useEffect(() => {
    if (data && !isLoading) {
      setCounter(data.counter);
    }
  }, [data]);

  // Implement useEffect on the counter from 'cache':
  useEffect(() => {
    if (data && data.counter !== 0) {
      console.log(`Rendering Counter component`);
    }
  }, [data.counter]);

  return (
  <div
      style={{
        width: "200px",
        height: "300px",
        position: "fixed",
        right: "0",
        bottom: "0",
        marginBottom: "30px",
        border: "3px solid black",
      }}
    >
    <h2>{`Counter is ${counter}`}</h2>
       <button
        onClick={() => {
          data.counter += 1;
          setCounter(data.counter);
        }}
       >
        Increase
      </button>

    </div>
  );

}
export default Counter;

4- In Landing.js, I’m just using the component Counter.js and implementing useEffect on the counter from useCounter if it’s changing (the same as implementing useEffect on the counter in Counter.js component above).

import { useCounter } from "../cache/counter.cache";

const Landing = () => {
  const { data, isLoading } = useCounter();
  useEffect(() => {
    if (data && data.counter !== 0) {
      console.log(`Rendering landing page`);
    }
  }, [data.counter]);

  if(data && !isLoading)
     return <Counter/>
}

export default Landing;

My problem is console.log(Rendering landing page); is not being called, while ONLY console.log(Rendering Counter component); is being called.

So, as conclusion, the useEffect is being called only in Counter.js component, but not in Landing.js page.

Note that I’m using the following react-query and react-query dev tools version:

"@tanstack/react-query": "^4.29.12",
"@tanstack/react-query-devtools": "^4.29.12",

What I have tried (but in different scenario), I have implemented the following:

In the Counter.js component:

useEffect(()=>{
    const event = new CustomEvent("new_event", { detail: SomeDataHere });
    window.dispatchEvent(event);
},[counter])

In the Landing.js page:

useEffect(()=>{

    const handleNewData = (data) => {
      console.log("Rendering Landing component");
    }

    window.addEventListener("new_event", handleNewData);
    return () => {
      window.removeEventListener("new_event", handleNewData);
    };
  }, []);

Small hint: in the dev tools, there is a number ‘2’ beside the query key ‘counter’, and that’s because I have used the useCounter inside Landing.js and inside Counter.js component that is inside Landing.js page.

2

Answers


  1. You should not be storing the resulting data in local state in your Counter.js file. Instead you should be deriving the state, and updating your queryCache. e.g.

    const Counter = () => {
      const { data, isLoading } = useCounter();
      const queryClient = useQueryClient();
      const setCounter = useCallback(
        v => queryClient.setQueryData(['counter'], { counter: v }), 
        [queryClient]
      );
    
      // Implement useEffect on the counter from 'cache':
      useEffect(() => {
        if (data && data.counter !== 0) {
          console.log(`Rendering Counter component`);
        }
      }, [data.counter]);
    
      return (
      <div>
        <h2>{`Counter is ${counter}`}</h2>
           <button
            onClick={() => {
              setCounter(data.counter + 1);
            }}
           >
            Increase
          </button>
    
        </div>
      );
    
    }
    export default Counter;
    

    That said, it’s very unsual to use react-query for state that is not stored server side. Usually for a global state/store (like what you’re demonstrating) you’d use one of the various global state solutions like Redux, Zustand, or Jotai.

    Login or Signup to reply.
  2. In your example, you do this:

    data.counter += 1;
    

    That’s not allowed. Data in the React Query cache is immutable. When you directly modify it like that, React Query doesn’t know about the changes you made, so it can’t tell other components that things have been updated (which explains why one of the console.logs isn’t happening). In fact, that doesn’t tell either component to re-render — the only reason the re-render happens is because you also have the

    setCounter(data.counter);
    

    (which you shouldn’t have because you’re duplicating the state between React Query and vanilla React state, and any time you duplicate state, you’re asking for problems — that’s why caching is one of the famously hard problems in computer science).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search