Search code examples
state-managementsolid-js

Why to use Context and Provider given SolidJS reactivity?


I have this SPA project which authenticates and authorizes its users via access tokens (JWT). These short-lived access tokens has their refresh tokens as well, so when they expire the app should refresh the access token and replay the failing request(s). To achieve this I come up with a function (authAwareFetch) that wraps global fetch to; 1. add the authorization header if an access token present (i.e. user has logged in) and 2. check if request fails (due to expired token) refresh the access token and replay the request.

To find out if an access token present authAwareFetch file imports the token store (tokenStore), which is a mere SolidJS store to hold access token, its lifetime (valid-until as EPOCH time) and refresh token, created by createStore of SolidJS.
To be able to log users in I have this auth service which exports other functions among login; refreshAccessToken, logout. The service file also imports tokenStore, and its functions reads from and writes to the store.

Given the code organization above (stores and services in their own files) and being able to react changes (on and off a component, thanks to SolidJS), my question is what is the use-case of Context and Provider? If I am going to import a hook for each and every component (e.g. useAuth) wouldn't it be the same thing to import the store (to read from it) and the service (to update the store)? Am I missing something?
Thanks in advance.


Solution

  • Context allows you pass values down the component tree without going through parent child relationship.

    Say you want to use access token in a component located three levels deep inside the component hierarchy:

    const [authoStore, setAuthStore] = createStore({ /* Contents */ });
    
    <ParentA>
      <ParentB>
        <Child />
      </ParentB>
    </ParentA>
    

    You can either pass the store from ParentA to ParentB to Child:

    <ParentA store={authStore}>
      <ParentB store={authStore}>
        <Child store={authStore} />
      </ParentB>
    </ParentA>
    

    Or you can create a context and pass the store as its value so that Child component can access it directly through Context API.

    const AuthContext = createContext(defaultValue);
    <AuthContext.Provider value={authStore}>
      <ParentA>
        <ParentB>
          <Child />
        </ParentB>
      </ParentA>
    </AuthContext>
    

    And inside the Child component:

    const Child = () => {
       const authStore = useContext(AuthContext);
       // Snipped
    }
    

    Now, if we go back to the original question, using a signal or a store is irrelevant because as long as the inner components has access to the auth token, you can use store, signal, localstorage or even plain Javascript object. But Context API provides an easy way to share values between components, no matter how deep they lie on the component hierarchy.

    There are added benefits, for example, you can overwrite a value on different levels of the component tree by providing a different value.

    Here ChildA will receive the value red while ChildB will receive blue:

    <ThemeContext.Provider value='red'>
      <ChildA />
      <ThemeContext.Provider value='blue'>
        <ChildB />
      </ThemeContext.Provider>
    </ThemeContext.Provider>
    

    You can pass any kind of value through context, including signals and stores.