Effection Logo

Context

Usually, you can pass information from a parent operation to a child operation via function argument or lexical scope. But passing function arguments can become verbose and inconvenient when the you have to pass them through many operations in the middle, or if many operations need the same information. Likewise, passing information via lexical scope is only possible if you define the child operation in the body of the parent operation. Context lets the parent operation make some information available to any operation in the tree below it-no matter how deep-without passing it explicitely through function arguments or lexical scope.

💁 If you're familiar with React Context, you already know most of what you need to know about Effection Context. The biggest difference is the API but general concepts are same.

What is argument drilling?

Passing argument to operations is a convenient way to make data from parent operation available to the child operation.

But passing arguments can become inconvenient when the child operation is nested deeply in the tree of operations, or the same arguments need to be passed to many operations. This situation is called "argument drilling".

Wouldn't it be great if you could access information in a deeply nested operation from a parent operation without modifying operations in the middle? That's exaclty what Effection Context does.

Context: an alternative to passing arguments

Context makes a value available to any child process within a tree of processes.

import { createContext, main } from 'effection';

// 1. create the context
const GithubTokenContext = createContext("token");

await main(function* () {
  // 2. set the context value
  yield* TokenContext.set("gha-1234567890");

  yield* fetchGithubData();
})

function* fetchGithubData() {
  yield* fetchRepositories();
}

function* fetchRepositories() {
  // 3. use the context value in a child operation
  const token = yield* GithubTokenContext;

  console.log(token);
  // -> gha-1234567890
}

Context: overriding nested context

Context is attached to the scope of the parent operation. This allows you to override the context value of an operation in the tree without affecting the value higher up the tree.

Parent sets value to A, Child overrides value to B and all children below get B

Using Context

To use context in your operations, you need to do the following 3 steps:

  1. Create a context.
  2. Set the context value.
  3. Yield the context value.

Step 1: Create a context.

Effection provides a function for creating a context appropriatelly called createContext. This function will return a reference that you use to identify the context value in the scope.

import { createContext } from 'effection'

const MyValueContext = createContext("my-value");

Step 2: Set the context value.

await main(function* () {
  yield* MyValueContext.set("Hello World!");
});

Step 3: Yield the context value.


await main(function* () {
  yield* MyValueContext.set("Hello World!");

  yield* logMyValue();
});

function* logMyValue() {
  const value = yield* MyValueContext;

  console.log(value);
}

Use cases for context

  • Config: most apps written with Effection require values that come from the environment variables. Use can use context to store values from configuration to easily retrieve it in any operation.
  • Client APIs: many APIs require creating a client instance. You can create the client instance once and make it available to all operations using context.
  • PreviousError Handling