Effection Logo

Context

Effection contexts store ambient values that are needed by the operations in your application in order to run. Some of the most common use cases of Context are

  • Configuration: most apps require values that come from the environment such as evironment variables or configuration files. Use context to easily retrieve such values from within any operation.
  • Client APIs: many APIs provide client libraries to connect with them. With an Effection context, you can create a client instance once and share it between all child operations.
  • Services: Database connections, Websockets, and many other types of APIs involve a handle to a stateful object that needs to be destroyed accordingly to a set lifecycle. Combined with resource api, Effection context allows you to share a service between child operations and also to guarantee that it is disposed of properly when it is no longer needed.

The problem

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 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. That means that the operation and all of its children will see that same context.

However, if a child operation sets its own value for the context, it will not affect the value of any of its ancestors.

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);
}
  • PreviousError Handling