Back to Blogs

Async and You Shall Receive: A Deep Dive into React's Asynchronous Wizardry

2023-08-27 10 min read
React
Asynchronous
Frontend

Async and You Shall Receive: A Deep Dive into React's Asynchronous Wizardry

The Neural Network Puzzle game project was developed to demonstrate effective implementation of React features and frontend best practices, particularly in handling asynchronous operations. This article explores the techniques used in the project and examines how React internally manages these features.

1. useCallback: Memoization for Optimized Rendering

The project utilizes React's useCallback hook to optimize the createPuzzle function:

const createPuzzle = useCallback(async () => {
  try {
    setIsLoadingPuzzle(true);
    const response = await axios.post('/api/proxy?endpoint=/game/create_puzzle', { level });
    setPuzzle(response.data);
    setTestPredictions([]);
    setNetworkStructure({
      input: response.data.input_size,
      hidden: [1],
      output: response.data.output_size
    });
  } catch (error) {
    console.error('Error creating puzzle:', error);
  } finally {
    setIsLoadingPuzzle(false);
  }
}, [level, setPuzzle, setTestPredictions, setNetworkStructure, setIsLoadingPuzzle]);

React's Internal Handling: React maintains a memoized version of this function and only recreates it when one of the dependencies in the array changes. Internally, React uses the memoizedState property of the fiber node to store the memoized function and its dependencies.

2. Zustand for Global State Management

The project employs Zustand for managing global state:

import { create } from 'zustand';

interface GameState {
  level: number;
  puzzle: PuzzleData | null;
  isTraining: boolean;
  setLevel: (level: number) => void;
  setPuzzle: (puzzle: PuzzleData | null) => void;
  setIsTraining: (isTraining: boolean) => void;
}

const useGameStore = create<GameState>((set) => ({
  level: 1,
  puzzle: null,
  isTraining: false,
  setLevel: (level) => set({ level }),
  setPuzzle: (puzzle) => set({ puzzle }),
  setIsTraining: (isTraining) => set({ isTraining }),
}));

React's Integration with Zustand: Zustand creates a store outside of React's component tree, using React's useState and useEffect hooks internally to trigger re-renders when the state changes.

3. useEffect for Side Effects and Cleanup

useEffect is crucial for handling side effects in React applications:

useEffect(() => {
  let isMounted = true;
  const fetchPuzzle = async () => {
    try {
      const response = await axios.get(`/api/puzzles/${level}`);
      if (isMounted) setPuzzle(response.data);
    } catch (error) {
      if (isMounted) console.error('Fetch error:', error);
    }
  };
  fetchPuzzle();
  return () => { isMounted = false; };
}, [level]);

React's Internal Handling: React associates each useEffect call with the fiber node of the component. During the commit phase, React schedules the effect to run after the browser has painted.

4. Error Boundaries for Graceful Error Handling

Error Boundaries could enhance error handling:

class PuzzleErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Puzzle error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>An error occurred in the puzzle.</h1>;
    }
    return this.props.children;
  }
}

5. Suspense for Data Fetching (Potential Implementation)

Suspense could be implemented for improved loading states:

const PuzzleWithSuspense = React.lazy(() => import('./PuzzleComponent'));

function EnhancedPuzzleGame() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <PuzzleWithSuspense />
    </Suspense>
  );
}

React's Internal Handling: Suspense works by catching promises thrown by components during rendering. When a component suspends, React captures this promise and renders the nearest Suspense boundary's fallback.