npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@well-prado/blok-react-sdk

v1.0.5

Published

Type-safe React hooks and components for Blok Framework workflows with automatic code generation

Downloads

13

Readme

@well-prado/blok-react-sdk

Type-safe React hooks and components for Blok Framework workflows with automatic code generation.

npm version License: MIT

Overview

The @well-prado/blok-react-sdk provides a complete React integration for Blok Framework applications. It includes type-safe hooks, context providers, and utilities that make it easy to interact with Blok workflows from React components.

Features

  • 🎣 React Hooks - Custom hooks for workflow execution with caching and error handling
  • 🔄 TanStack Query Integration - Built on React Query for optimal data fetching
  • 🛡️ Type Safety - Full TypeScript support with auto-generated types
  • 🎯 Context Providers - Easy state management across your app
  • Performance - Automatic caching, deduplication, and background updates
  • 🔧 Extensible - Easy to customize and extend

Installation

npm install @well-prado/blok-react-sdk @tanstack/react-query

Peer Dependencies

This package requires the following peer dependencies:

  • react: ^18.0.0 || ^19.0.0
  • @tanstack/react-query: ^5.0.0

Quick Start

1. Setup the Provider

Wrap your app with the BlokProvider:

import { BlokProvider } from "@well-prado/blok-react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <BlokProvider
        config={{
          baseURL: "http://localhost:4000",
          timeout: 10000,
        }}
      >
        <YourApp />
      </BlokProvider>
    </QueryClientProvider>
  );
}

2. Use Workflows in Components

import {
  useWorkflowQuery,
  useWorkflowMutation,
} from "@well-prado/blok-react-sdk";

function UserProfile() {
  // Query data (GET-like operations)
  const { data: user, isLoading, error } = useWorkflowQuery("get-user-profile");

  // Mutations (POST/PUT/DELETE-like operations)
  const updateProfile = useWorkflowMutation("update-profile", {
    onSuccess: () => {
      console.log("Profile updated successfully!");
    },
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Hello, {user?.name}</h1>
      <button
        onClick={() => updateProfile.mutate({ name: "New Name" })}
        disabled={updateProfile.isPending}
      >
        Update Profile
      </button>
    </div>
  );
}

API Reference

BlokProvider

The main provider component that sets up the Blok client and context.

interface BlokProviderProps {
  config: BlokClientConfig;
  children: React.ReactNode;
}

<BlokProvider
  config={{
    baseURL: "http://localhost:4000",
    timeout: 15000,
    withCredentials: true,
  }}
>
  {children}
</BlokProvider>;

Hooks

useWorkflowQuery

Execute workflows that fetch data (similar to GET requests).

const result = useWorkflowQuery(
  workflowName: string,
  input?: any,
  options?: UseWorkflowQueryOptions
);

Parameters:

  • workflowName: The name of the workflow to execute
  • input: Optional input data for the workflow
  • options: React Query options + Blok-specific options

Returns:

  • data: The workflow response data
  • isLoading: Loading state
  • error: Error object if the request failed
  • refetch: Function to manually refetch data
  • isSuccess: Boolean indicating successful completion

Example:

// Simple query
const { data, isLoading } = useWorkflowQuery("get-users");

// With input parameters
const { data: user } = useWorkflowQuery("get-user-by-id", { id: "123" });

// With options
const { data, refetch } = useWorkflowQuery("get-dashboard-data", null, {
  refetchInterval: 30000, // Refetch every 30 seconds
  staleTime: 60000, // Data is fresh for 1 minute
  enabled: isAuthenticated, // Only run when authenticated
});

useWorkflowMutation

Execute workflows that modify data (similar to POST/PUT/DELETE requests).

const mutation = useWorkflowMutation(
  workflowName: string,
  options?: UseWorkflowMutationOptions
);

Parameters:

  • workflowName: The name of the workflow to execute
  • options: React Query mutation options + Blok-specific options

Returns:

  • mutate: Function to trigger the mutation
  • mutateAsync: Async version of mutate
  • data: The mutation response data
  • isPending: Loading state
  • error: Error object if the mutation failed
  • isSuccess: Boolean indicating successful completion
  • reset: Function to reset mutation state

Example:

// Simple mutation
const createUser = useWorkflowMutation("create-user");

// With success/error handlers
const updateUser = useWorkflowMutation("update-user", {
  onSuccess: (data) => {
    console.log("User updated:", data);
    // Invalidate and refetch user list
    queryClient.invalidateQueries(["get-users"]);
  },
  onError: (error) => {
    console.error("Update failed:", error);
  },
});

// Usage in component
const handleSubmit = (userData) => {
  updateUser.mutate(userData);
};

// Async usage
const handleAsyncSubmit = async (userData) => {
  try {
    const result = await updateUser.mutateAsync(userData);
    console.log("Success:", result);
  } catch (error) {
    console.error("Error:", error);
  }
};

useBlokClient

Access the underlying Blok client instance.

const client = useBlokClient();

// Use client directly
const handleCustomRequest = async () => {
  const response = await client.executeWorkflow("custom-workflow");
  console.log(response);
};

useWorkflows

Get information about available workflows (requires workflow discovery).

const { workflows, isLoading } = useWorkflows();

// Display available workflows
workflows?.forEach((workflow) => {
  console.log(workflow.name, workflow.description);
});

Advanced Usage

Custom Query Keys

Customize how queries are cached:

const { data } = useWorkflowQuery(
  "get-user-posts",
  { userId: "123" },
  {
    queryKey: ["user-posts", userId], // Custom cache key
    staleTime: 5 * 60 * 1000, // 5 minutes
  }
);

Optimistic Updates

Update UI immediately before server response:

const updatePost = useWorkflowMutation("update-post", {
  onMutate: async (newPost) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries(["get-posts"]);

    // Snapshot previous value
    const previousPosts = queryClient.getQueryData(["get-posts"]);

    // Optimistically update
    queryClient.setQueryData(["get-posts"], (old) =>
      old.map((post) => (post.id === newPost.id ? newPost : post))
    );

    return { previousPosts };
  },
  onError: (err, newPost, context) => {
    // Rollback on error
    queryClient.setQueryData(["get-posts"], context.previousPosts);
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries(["get-posts"]);
  },
});

Dependent Queries

Execute queries that depend on other queries:

function UserProfile({ userId }) {
  // First, get the user
  const { data: user } = useWorkflowQuery("get-user", { id: userId });

  // Then get user's posts (only when we have the user)
  const { data: posts } = useWorkflowQuery(
    "get-user-posts",
    { userId: user?.id },
    {
      enabled: !!user?.id, // Only run when user is loaded
    }
  );

  return (
    <div>
      <h1>{user?.name}</h1>
      {posts?.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

Infinite Queries

Handle paginated data:

import { useInfiniteWorkflowQuery } from "@well-prado/blok-react-sdk";

function PostList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useInfiniteWorkflowQuery(
      "get-posts-paginated",
      ({ pageParam = 1 }) => ({ page: pageParam, limit: 10 }),
      {
        getNextPageParam: (lastPage, pages) => {
          return lastPage.hasMore ? pages.length + 1 : undefined;
        },
      }
    );

  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.posts.map((post) => (
            <div key={post.id}>{post.title}</div>
          ))}
        </div>
      ))}

      <button
        onClick={fetchNextPage}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage ? "Loading..." : "Load More"}
      </button>
    </div>
  );
}

Error Handling

Global Error Handling

import { BlokProvider, BlokErrorBoundary } from "@well-prado/blok-react-sdk";

function App() {
  return (
    <BlokProvider config={config}>
      <BlokErrorBoundary
        fallback={({ error, retry }) => (
          <div>
            <h2>Something went wrong</h2>
            <p>{error.message}</p>
            <button onClick={retry}>Try Again</button>
          </div>
        )}
      >
        <YourApp />
      </BlokErrorBoundary>
    </BlokProvider>
  );
}

Component-Level Error Handling

function UserComponent() {
  const { data, error, isError } = useWorkflowQuery("get-user");

  if (isError) {
    return (
      <div className="error">
        <h3>Failed to load user</h3>
        <p>{error.message}</p>
        {error.code === "UNAUTHORIZED" && (
          <button onClick={() => (window.location.href = "/login")}>
            Please log in
          </button>
        )}
      </div>
    );
  }

  return <div>{data?.name}</div>;
}

TypeScript Integration

Auto-Generated Types

When using with @well-prado/blok-codegen, you get fully typed workflows:

// Types are automatically generated from your workflows
import { useWorkflowQuery } from "./blok-types/hooks";

function UserProfile() {
  // TypeScript knows the exact shape of the response
  const { data } = useWorkflowQuery("getUserProfile"); // ✅ Typed

  return <h1>{data.user.name}</h1>; // ✅ Type safe
}

Manual Type Definitions

For custom typing without codegen:

interface User {
  id: string;
  name: string;
  email: string;
}

interface GetUserResponse {
  user: User;
}

const { data } = useWorkflowQuery<GetUserResponse>("get-user");
// data is now typed as GetUserResponse

Performance Optimization

Query Invalidation

Efficiently update cache when data changes:

import { useQueryClient } from "@tanstack/react-query";

function useUserActions() {
  const queryClient = useQueryClient();

  const deleteUser = useWorkflowMutation("delete-user", {
    onSuccess: (data, variables) => {
      // Remove from user list
      queryClient.invalidateQueries(["get-users"]);

      // Remove individual user query
      queryClient.removeQueries(["get-user", variables.id]);
    },
  });

  return { deleteUser };
}

Background Updates

Keep data fresh automatically:

const { data } = useWorkflowQuery("get-notifications", null, {
  refetchInterval: 30000, // Check every 30 seconds
  refetchIntervalInBackground: true, // Even when tab is not active
  staleTime: 60000, // Consider fresh for 1 minute
});

Testing

Mock Workflows for Testing

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BlokProvider } from "@well-prado/blok-react-sdk";
import { render, screen } from "@testing-library/react";

// Create test wrapper
function TestWrapper({ children }) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      <BlokProvider config={{ baseURL: "http://test-api" }}>
        {children}
      </BlokProvider>
    </QueryClientProvider>
  );
}

// Test component
test("displays user name", async () => {
  // Mock the workflow response
  const mockUser = { id: "1", name: "John Doe" };

  // Setup your test...
  render(<UserProfile />, { wrapper: TestWrapper });

  expect(await screen.findByText("John Doe")).toBeInTheDocument();
});

Best Practices

1. Query Key Consistency

Use consistent query keys across your app:

// ✅ Good - Consistent structure
const QUERY_KEYS = {
  users: {
    all: ["users"],
    detail: (id) => ["users", id],
    posts: (id) => ["users", id, "posts"],
  },
};

const { data } = useWorkflowQuery(
  "get-user",
  { id },
  {
    queryKey: QUERY_KEYS.users.detail(id),
  }
);

2. Separate Data Fetching Logic

Create custom hooks for complex data operations:

// hooks/useUserData.ts
export function useUserData(userId) {
  const user = useWorkflowQuery("get-user", { id: userId });
  const posts = useWorkflowQuery(
    "get-user-posts",
    { userId },
    {
      enabled: !!userId,
    }
  );

  return {
    user: user.data,
    posts: posts.data,
    isLoading: user.isLoading || posts.isLoading,
    error: user.error || posts.error,
  };
}

// Component
function UserProfile({ userId }) {
  const { user, posts, isLoading } = useUserData(userId);

  if (isLoading) return <Loading />;

  return (
    <div>
      <h1>{user.name}</h1>
      {posts.map((post) => (
        <Post key={post.id} {...post} />
      ))}
    </div>
  );
}

3. Error Boundaries

Use error boundaries to catch and handle errors gracefully:

import { ErrorBoundary } from "react-error-boundary";

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>Something went wrong:</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <BlokProvider config={config}>
        <YourApp />
      </BlokProvider>
    </ErrorBoundary>
  );
}

Migration Guide

From REST API to Blok Workflows

// Before (REST API)
const [user, setUser] = useState(null);
useEffect(() => {
  fetch("/api/users/123")
    .then((res) => res.json())
    .then(setUser);
}, []);

// After (Blok SDK)
const { data: user } = useWorkflowQuery("get-user", { id: "123" });

From Manual State Management

// Before (Manual state)
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);

const addUser = async (userData) => {
  setLoading(true);
  try {
    const newUser = await api.createUser(userData);
    setUsers((prev) => [...prev, newUser]);
  } finally {
    setLoading(false);
  }
};

// After (Blok SDK)
const { data: users } = useWorkflowQuery("get-users");
const addUser = useWorkflowMutation("create-user", {
  onSuccess: () => {
    queryClient.invalidateQueries(["get-users"]);
  },
});

Troubleshooting

Common Issues

  1. Provider Not Found Error

    Error: useBlokClient must be used within a BlokProvider

    Solution: Wrap your app with BlokProvider.

  2. Query Not Updating

    • Check if you're invalidating queries after mutations
    • Verify query keys are consistent
    • Ensure enabled option isn't preventing execution
  3. Type Errors

    • Run npm run blok:codegen to regenerate types
    • Check that workflow names match exactly
    • Verify input/output types match workflow definitions

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Related Packages