Async Operations

Handle asynchronous data fetching and updates with built-in loading states.

Async Data Fetching (Getters)

Getters allow you to fetch data asynchronously with automatic loading states:

const [useStorken] = create({
  getters: {
    // Simple fetch
    user: async () => {
      const response = await fetch('/api/user')
      return response.json()
    },
    
    // With parameters
    posts: async (storken, userId: string, page = 1) => {
      const response = await fetch(
        `/api/users/${userId}/posts?page=${page}`
      )
      return response.json()
    },
    
    // With error handling
    profile: async (storken, id: string) => {
      try {
        const response = await fetch(`/api/profiles/${id}`)
        if (!response.ok) {
          throw new Error('Profile not found')
        }
        return response.json()
      } catch (error) {
        console.error('Failed to fetch profile:', error)
        return null
      }
    }
  }
})

Using Getters in Components

function UserProfile() {
  const [user, , , loading, refetch] = useStorken('user')
  
  useEffect(() => {
    // Initial fetch
    refetch()
  }, [refetch])
  
  if (loading) {
    return <Spinner />
  }
  
  if (!user) {
    return <div>No user found</div>
  }
  
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => refetch()}>
        Refresh
      </button>
    </div>
  )
}

Async Updates (Setters)

Setters allow you to perform async operations when state changes:

const [useStorken] = create({
  setters: {
    // Save to backend
    profile: async (storken, profile: Profile) => {
      const response = await fetch('/api/profile', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(profile)
      })
      
      if (!response.ok) {
        throw new Error('Failed to save profile')
      }
      
      // Update related states
      const updated = await response.json()
      storken.sky.set('lastUpdated', new Date())
      
      return updated
    },
    
    // Delete with confirmation
    todo: async (storken, todo: Todo | null, action?: string) => {
      if (action === 'delete' && todo) {
        await fetch(`/api/todos/${todo.id}`, {
          method: 'DELETE'
        })
        
        // Remove from list
        const todos = storken.sky.get('todos') || []
        storken.sky.set('todos', 
          todos.filter(t => t.id !== todo.id)
        )
      }
    }
  }
})

Using Setters in Components

function ProfileEditor() {
  const [profile, setProfile, , saving] = useStorken('profile')
  
  const handleSave = async (updates: Partial<Profile>) => {
    try {
      await setProfile({ ...profile, ...updates })
      toast.success('Profile saved!')
    } catch (error) {
      toast.error('Failed to save profile')
    }
  }
  
  return (
    <form onSubmit={e => {
      e.preventDefault()
      handleSave(formData)
    }}>
      {/* Form fields */}
      <button type="submit" disabled={saving}>
        {saving ? 'Saving...' : 'Save'}
      </button>
    </form>
  )
}

Loading States

Storken automatically manages loading states for async operations:

function DataComponent() {
  const [
    data,      // The current value
    setData,   // Setter function
    reset,     // Reset function
    loading,   // Loading state (true during async ops)
    refetch    // Trigger getter again
  ] = useStorken('data')
  
  // Loading states for different scenarios
  if (loading && !data) {
    // Initial load
    return <FullPageLoader />
  }
  
  if (loading && data) {
    // Refreshing existing data
    return (
      <div className="relative">
        <div className="opacity-50">{/* Show existing data */}</div>
        <LoadingOverlay />
      </div>
    )
  }
  
  if (!loading && !data) {
    // No data available
    return <EmptyState onRetry={refetch} />
  }
  
  // Data loaded successfully
  return <DataDisplay data={data} onRefresh={refetch} />
}

Error Handling

Implement robust error handling for async operations:

// Store configuration with error handling
const [useStorken] = create({
  getters: {
    data: async (storken, id: string) => {
      try {
        const response = await fetch(`/api/data/${id}`)
        
        if (!response.ok) {
          // Store error state
          storken.sky.set('dataError', {
            status: response.status,
            message: `Failed to fetch data: ${response.statusText}`
          })
          return null
        }
        
        // Clear any previous errors
        storken.sky.set('dataError', null)
        return response.json()
        
      } catch (error) {
        // Network or other errors
        storken.sky.set('dataError', {
          message: error.message || 'An unexpected error occurred'
        })
        return null
      }
    }
  }
})

// Component with error handling
function DataWithError() {
  const [data, , , loading, refetch] = useStorken('data')
  const [error] = useStorken('dataError')
  
  useEffect(() => {
    refetch('123')
  }, [refetch])
  
  if (loading) return <Loading />
  
  if (error) {
    return (
      <ErrorDisplay 
        message={error.message}
        onRetry={() => refetch('123')}
      />
    )
  }
  
  if (!data) return <NoData />
  
  return <DataDisplay data={data} />
}

Polling & Real-time Updates

Implement polling or real-time updates with Storken:

// Polling implementation
function usePolling(key: string, interval = 5000) {
  const [data, , , loading, refetch] = useStorken(key)
  
  useEffect(() => {
    // Initial fetch
    refetch()
    
    // Set up polling
    const timer = setInterval(() => {
      refetch()
    }, interval)
    
    return () => clearInterval(timer)
  }, [refetch, interval])
  
  return { data, loading, refetch }
}

// WebSocket integration
function useRealtimeData(key: string) {
  const [data, setData] = useStorken(key)
  
  useEffect(() => {
    const ws = new WebSocket('wss://api.example.com/stream')
    
    ws.onmessage = (event) => {
      const update = JSON.parse(event.data)
      
      // Update state with real-time data
      setData(prev => ({
        ...prev,
        ...update
      }))
    }
    
    ws.onerror = (error) => {
      console.error('WebSocket error:', error)
    }
    
    return () => ws.close()
  }, [setData])
  
  return data
}

Caching & Deduplication

Implement caching to avoid unnecessary requests:

// Cache plugin
const cachePlugin = (ttl = 60000) => (storken) => {
  const cache = new Map()
  const timestamps = new Map()
  
  // Intercept getter calls
  const originalGetter = storken.opts?.getter
  if (originalGetter) {
    storken.opts.getter = async (...args) => {
      const cacheKey = JSON.stringify(args)
      const cached = cache.get(cacheKey)
      const timestamp = timestamps.get(cacheKey)
      
      // Return cached if still valid
      if (cached && timestamp && Date.now() - timestamp < ttl) {
        return cached
      }
      
      // Fetch fresh data
      const fresh = await originalGetter.call(storken, ...args)
      cache.set(cacheKey, fresh)
      timestamps.set(cacheKey, Date.now())
      
      return fresh
    }
  }
  
  return {
    clear: () => {
      cache.clear()
      timestamps.clear()
    },
    invalidate: (key?: string) => {
      if (key) {
        cache.delete(key)
        timestamps.delete(key)
      } else {
        cache.clear()
        timestamps.clear()
      }
    }
  }
}

// Use with caching
const [useStorken] = create({
  getters: {
    user: async (storken, id) => {
      // This will be cached
      const response = await fetch(`/api/users/${id}`)
      return response.json()
    }
  },
  plugins: {
    cache: cachePlugin(30000) // 30 second TTL
  }
})

🚀 Performance Tips

  • • Use caching for frequently accessed data
  • • Implement request deduplication
  • • Show stale data while refreshing
  • • Use optimistic updates for better UX
  • • Handle errors gracefully
  • • Consider pagination for large datasets