State Management

State Management Patterns

Web (React + Apollo)

// Example React component using Apollo's `useQuery` hook
import { useQuery, gql } from '@apollo/client';

const GET_OBJECTS = gql`
  query getMyObjects {
    myObjects {
      id
      name
      main_image_url
    }
  }
`;

export const ObjectList = () => {
  const { data, loading, error } = useQuery(GET_OBJECTS);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return (
    <ul>
      {data.myObjects.map((o: any) => (
        <li key={o.id}>{o.name}</li>
      ))}
    </ul>
  );
};

State Management Implementation

The logic for optimistic updates resides within the Apollo Client cache (or an additional Zustand store if more complex local state is required).

// Conceptual React hook for an optimistic moveObject mutation
import { gql, useMutation } from '@apollo/client';

const MOVE_OBJECT = gql`
  mutation MoveObject($objectId: ID!, $newContainerId: ID!) {
    moveObject(objectId: $objectId, newContainerId: $newContainerId) {
      id
      containerId
    }
  }
`;

export const useMoveObject = () => {
  const [mutate, { error }] = useMutation(MOVE_OBJECT, {
    optimisticResponse: ({ objectId, newContainerId }) => ({
      moveObject: {
        __typename: 'SpatialObject',
        id: objectId,
        containerId: newContainerId,
      },
    }),
    update(cache, { data: { moveObject } }) {
      // Update any necessary queries in cache here
    },
  });

  return { moveObject: mutate, error };
};

Technology Stack

Web Application

  • Framework: React 18 (function components + hooks)
  • GraphQL Client: Apollo Client (or similar)
  • State Management: React Context + Apollo Client cache (optionally Zustand)
  • UI Components: Tailwind CSS + Custom Design System
  • Build Tool: Vite (via CDN for gradual migration)
  • PWA: Service Worker for offline support

Native Mobile Apps (Future)

  • Platform: Native iOS (Swift) / Android (Kotlin)
  • GraphQL Client: Native Apollo Client
  • State Management: Platform-native reactive patterns

Status State Management

Status Definitions Cache

Status definitions should be loaded once on app startup and cached for the session:

// Global status state hook
export const useStatusDefinitions = () => {
  const { data, loading, error } = useQuery(GET_STATUS_DEFINITIONS, {
    // Cache for the entire session
    fetchPolicy: 'cache-first',
    // Persist in local storage for offline usage
    notifyOnNetworkStatusChange: false
  });

  const statusByKey = useMemo(() => 
    data?.statusDefinitions?.reduce((acc, status) => {
      acc[status.key] = status;
      return acc;
    }, {}) || {}, [data]
  );

  const statusesByCategory = useMemo(() => 
    data?.statusDefinitions?.reduce((acc, status) => {
      if (!acc[status.category]) acc[status.category] = [];
      acc[status.category].push(status);
      return acc;
    }, {}) || {}, [data]
  );

  return { 
    statusDefinitions: data?.statusDefinitions || [],
    statusByKey,
    statusesByCategory,
    loading,
    error 
  };
};

Status Validation State

// Status validation hook with debouncing
export const useStatusValidation = () => {
  const [validationState, setValidationState] = useState({
    isValidating: false,
    result: null,
    error: null
  });

  const [validateStatusCombination] = useLazyQuery(VALIDATE_STATUS_COMBINATION, {
    onCompleted: (data) => {
      setValidationState({
        isValidating: false,
        result: data.validateStatusCombination,
        error: null
      });
    },
    onError: (error) => {
      setValidationState({
        isValidating: false,
        result: null,
        error
      });
    }
  });

  const validate = useCallback(
    debounce((statusKeys: string[]) => {
      setValidationState(prev => ({ ...prev, isValidating: true }));
      validateStatusCombination({ variables: { statusKeys } });
    }, 300),
    [validateStatusCombination]
  );

  return { validationState, validate };
};

Object Status Updates

// Optimistic status update hook
export const useUpdateObjectStatus = () => {
  const [updateObject] = useMutation(UPDATE_OBJECT_STATUS, {
    optimisticResponse: ({ objectId, statuses }) => ({
      updateObject: {
        __typename: 'ObjectInstance',
        id: objectId,
        statuses
      }
    }),
    update(cache, { data: { updateObject } }) {
      // Update any cached object queries
      cache.modify({
        id: cache.identify(updateObject),
        fields: {
          statuses() {
            return updateObject.statuses;
          }
        }
      });
    }
  });

  return updateObject;
};