Revisit Context with a useful pattern, illustrated with SolidJS and async data
Tags: javascript, frontend, solidjs
Published: 2023-06-08 11:34:15

A component is a function takes some props and renders a view. In these props, you may have some state elements. When you do multi-page SPAs, you rebuild components on every new page. You may want to use a global store to share state between them. A useful pattern for this is is the Context. You can also do this by parametrizing the function component:

```js
(context) => function Component(props) {HTML like stuff}
```

Below is a simple example of a navigation between pages that renders data using the context pattern and SolidJS:
 
{% stackblitz https://stackblitz.com/edit/github-jx7gym?embed=1&file=src%2Fcontext.js %}

You can pass in all the stuff you may need along the road such as themes, useful functions...

With some frameworks, you can declare state outside of components. With `SolidJS`, you use `createSignal` to declare a state element; it can be encapsulated in a component, or declared outside of it. You can take advantage of this "context": you can declare it at the context level if you need to share it among components.

```js
// context.js

import {createSignal} from from "solid-js";

const [bool, setBool] = createSignal(false);

const context = {
  bool, setBool,
  theme: {...}, 
  ...
}
```

In the example below, we get the state from the context, with the getter and setter.

```jsx
const comp1 = (ctx) => {
  const {bool, setBool, theme} = ctx;
  
  return const Comp1 = (props) =>
     (
        <p>The state value is: {bool() ? "true" : "false"}</p>
        <button onClick={()=> setBool(v => !v}>Toggle</button>
        {props?.children}
    )
}

[...]

import context from "./context";
const Comp1 = comp1(context)
<Comp1>Hi</Comp1>
```

When you navigate to another component, you import the state and have the reactive value every time you navigate back to this component.

```jsx
const comp2 = (ctx) => {
  const {bool} = ctx;
  
  return () =>
     (
      <div>
        <p>Comp1 changed the state: {bool()}</p>
     </div>
    )
}

[...]

import context from "./context";
const Comp2 = comp2(context)
<Comp2/>
```

We can have an async function that sets data from a component, and we want to use the result elsewhere. When you want to run an async call with SolidJS, it is specified in the SolidJS documentation that you should use `createResource`. Then, if you need ot update the state, you should avoid `createEffect` as this is used to mutate the DOM but rather attach a `then(..)` to the async call. Let's do that.


We mock an async call and update the state with the result. Furthermore, since we may want to write this function out of the scope of the component, we parametrize it with the context so the state setter will be available:

```js
// asyncFunc.js

const asyncFunc = ({setData}) => 
  (x) =>
    new Promise(resolve => 
      setTimeout(() => resolve(x), 1000)
    )
    .then(setData)
```

We first add a signal in the context to share the data within components:

```js
// context.js

import {createSignal} from from "solid-js";

const [data, setData] = createSignal("");

const context = {
  data, setData,
  ...
}
```

The async calls are handled with `createRessource` in SolidJS: it can take an argument and returns a getter function (written in the form `(argOfFun, Fun)`).

```jsx
import { createEffect, createResource } from "solid-js";
import context from "./context";
import asyncFunc from "./asyncFunc";
 

const comp3 = (ctx) => {
  // import the state
  const {data, setData, theme} = ctx;
  const AsyncFun = asyncFunction(ctx)
 
  return function Comp1(props) {
    // run the async function; it will update the state
    const [result] = createResource(100, AsyncFun);

    return(
      <p> Async render: {result()}</p>
    )
  }
}
```

You can use it elsewhere by importing the state `data` as above and when you navigate, the component will be up-to-date.

Note that the function component will only run **once**, so the async function will be run only **once**.

What if we want to change it? Lets give an example. We use a slider to change the parameter of the async function. 

We use a local state to track the value just for the display; this means it will be reset every time the component is rendered. You can choose a global state if you want persistence. 

We need to implement 2 async calls: the first one will run when the component is rendered, and the second will react on changes during the life time of the component. SolidJS makes this easy as we are assured the function component is run only once.

```js
import ...

const comp4 = (ctx) => {
  // import the state from context
  const {data, setData} = ctx;
  const AsyncFun = asyncFunction(ctx)
  
  return ()=> {
    // local state to display the slider values, reset on every mount
    const [slide, setSlide] = createSignal(10);

    // initial async call on navigation
    !data() && createResource(slide(), AsyncFun);

    return (
      <div>
        <input
          type="range"
          min="10"
          max="100"
          value="10"
          onchange={({target: {value}) => {
            setSlide(value);
            // dynamic async call
            createResource(value, AsyncFun)
          }}
        />
        <p>{slide()}</p>
        <p> Dynamic async update: {data()}.</p>
    </div>
  )
```

Any other component can access to the reactive state `data`.