RocketChat / EmbeddedChat

An easy to use full-stack component (ReactJS) embedding Rocket.Chat into your webapp
https://rocketchat.github.io/EmbeddedChat/docs
126 stars 252 forks source link

feat: use react-query to handle API requests #124

Closed Rec0iL99 closed 1 year ago

Rec0iL99 commented 1 year ago

Currently we are using the fetch API and side effects (useEffect) in components to fetch data from the RocketChat API and store the data like a cache in state management stores (Zustand). While this works, this can get complex in the future as new features get added and our state management size grows and complexity increases thus making maintenance of the code difficult.

We also have issues where multiple API requests are made than needed (refer #112). This might not be a problem when RocketChat server is hosted locally, but when a user hosts their RocketChat server on the cloud on services like AWS/Digitaocean multiple hits to the server can be problematic. RocketChat might have a rate limiter, but still it is bad client wise.

How can React Query help us?

It replaces using traditional fetch with component side effects and custom state management to handle API calls!

React query uses custom hooks like the useQuery hook where you would need to pass in the async function that does the API call. The hook then would send a JS object with properties such as isLoading, data, error. So now we don't need to check what the API request state is imperatively, react-query does the magic for us behind the scenes and returns the state such as, did the API call return an error (property error helps us here) etc.

  const { isLoading, error, data } = useQuery({
    queryKey: ["repoData"],
    queryFn: () =>
      fetch("https://api.github.com/repos/tannerlinsley/react-query").then(
        (res) => res.json()
      ),
  });

NOTE: Instead of fetch we can use any other APIs too like Axios, SuperAgent etc, which is pretty cool! NOTE: useQuery is for GET requests and useMutation is for POST requests.

How does it replace custom state management?

The first argument to the useQuery hook is the queryKey. This query key must be unique and it is a key that react-query uses to store our API calls data in a cache.

Example in our case:

Previous Behaviour: To get the RocketChat messages for a particular channel we use the api/v1/channels.messages endpoint and send in a GET request. This returns our messages array and we store it in a zustand store.

React-Query Behaviour: With react-query we can now use the useQuery hook to make our API call. Now after the request is successful, react-query will store the data in a cache using the query key we provided. Now if we want to get the messages in a different component, we can just use the useQuery hook again and except this time react-query won't make the API request instead it will search the cache and return us the data (messages). What if messages changed in the server? We can add cache timeouts which will invalidate the cache when the timeout is reached and react-query will have to make a request to the server to get the new data whenever useQuery is called anywhere the next time.

More details can be found in the React-Query docs. We can also do a lot of custom cache manipulation, but I think it is a thing for the future.

Addittional features:

If a query is called multiple times by a component, react-query will batch all the requests into one single request thus saving us a lot of API calls.

I have created a small component to illustrate this:

function SimpleComponent() {
  const [count, setCount] = useState(0);
  const { isLoading, error, data } = useQuery({
    queryKey: ["repoData"],
    queryFn: () =>
      fetch("https://api.github.com/repos/tannerlinsley/react-query").then(
        (res) => res.json()
      ),
  });

  if (data) {
    console.log(data);
  }

  return (
    <div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        {count}
      </button>
    </div>
  );
}

I clicked the count button which updates state multiple times in order to trigger a re-render of the component thus leading to multiple API calls but react-query batched it into one API call, but the console.log will happen because we are getting data from the cache as explained above.

Screenshot 2023-02-06 at 12 31 21 PM Screenshot 2023-02-06 at 12 32 09 PM

Not using react-query:

function SimpleComponent() {
  const [count, setCount] = useState(0);

  fetch("https://api.github.com/repos/tannerlinsley/react-query")
    .then((res) => res.json())
    .then((res) => console.log(res));

  return (
    <div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        {count}
      </button>
    </div>
  );
}
Screenshot 2023-02-06 at 12 37 50 PM Screenshot 2023-02-06 at 12 37 57 PM

NOTE: Two API calls are done together because of StrictMode enabled in React for the development build.

Current EmbeddedChat network calls

215328998-b5c91562-6197-4938-95b2-fe8310d94ca7

Rec0iL99 commented 1 year ago

@sidmohanty11 let me know your thoughts on this. If approved, please assign the issue to me. Thanks.

sidmohanty11 commented 1 year ago

@Rec0iL99 these are some really great points! I really like this idea. Let's start a discussion inside #embeddedchat channel.

umangutkarsh commented 1 year ago

Is this feature needs to be added @sidmohanty11 ? Please LMN. Thanks

abhinavkrin commented 1 year ago

Closed since we have solved the issue of multiple API calling. As an update: We have moved the API to its own package which uses real-time DDP communication for message updates, user status, UI interaction updates, and API fetching for message actions - start, pin ,delete report, edit, send

Rec0iL99 commented 1 year ago

Thanks for the update @abhinavkrin