state Archives - ProdSens.live https://prodsens.live/tag/state/ News for Project Managers - PMI Wed, 11 Oct 2023 00:25:49 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://prodsens.live/wp-content/uploads/2022/09/prod.png state Archives - ProdSens.live https://prodsens.live/tag/state/ 32 32 Easy Context in React Server Components (RSC) https://prodsens.live/2023/10/11/easy-context-in-react-server-components-rsc/?utm_source=rss&utm_medium=rss&utm_campaign=easy-context-in-react-server-components-rsc https://prodsens.live/2023/10/11/easy-context-in-react-server-components-rsc/#respond Wed, 11 Oct 2023 00:25:49 +0000 https://prodsens.live/2023/10/11/easy-context-in-react-server-components-rsc/ easy-context-in-react-server-components-(rsc)

Continuing the need to have easy context from my other posts, I wanted to find a way to…

The post Easy Context in React Server Components (RSC) appeared first on ProdSens.live.

]]>
easy-context-in-react-server-components-(rsc)

Continuing the need to have easy context from my other posts, I wanted to find a way to share context on the server as well.

You may have noticed that useContext and createContext do not compile on the server (another millionth reason I love SvelteKit!).

So, there needs to be a way to do this easily in RSC, even though the React Team, who seems to be extremely far away from the actual userbase, decided to depreciate this feature before it was actually implemented.

You can find createServerContext in the latest React as an import, but there is no actual Consumer like there is in a regular context, so I couldn’t get it to work. Supposedly it could be shared on the client and server, but only for small strings. The only actual mention of this is in a tweet:

let Lang = createServerContext("lang", "en");
...
<Lang.Provider value={}>
...
use(Lang)

Either way, I would have re-written this to work like my other context, so it is a moot point.

I should also add, the second best solution is to use the server-only-context library (by manvalls) which uses cache under the hood.

Dear React Team (and NextJS Team)

There is a reason there are so many external state libraries for React. The way React works out-of-the-box is terrible and uses too much boilerplate. We love RSC (the people on the React train who won’t move to Svelte have no choice) and we love that Vercel wants to add Server Actions, but either of you can fix this problem. No more Context Hell please!

React Team… Let’s add signals (or something better) as well please and not worry about memoizing state! Why are you making things hard that don’t have to be!!!!? Serious question.

Ok, I digress.

The Solution

So I basically copied the cache idea from above, but simplified it for my use case with the Map from my other solutions. The cache is actually a Map itself, so I believe this is a Map of a Map under the hood.

use-server-provider.tsx

import 'server-only';
import { cache } from 'react';

const serverContext = cache(() => new Map());

export const useServerProvider = <T,>(
    key: string,
    defaultValue?: T
) => {
    const global = serverContext();

    if (defaultValue !== undefined) {
        global.set(key, defaultValue);
    }

    return [
        global.get(key),
        (value: T) => global.set(key, value)
    ];
};

Parent

const [count, setCount] = useServerProvider('count', 23);

Child

const [count] = useServerProvider<number>('count');

However, it is worth noting, unlike a reactive component, if you set the component after you define the count variable, it won’t update unless you grab the count variable again. One way to fix this is to use a value object key like in a signal:

import 'server-only';
import { cache } from 'react';

const serverContext = cache(() => new Map());

export const useServerProvider = <T,>(
    key: string,
    defaultValue?: T
) => {
    const global = serverContext();

    if (defaultValue !== undefined) {
        global.set(key, defaultValue);
    }

    return {
        get value() {
            return global.get(key)
        },
        set value(v: T) {
            global.set(key, v);
        }
    }
};

Then you could do this:

Parent

const count = useServerProvider('count', 23);

count.value = 27;

Child

export default function TestChild() {

    const count = useServerProvider<number>('count');

    return (
        <p>Child: {count.value}</p>
    )
}

Nevertheless, on a server you shouldn’t care about any of this… so the original react-like way still stands.

Probably the last article in this series… probably…

J

Getting back to rebuilding code.build

The post Easy Context in React Server Components (RSC) appeared first on ProdSens.live.

]]>
https://prodsens.live/2023/10/11/easy-context-in-react-server-components-rsc/feed/ 0
Easy Shared Reactive State in React without External Libraries https://prodsens.live/2023/10/06/easy-shared-reactive-state-in-react-without-external-libraries/?utm_source=rss&utm_medium=rss&utm_campaign=easy-shared-reactive-state-in-react-without-external-libraries https://prodsens.live/2023/10/06/easy-shared-reactive-state-in-react-without-external-libraries/#respond Fri, 06 Oct 2023 03:27:04 +0000 https://prodsens.live/2023/10/06/easy-shared-reactive-state-in-react-without-external-libraries/ easy-shared-reactive-state-in-react-without-external-libraries

Right now using useState with useContext requires a LOT of boilerplate. For every context you have to custom…

The post Easy Shared Reactive State in React without External Libraries appeared first on ProdSens.live.

]]>
easy-shared-reactive-state-in-react-without-external-libraries

Right now using useState with useContext requires a LOT of boilerplate. For every context you have to custom provider, which as we have seen, can be a pain in the but. For what ever reason, Facebook refuses to fix this, so we have other libraries:

These are the ones I found helpful, but ultimately you’re still using an external library to do something React already does itself. Surely there is a way to do this in React without all the boilerplate etc!?

useProvider

So I created the useProvider hook in the previous post in the series as an experiment. It basically uses a map to store objects so that you can retrieve them whenever you want.

That post was probably one of my least popular posts, and I realized why. You can’t store a useState object in a map, at least not while keeping the reactivity.

As I always planned, I went back and thought about it to figure out how to do it.

🤔💡💭

What if we store the contexts in the map instead of the state itself? Ok, honestly, I’m not sure what my brain was thinking, but I some how (maybe accidently) figured out how to do that. You still have one universal provider, and you can grab any state (even reactive) by the context. Review the previous posts to see this evolution:

use-provider.tsx

'use client';

import {
    FC,
    ReactNode,
    createContext,
    useContext,
    type Context,
    useState
} from "react";

const _Map = <T,>() => new Map<string, T>();
const Context = createContext(_Map());

export const Provider: FC<{ children: ReactNode }> = ({ children }) =>
    <Context.Provider value={_Map()}>{children}</Context.Provider>;

const useContextProvider = <T,>(key: string) => {
    const context = useContext(Context);
    return {
        set value(v: T) { context.set(key, v); },
        get value() {
            if (!context.has(key)) {
                throw Error(`Context key '${key}' Not Found!`);
            }
            return context.get(key) as T;
        }
    }
};

This is the same code from the first post, just renamed to useContextProvider. However, now we are going to use this as a helper function for the real useProvider hook:

export const useProvider = <T,>(key: string, initialValue?: T) => {
    const provider = useContextProvider<Context<T>>(key);
    if (initialValue !== undefined) {
        const Context = createContext<T>(initialValue);
        provider.value = Context;
    }
    return useContext(provider.value);
};

Here is what is happening. The useContextProvider just creates a universal provider that can store anything in a map. Again, see the first post. useProvider creates a new context for whatever value is passed in, and sets that as a value to the key you pass in. I know this sounds confusing, so imagine this:

container


---- my app components

simplified set value (pseudocode)

// create a new map and set that as value of universal provider
const providers = new Map()
providers.set('count', createContext(0))
<Context.Provider value={provider} />

simplified get value (pseudocode)

// get the 'count' key from universal provider
// which returns a context, use that context to get counter
const providers = useContext(Provider)
const countContext = providers.get('count')
const counter = useContext(countContext.value)

I’m not sure if that makes sense, but that is in its simplest form what is happening. To use it, you simply call it like this:

Parent

// create a state context
const state = useState(0);
useProvider('count', state);

Child

const [count, setCount] = useProvider('count')

And that’s it!!!

You can have as many providers you want with ONE SINGLE PROVIDER. Just name it whatever you like. No more context hell!

However, I didn’t stop there. You pretty much are always going to want to share state, so why not make that automatic too!

export const useSharedState = <T,>(key: string, initialValue?: T) => {
    let state = undefined;
    if (initialValue !== undefined) {
        const _useState = useState;
        state = _useState(initialValue);
    }
    return useProvider(key, state);
};

This helper function will allow you to just use the provider like a state hook anywhere!

Parent

const [count, setCount] = useSharedState('count', 0);

Child / Sibling / Grand Child

const [count, setCount] = useSharedState<number>('count');

That’s literally it! Works like a charm everywhere. You still need to include the ONE universal provider in your root:

page.tsx

import Test from "./test";
import { Provider } from "./use-provider";

export default function Home() {

  return (
    <Provider>
      <Test />
    </Provider>
  );
}

Final Code

use-provider.tsx

'use client';

import {
    FC,
    ReactNode,
    createContext,
    useContext,
    type Context,
    useState
} from "react";

const _Map = <T,>() => new Map<string, T>();
const Context = createContext(_Map());

export const Provider: FC<{ children: ReactNode }> = ({ children }) =>
    <Context.Provider value={_Map()}>{children}</Context.Provider>;

const useContextProvider = <T,>(key: string) => {
    const context = useContext(Context);
    return {
        set value(v: T) { context.set(key, v); },
        get value() {
            if (!context.has(key)) {
                throw Error(`Context key '${key}' Not Found!`);
            }
            return context.get(key) as T;
        }
    }
};

export const useProvider = <T,>(key: string, initialValue?: T) => {
    const provider = useContextProvider<Context<T>>(key);
    if (initialValue !== undefined) {
        const Context = createContext<T>(initialValue);
        provider.value = Context;
    }
    return useContext(provider.value);
};

export const useSharedState = <T,>(key: string, initialValue?: T) => {
    let state = undefined;
    if (initialValue !== undefined) {
        const _useState = useState;
        state = _useState(initialValue);
    }
    return useProvider(key, state);
};

This is not a lot of code for the power it provides! It will save you so much boilerplate!

Note: I did the trick above for conditional useState if you find it interesting 🙂

Counter useProvider

I’m sure I missed something here, but this seems to be amazing. If I ever decide to actually use react (I love Svelte and Qwik!), I woudl definitely use this custom hook: useProvider.

Let me know if I missed something!

J

Current rebuilding code.build

The post Easy Shared Reactive State in React without External Libraries appeared first on ProdSens.live.

]]>
https://prodsens.live/2023/10/06/easy-shared-reactive-state-in-react-without-external-libraries/feed/ 0