A lot of React applications out there run for state management solutions such as Redux or Mobx and use it mostly for server side state like 'isLoading', 'isError', etc. We need to stop mixing between server state and client state. Don't get me wrong, client state is important, but 70% of the state is actually a server state. In this talk I will demonstrate how we can encapsulate server state using our own custom hook or using a (perfect) solution such as react-query.
Stop Abusing Client State Management
AI Generated Video Summary
This Talk discusses state management abuse and the use of React Query for API handling in React applications. The speaker demonstrates implementing loading indicators, custom hooks, caching mechanisms, and introduces React Query as a powerful tool for fetching, caching, and loading data. The conclusion emphasizes that React Query simplifies API handling without the need for complex state management tools like Redux or MobX.
Hi everyone. My name is Nir. I'm 34 years old from Israel, married to a frat and father to three little wonderful girls. I've built web apps since I was a kid. Currently I run a consulting company in webapps development, both in the world of web 2.0 and web 3.0.
Hi everyone. My name is Nir. I'm 34 years old from Israel, married to a frat and father to three little wonderful girls, Baar and Adas. I've built web apps since I was a kid. My first website was the Biggest Israeli Pokemon Fans website and it was a great project. Since then I had my BSc in Computer Science at the Hebrew University of Jerusalem. I worked as a full-stack at Intel for a couple of years. Afterwards I was a frontend team leader at Curv, a blockchain startup which acquired by PayPal. Currently I run a consulting company in webapps development, both in the world of web 2.0 which you're all familiar with, and also in the web 3.0 world.
2. State Management Common Abuse
Let's talk about state management common abuse. We choose our favorite state management tool, connect components to backend API functionality, and start using state management awesomeness. I created a React application, declared an API function called fetchPokemon, and used a Pokemon API service to fetch relevant data. I added a two-second delay, fetched the data, and implemented the UI to display the Pokemon data and a button to fetch random Pokemon. Finally, I added some styling to the UI.
Enough about me, let's talk about state management common abuse. So we've all been there, starting a React application, choosing our favorite state management tool, like Redux or Morbix, or many others out there, and connecting our components using these tools to our backend API functionality and start using state management awesomeness.
Let's see it in action. I created a simple React application using code sandbox, and I'll start by declaring an API function called fetchPokemon, which will get in the parameters the Pokemon name, and we will use a Pokemon API service to fetch the relevant Pokemon data. I wrapped it with a promise to make sure we get some delay in the response to mock a common behavior of API functions. So there's a setTimeout, and I set it later to two seconds of delay.
Now I'll do the fetching. We have the Pokemon name, handle the response, which is in a JSON format, so I'll use the JSON, and return the data. Cool. Great. Let's add the two seconds delay I talked about, and now I can call the endpoint in the UI, declaring the state PokemonData and setPokemonData using the useState hook, and creating a fetch random Pokemon function so the app won't be boring.
So I'll be using or I'll be choosing out of three common Pokemons, Pikachu, Charizard, and Squirtle. We will use some random logic using the math library, so multiplying math random with the array length minus 1 and round it up. And now I can use it. I'm calling the fetchPokemon with the chosen Pokemon name. Cool. Return the data. Great. And now for the best part, the UI. So I will declare a div container and display the Pokemon data in a div and create a button for using the fetch function. So in the div, I will display the Pokemon name as well as its image using the sprites data which we get from the Service API. Put it in the image source. And great! Back to the button. I will attach the fetch random Pokemon, which we implemented. And let's add some text. And let's add some alternative text to remove the annoying warning of the Kot Sandbox. Add some typo here. And test it. Cool! We've got Squidward. Now let's add some style, just to move the text a little bit up above.
3. Implementing Loading Indicator
And we are done! We've got Toysard, and Squirtle, and Pikachu. I will use user reducer for simplicity reasons. Let's add a state for error data and a success state. Let's adapt the UI to use our new state variable. Implement the loading indicator easily. Fix the bug in the SetTimeout.
And we are done! We've got Toysard, and Squirtle, and Pikachu. Great! I think to myself, an app is not an app without some loading indicator. That's the part where most developers will use Redux or some other solution to hold these kind of states.
I will use user reducer for simplicity reasons, which is very similar to Redux reducers. So I'll create a Pokemon Reducer, getting a state and an action, and switching the action type. So first let's handle a default case, throw in some error, and now add the common loading state. Status is loading, and great. But what if I get an error? So let's add a state for this as well, with the error data, and surely we need a success state to hold the data getting from the Service API. And since we can be sometimes not in neither of these states, let's add an idle state and declare an initial state to begin with.
Now I can just use it in the application, instead of the regular useState. So extracting the state and the dispatch function out of the reducer, using the PokémonReducer and the initial state. And let's adapt the UI to use our new state variable. So when I'm idle, I will display some text, click to fetch a Pokémon, and on success I want to display the Pokémon data. So let's implement this as well. Pay attention that Pokémon data doesn't exist anymore, so we'll replace it with the state variable. And on error, I want to display the error message, which I saved in the state. And finally, I want to disable the button while I'm on loading state.
Now I can finally implement the loading indicator easily. So what I will do, I will switch the button text to loading when I'm in the status of loading. And display the FetchPokemon text when I'm not. Fetching. Okay, and don't forget to dispatch the right state when loading. And when receiving the data. With the data. And when encountering some error. Let's hit the refresh and test it. And I see we don't get the loading indicator. So I think I know why we have some bug in the SetTimeout. Let's fix that. Great.
4. Implementing Custom Hook
Test again and we got this. Just the style. We missed the style. So let's move the style to the inner div we implemented. Usually for this kind of async function, developers write different reducers, but I know a better way to share some logic. Custom hooks. Let's create one. Let's take the reducer, move it down and declare a custom hook named userSync. And now let's create a function called execute that will be responsible for changing the state using the dispatch.
Test again and we got this. Just the style. We missed the style. So let's move the style to the inner div we implemented. And great. Test again. Cool.
Usually for this kind of async function, developers write different reducers, but I know a better way to share some logic. Custom hooks. So let's create one. So let's take the reducer, move it down and declare a custom hook which I will name userSync. Okay. The userSync will get an async function. And actually, we can use our reducer. Let's just rename it to something more generic. So fetchReducer might be better. And we can use it in our hook. Great.
And now let's create a function that will be responsible to changing the state using the dispatch. So we'll name it execute. And this function will get parameters of course. And start by dispatching the loading state. Then we will call the async function argument we got with the params. And onSuccess, dispatch the success state. We have the data, of course. And last but not least, when getting an error, we will dispatch the error state, similar to what we did earlier. Quit error with the error data. Now I will have the execute function with use callback to avoid re-renders, except the time, of course, when the function does change. And finally we return from the hook the execute function and the state, which we can use in the application. And let's actually use it.
5. Extracting Execute and State
Let's extract execute and state out of the user sync custom hook and pass it our function, fetchRandomPokemon. This pattern is not my invention but rather something I found in a library called user sync, which is implemented very similarly to what we did. It's an amazing library with many other custom hooks.
So let's extract execute and state out of the user sync custom hook, and we need to pass it our function, so fetchRandomPokemon. And let's make some adjustment. We move in the dispatch and move in this function out of the application. Call in execute. Great. And this is working nice. So actually I didn't invent any of that. I just use a pattern I met in the school library, use hooks, and you can see here user sync, which is implemented very similar to what we did. And it's actually amazing and this library has many other custom hooks. I really suggest checking it out.
6. Implementing Cache Mechanism
Let's add a cache mechanism to store the fetched data and implement caching. If the data exists in the cache, it will be returned. Otherwise, it will be fetched using an async function and saved in the cache. We can also add invalidation to reset the cache when the data is not up to date. This allows us to re-fetch the data when necessary, such as when a timer is connected.
So with your permission and to make some point, I'm getting rid of the Pokemon randomness for a while and fetching only the great Pikachu. Notice that I am fetching the data every time I click, even though I have the same data all the time. Pikachu. So I think it will be nice to add some cache mechanism. I'll add a cache variable which will hold the data and add some flag named with cache and let's call it with true. And let's implement the caching. So if the data does exist, I will return it from the cache. Right, if it exists. And otherwise, I will fetch it using the async function and save it in the cache. Great. Let's test it. Now it's fetching, and for the second time and third time and so on, it won't fetch again because it's using the cached data. And let's add some invalidation to make sure we support when the data is not up to date. So if invalidate flag is true, I'm resetting the cache. Let's add it to the dependencies array. And now let's set the flag to true. And if I click over and over again, I'm fetching. This is good when I want to tell my custom hook that the data is not relevant anymore and I want to re-fetch it. So I can connect a timer to it or something like that.
7. Introducing React Query
React Query is my first choice for dealing with API LL. It provides a built-in hook called Use Query that includes everything we implemented in UserSync. We can replace UserSync with Use Query, passing the Query key and query function as parameters. The result includes data, error indicators, and a refresh function. With React Query, we get caching out of the box.
And you know what? It's so cool to add some retry mechanism. And I think you know where I'm going with it. We don't have to work this hard, although it is fun. So I want you to meet React Query, which is my first choice when dealing with the API LL.
So first I'll wrap it up real quick with the React Query provider. Coming from the React Query library, which I just installed. And wrapping the entire application with the provider and the client. And now I can use the built-in hook from React Query called Use Query. So this hook actually holds everything we just implemented in UserSync, including the cache system, the retry mechanism, the invalidation, and many other cool functionalities.
We can get rid of the UserSync and let's use Use Query instead. It has two parameters, actually. The first one is the Query key, which serves as the id of the query. And the second one is the query function, as similar as the UserSync hook we just implemented. So, this will be the FetchPokemon function, currently with Pikachu. And let's extract everything out of the result. So we have the data. We have some indicators, like if there was an error, if it's loading, and so on. And let's use it in the UI, make some adjustments. Great, the error. We can remove this and use the IsError flag, which we got for free. And the error variable, which we can extract as well. And the refresh function, which will replace the execute from the user's sync hook. Great, isLoading here instead. And here as well. Not IsLoading. Cool. And great, we got the caching out of the box. Let's clear things out. All of this. Oh, this is such a good feeling.
8. Conclusion and Recommendation
And we can even use the fetch random Pokemon once again and get in the cache. So here is the React Query library website. It has some cool documentation and functionality that makes handling the API layer simple. In conclusion, we connected a simple API function to a simple UI. As the UI grows, handling API functionality becomes more complicated. We don't need fancy tools like Redux or MobX for server state handling. React Query is a helpful solution for fetching, caching, and loading in every React application.
And we can even use the fetch random Pokemon once again and get in the cache. Great. Everything works.
So here is the React Query library website. Which is very great. It has some cool documentation. And I really suggest going over it. It has so much functionality which is useful. And really makes things very simple relating to handling the API layer. So it's really worth checking.
So in conclusion, what we did here was taking a simple API function and connecting it to a really simple UI. But as you can imagine, as the UI gets bigger and bigger, it gets more and more complicated to handle this kind of API functionality. And we don't need fancy tools such as Redux, MobX or many others out there to handle what is called server states. These tools are meant for client state operations such as if the dialog is open, if a menu bar is open, if I'm logged in or not logged in. This is client state. In any case of server state handling, such as fetching, caching, loading and so on, I really suggest either build some similar solution to use query or use async, custom hooks we just saw or really consider using React query which is really, really helpful in actually every application, every React application I saw so far. So hope you had a great time.