State and State Management: Client, Server, or Both? Simplified for Developers
Mastering the Flow of Information with .NET and React Examples
State and state management are at the heart of building dynamic web applications. Whether you’re a beginner or an experienced developer, understanding how and where to manage state—on the client, server, or both—is critical to creating scalable and performant applications.
In this blog, we’ll break down state, explore various ways to manage it, and explain when to use client-side, server-side, or hybrid state management. We’ll also include examples using .NET and React to illustrate these concepts.
What is State?
Think of state as the memory of an application. It stores data about what is happening in the app at a given moment, allowing the app to react to user actions and maintain continuity.
👋 Become 1% better at .NET Full Stack development every day.
👆 https://dotnet-fullstack-dev.blogspot.com/
â™» Restack it on if this made your minute count!
For example:
A user logs into an app: their authentication status is state.
A shopping cart holds selected items: the cart contents are state.
State Categories
Client-Side State: Managed in the browser (e.g., using React or local Storage).
Server-Side State: Managed on the server (e.g., using sessions or databases).
Hybrid State: A combination of both.
Client-Side State Management
What It Is:
All data is stored and managed within the browser. This is ideal for scenarios where the data is ephemeral (e.g., UI updates) or specific to the user’s session.
Common Tools:
React’s
useState
,useReducer
, and Context API.Browser storage like
localStorage
andsessionStorage
.
Example: React Client-Side State
Scenario: Managing a Counter in React
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
export default Counter;
Key Points:
The state (
count
) is stored in the browser.It resets when the page is refreshed because it's not persistent.
Server-Side State Management
What It Is:
The server stores and manages state. This is best for critical data like authentication or application-wide state that multiple users can access.
Common Tools:
Sessions or cookies in .NET.
Persistent storage like databases.
Example: Managing Authentication in .NET
Backend: .NET Core Authentication
public class AuthController : ControllerBase
{
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
if (request.Username == "user" && request.Password == "pass")
{
HttpContext.Session.SetString("User", request.Username);
return Ok("Login successful");
}
return Unauthorized("Invalid credentials");
}
[HttpGet("user")]
public IActionResult GetUser()
{
var user = HttpContext.Session.GetString("User");
return user != null ? Ok($"Hello, {user}!") : Unauthorized("No user logged in");
}
}
Key Points:
State (user authentication) is stored on the server using sessions.
It is persistent across requests but scoped to the user.
Hybrid State Management
What It Is:
A combination of client-side and server-side state management. This approach is common in modern applications that need both speed and reliability.
When to Use It:
When you need fast UI updates but want to persist critical data on the server.
Example: Shopping cart items stored on the client for speed and synchronized with the server for persistence.
Example: Hybrid State for a Shopping Cart
Client: React Shopping Cart Component
import React, { useState, useEffect } from "react";
function ShoppingCart() {
const [cart, setCart] = useState([]);
useEffect(() => {
// Fetch initial cart from the server
fetch("http://localhost:5000/api/cart")
.then((response) => response.json())
.then((data) => setCart(data));
}, []);
const addItem = (item) => {
setCart([...cart, item]);
// Update server
fetch("http://localhost:5000/api/cart", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(item),
});
};
return (
<div>
<h1>Shopping Cart</h1>
<ul>
{cart.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
<button onClick={() => addItem({ name: "New Item" })}>Add Item</button>
</div>
);
}
export default ShoppingCart;
Backend: .NET Cart API
[ApiController]
[Route("api/cart")]
public class CartController : ControllerBase
{
private static List<string> _cart = new();
[HttpGet]
public IActionResult GetCart()
{
return Ok(_cart);
}
[HttpPost]
public IActionResult AddToCart([FromBody] string item)
{
_cart.Add(item);
return Ok("Item added");
}
}
Key Points:
The cart state is stored both on the client (browser) and the server.
Updates are synchronized between the client and server.
Explaining to a Layman
Client-Side State: Imagine writing notes on a whiteboard in your room. It’s fast and only you can see it. If you leave the room (refresh the page), the notes disappear unless you save them elsewhere.
Server-Side State: Think of storing your notes in a locker at a public library. The library (server) keeps your notes safe, and you can retrieve them anytime from any room (device).
Hybrid State: You keep quick notes on your whiteboard (client) and copy important ones to your locker (server) for safekeeping.
Best Practices
Use Client-Side State for UI Responsiveness:
Ideal for temporary, session-specific, or lightweight data.
Use Server-Side State for Persistence and Security:
Best for authentication, user profiles, and shared data.
Combine Both for Complex Applications:
Hybrid approaches provide the best of both worlds.
Conclusion
State management is about deciding where your app’s memory should live—on the client, the server, or both. By understanding the strengths and weaknesses of each approach, you can design responsive and reliable applications.
Have questions or a favourite state management trick? Could you share it in the comments?