DEV Community

NDREAN
NDREAN

Posted on • Updated on

Revisit Context with a useful pattern, illustrated with SolidJS and async data

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:

(context) => function Component(props) {HTML like stuff}
Enter fullscreen mode Exit fullscreen mode

Below is a simple example of a navigation between pages that renders data using the context pattern and SolidJS:

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.

// context.js

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

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

const context = {
  bool, setBool,
  theme: {...}, 
  ...
}
Enter fullscreen mode Exit fullscreen mode

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

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>
Enter fullscreen mode Exit fullscreen mode

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

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/>
Enter fullscreen mode Exit fullscreen mode

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:

// asyncFunc.js

const asyncFunc = ({setData}) => 
  (x) =>
    new Promise(resolve => 
      setTimeout(() => resolve(x), 1000)
    )
    .then(setData)
Enter fullscreen mode Exit fullscreen mode

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

// context.js

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

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

const context = {
  data, setData,
  ...
}
Enter fullscreen mode Exit fullscreen mode

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)).

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>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  )
Enter fullscreen mode Exit fullscreen mode

Any other component can access to the reactive state data.

Top comments (0)