State Manager

The store variable is instance of the StateManger class is designed to provide a robust and efficient way to manage the state within applications. It encapsulates functionality for state initialization, updates, listening for state changes, and persisting the state across sessions using local storage. With its singleton pattern, it ensures that only one instance of the state manager exists throughout the application, thus maintaining a centralized state management system.

Constructor

The constructor initializes the StateManager with an optional initial state. It prepares the state management system by setting up an internal state and a list of listeners for state changes.

  • Parameters: initialState - An optional initial state object for the StateManager. Defaults to an empty object if not provided.

Methods

getInstance(initialState)

A static method that ensures a singleton instance of the StateManager. It returns an existing instance or creates a new one with the provided initial state, also triggering the loadState method to initialize the state with any persisted data.

  • Parameters: initialState - An optional initial state for the new instance. Used only if an instance does not already exist.
  • Returns: The StateManager instance.

setState(update)

Merges the provided updates into the current state, notifies all subscribed listeners about the change, and persists the updated state to local storage. This method allows for efficient state updates with immediate feedback to subscribed components.

  • Parameters:
    • update - An object containing the updates to be merged into the current state.

subscribe(listener)

Subscribes a new listener function to state changes. The listener is immediately called with the current state upon subscription and provided with a function to unsubscribe.

  • Parameters: listener - The function to be called on state updates.
  • Returns: A function to unsubscribe the provided listener.

saveState()

Persists the current state to local storage, allowing the state to be retained across page reloads and browser sessions, thereby enhancing the user experience by maintaining application continuity.

loadState()

Loads the state from local storage, if available, and updates the state accordingly. This method is automatically called during the instance creation to ensure the state is initialized from persisted data if present.

resetState(id?: string)

Resets the state based on the provided id or clears the entire state if no id is provided. The updated state is also persisted in local storage.

  • With id: Removes only the specified part of the state (e.g., resetState("user") resets the user key).
  • Without id: Resets the entire state to an empty object and clears the persisted state from local storage.

This is useful for scenarios such as user logouts (clear entire state) or refreshing specific parts of the state dynamically.

State Management for UI Components

This section illustrates how the StateManager class can be applied to manage and persist the state of UI components, such as sidebar visibility and dropdown states, across page reloads. The example demonstrates how to integrate state management into UI interactions to enhance user experience by maintaining consistent UI states across sessions.

Overview

Proper management of UI component states, like a sidebar's open/close status or the expansion state of dropdown menus, significantly improves user experience by ensuring UI consistency. The following example showcases the use of StateManager to achieve this goal efficiently.

Your First Store

To create a new store instance, you can use the global store variable, which is available throughout the application. The following code snippet demonstrates how to create a new store instance with an initial state and subscribe to state changes. To observe the changes, open your developer console, navigate to the Application tab, and look for the key appState_59E13 in the local storage. This key represents the store's state saved in the local storage.

<button id="aside-button" class="p-2 bg-blue-500 text-white rounded">
      Toggle Sidebar
  </button>
  
  <script>
      const sideButton = document.getElementById('aside-button');
  
      sideButton.addEventListener('click', () => {
          store.setState({
              sidebarOpen: !store.state.sidebarOpen
          });
      });
  </script>

Open and Close Sidebar

<div class="relative z-50">
        <button id="aside-button" class="p-2 bg-blue-500 text-white rounded fixed top-4 left-4">
            Toggle Sidebar
        </button>
    </div>
    <aside class="fixed top-0 left-0 w-64 h-full bg-gray-100 shadow transform -translate-x-full transition-transform z-40">
        <div class="p-4">This is the sidebar content.</div>
    </aside>
    
    <script>
        const aside = document.querySelector('aside');
        const button = document.querySelector('#aside-button');
    
        // Initialize sidebar state on page load
        if (store.state.sidebarOpen) {
            aside.classList.remove('-translate-x-full');
        } else {
            aside.classList.add('-translate-x-full');
        }
    
        // Button click event listener to toggle sidebar
        button.addEventListener('click', () => {
            store.setState({
                sidebarOpen: !store.state.sidebarOpen
            });
            aside.classList.toggle('-translate-x-full', !store.state.sidebarOpen);
        });
    </script>

Managing Dropdown States

<div class="relative z-50">
      <button id="dropdown-button" class="p-2 bg-blue-500 text-white rounded">
          Toggle Dropdown
      </button>
      <div id="dropdown-menu" class="absolute w-48 bg-white border border-gray-200 shadow hidden">
          <ul class="py-2">
              <li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 1</li>
              <li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 2</li>
              <li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 3</li>
          </ul>
      </div>
  </div>
  
  <script>
      const dropdownButton = document.querySelector('#dropdown-button');
      const dropdownMenu = document.querySelector('#dropdown-menu');
  
      // Initialize dropdown state on page load
      if (store.state.dropdownOpen) {
          dropdownMenu.classList.remove('hidden');
          dropdownMenu.classList.add('block');
      } else {
          dropdownMenu.classList.remove('block');
          dropdownMenu.classList.add('hidden');
      }
  
      // Button click event listener to toggle dropdown
      dropdownButton.addEventListener('click', () => {
          store.setState({
              dropdownOpen: !store.state.dropdownOpen
          });
  
          if (store.state.dropdownOpen) {
              dropdownMenu.classList.remove('hidden');
              dropdownMenu.classList.add('block');
          } else {
              dropdownMenu.classList.remove('block');
              dropdownMenu.classList.add('hidden');
          }
      });
  
      // Close dropdown if clicking outside
      document.addEventListener('click', (event) => {
          if (!dropdownButton.contains(event.target) && !dropdownMenu.contains(event.target)) {
              store.setState({
                  dropdownOpen: false
              });
              dropdownMenu.classList.remove('block');
              dropdownMenu.classList.add('hidden');
          }
      });
  </script>

Persisting State Across Sessions

The examples above demonstrate effective strategies for saving and restoring UI component states using the StateManager. This approach ensures that the sidebar's visibility and the state of dropdown menus are consistent across page reloads, thereby providing a seamless and engaging user experience.

Conclusion

Leveraging the StateManager for UI state management enables developers to create more interactive and user-friendly web applications. By persisting UI states, applications can maintain user context and preferences across sessions, enhancing the overall usability and satisfaction.