Context and providers
Contexts and providers are powerful tools in GenSX for sharing data and managing configuration across components without explicitly passing props through every level of your component tree. They work similarly to React’s Context API but are adapted to work with GenSX workflows.
What are contexts and providers?
Contexts and providers work together to share data and manage dependencies across components.
- Contexts give you a way to share data (like state, configuration, or dependencies) across components without manually passing props down the component tree.
- Providers are components that supply data or services to a context. Any component within a provider’s subtree can access the context.
The two concepts are interdependent so you can’t use one without the other. Combined, they’re great for:
- Providing data to components without prop drilling
- Sharing configuration and dependencies, such as clients, for your workflow
- Managing state that needs to be accessed by multiple components
The remainder of this document will show you how to create and use both contexts and providers in GenSX.
Creating and using contexts
This next section walks through the steps needed to create and use a context in your GenSX workflow.
Step 1: Create a context
To create a context, start by defining its interface and then use gsx.createContext<T>() to initialize it along with a default value. For example, here’s how to create a User context:
import { gsx } from "gensx";
// Define the interfaceinterface User { name: string;}
// Create a context with a default valueconst UserContext = gsx.createContext<User>({ name: "",});Step 2: Use the context in a component
To use the context, call the gsx.useContext(context) hook inside of a component. Here a Greeting component is created that uses the UserContext to get the user’s name:
const Greeting = gsx.Component<{}, GreetingOutput>("Greeting", () => { const user = gsx.useContext(UserContext); return `Hello, ${user.name}!`;});Step 3: Provide the context value
To make the context value available to your components, you need to wrap your component in a Provider component and pass in a value via the value prop:
const result = await gsx.execute( <UserContext.Provider value={{ name: "John" }}> <Greeting /> </UserContext.Provider>,);Using providers for configuration and dependencies
Providers are a specialized way to use contexts that focus on managing configuration and dependencies for your workflow. They simplify the process of sharing data like API keys, client instances, or feature flags across your components.
Built-in providers
The main provider available today is the OpenAIProvider, which manages your OpenAI API key and client:
const result = await gsx.execute( <OpenAIProvider apiKey={process.env.OPENAI_API_KEY}> <ChatCompletion model="gpt-4" messages={[{ role: "user", content: "Hello!" }]} /> </OpenAIProvider>,);Creating a Custom Provider
If you need a provider that isn’t available out of the box, you can easily create your own. The example below shows how to create a provider for the Firecrawl API.
Step 1: Create a context
Start by importing gsx and the package you want to use:
import { gsx } from "gensx";import FirecrawlApp, { FirecrawlAppConfig } from "@mendable/firecrawl-js";Then, create the context:
// Create a contextexport const FirecrawlContext = gsx.createContext<{ client?: FirecrawlApp;}>({});The context contains the client that you’ll use to interact with the Firecrawl API.
Step 2: Create the provider
Next, wrap your context in a provider component:
// Create the providerexport const FirecrawlProvider = gsx.Component<FirecrawlAppConfig, never>( "FirecrawlProvider", (args: FirecrawlAppConfig) => { const client = new FirecrawlApp({ apiKey: args.apiKey, }); return <FirecrawlContext.Provider value={{ client }} />; },);The provider will take in the apiKey as a prop and use it to initialize the Firecrawl client.
Step 3: Use the provider in a component
Finally, you can build components that consume the context supplied by the provider:
export const ScrapePage = gsx.Component<ScrapePageProps, string>( "ScrapePage", async ({ url }) => { const context = gsx.useContext(FirecrawlContext);
if (!context.client) { throw new Error( "Firecrawl client not found. Please wrap your component with FirecrawlProvider.", ); } const result = await context.client.scrapeUrl(url, { formats: ["markdown"], timeout: 30000, });
if (!result.success || !result.markdown) { throw new Error(`Failed to scrape url: ${url}`); }
return result.markdown; },);Step 4: Use the provider in your workflow
Now when you use the ScrapePage component in your workflow, you’ll wrap it in the FirecrawlProvider and pass in the apiKey:
const markdown = await gsx.execute( <FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}> <ScrapePage url="https://gensx.com/docs/" /> </FirecrawlProvider>,);Nesting Providers
You can nest multiple providers to combine different services or configurations in your workflow. This is useful when a component needs access to multiple contexts. Here’s an example that combines the OpenAI provider with our custom Firecrawl provider:
const result = await gsx.execute( <OpenAIProvider apiKey={process.env.OPENAI_API_KEY}> <FirecrawlProvider apiKey={process.env.FIRECRAWL_API_KEY}> <WebPageSummarizer url="https://gensx.com/docs/" /> </FirecrawlProvider> </OpenAIProvider>,);In this example, the WebPageSummarizer component can access both the OpenAI client and Firecrawl client through their respective contexts.
The order of nesting doesn’t matter as long as the component using a context is wrapped by its corresponding provider somewhere up the tree.
Additional Resources
You can find the full example code demonstrating these concepts on GitHub: