Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[question] Recommended strategies to pass context #31

Closed
eelkevdbos opened this issue Sep 21, 2023 · 15 comments
Closed

[question] Recommended strategies to pass context #31

eelkevdbos opened this issue Sep 21, 2023 · 15 comments
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@eelkevdbos
Copy link

eelkevdbos commented Sep 21, 2023

Seeing as a library like this will be used as a (lightweight) templating engine, I wonder what strategies could be used to inject context into a template to prevent property drilling.

Example use cases:

  • Passing theme
  • Passing localization

I've searched for some packages that provide a similar feature for rendering JSX to HTML without React in the mix:

I would love to hear your thoughts on this @arthurfiorette

@arthurfiorette
Copy link
Member

arthurfiorette commented Sep 29, 2023

Hey @eelkevdbos. Awesome question btw.

The context concept is really useful to have changing-state in multiple branches of your component tree, without having to drill state and setState properties for multiple components and having the whole tree being rendered again when a setState is called.

However, this is not present in this library, as each component is immutable and only returns a string. Although it would be awesome to be able to define per-render "global" variables that could be used deep inside components, we cannot do this without changing the return type to something else than a string/Promise or keep asynchronous safe renders.

I did a lot of experiments and even looked up similar implementations like the new beth-stack. All of them either have a different return type or, like in the beth stack, are not asynchronous safe.

The asynchronous problem happens in cases like this example:

async function AsyncComponent({ userId }) {
  const username = await dbQueryThatTakesOneSecondOrMore(userId);
  return <div safe>{username}</div> 
}

In a web server like backend, which will be most of usecases, having a http route that renders the above async component, would work normally for all cases. If we change it to use a "context-like" approach:

async function AsyncComponent() {
  const requestContext = useContext(Request);
  const username = await dbQueryThatTakesOneSecondOrMore(requestContext.userId);
  return <div safe>{username}</div> 
}

However, without changing the string-like return type, it would need some sort of per-request variable that is defined at the beginning of the request and cleared out after it renders successfully. The problem happens when a second request is received without having returned the previous one. (This example of 1+ seconds is easily to reproduce). NodeJS is asynchronous, and the event loop would jump a non blocking task until it completes. Which would conflict with the global variable usage and break implementations completely by returning wrong states in async components when two requests are being rendered at the same time. Which is something really important but may not be obvious.

A solution I found was, like in the Suspense implementation, fallback to a rid property created at the html root, which even for asynchronous operations can still be traced back with the rid. It is not an ideal DX but is the only solution without removing the string-like return type which is the big-deal of this package.

I also tried implementations with the rid to create a context example. I ended up with something like this:

// rid was a native prop, the same way as children is defined for every component.
function Component({rid}) {
  const contextA = useContext(MyContextA, rid);
  const contextB = useContext(MyContextB, rid);
  return <div>...</div>;
}

const html = renderWithContext(rid => (
  <Layout rid={rid}>
    <ComponentA rid={rid} />
    <Componentb rid={rid} />
    <ComponentC rid={rid} />
    <ComponentD rid={rid} />
  </Layout>
), { stateProperty })

But as you saw, we would still need to prop-drill rid. And, as each component is immutable and only returns a string, we could only have read only states that have its properties defined at the root. (In this example { stateProperty } would need to be 100% calculated before the first render.

With all of the above in mind. I'm not sure we will be able to do any context-like implementation. But remember, @kitajs/html is just a templating engine on steroids no mutation is done by itself (look at solutions like htmx).

Anyways, happy to discuss further possibilities :)

@eelkevdbos
Copy link
Author

Thank you for the thorough explanation! I think I understand the issue a little more.

Coming from a python background, I am familiar with the concept of a Context Local (https://docs.python.org/3/library/contextvars.html), a seemingly "global" variable but with a thread or event loop local scope.

After a bit of searching for an analogy, I found the following: https://nodejs.org/api/async_context.html

It seems to me that we could use this (if ported to Bun) to have that "global" async-safe variable.

@arthurfiorette
Copy link
Member

Somehow I totally missed async_context module... I'll take a deeper look at it.

@arthurfiorette arthurfiorette self-assigned this Nov 20, 2023
@arthurfiorette arthurfiorette added enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed labels Nov 20, 2023
@arthurfiorette
Copy link
Member

This should be a totally doable feature, however I do not have time to spend on it as I'm using my spare time to build https://kita.js.org. Any help is appreciated, does someone wanna work on it?

@RomainLanz
Copy link

Hey all! 👋🏻

This should be a totally doable feature, however I do not have time to spend on it as I'm using my spare time to build https://kita.js.org. Any help is appreciated, does someone wanna work on it?

I am not sure if this should be handled by KitaJS or by the framework using it. The ALS should wrap the whole request to keep a nice DX.

I have tested to use the ALS of AdonisJS and it played nicely with KitaJS to avoid props drilling.

router.get('test', async () => {
  return <Home title={'Hello'} />
})
import { HttpContext } from '@adonisjs/core/http'

export default function Home({ title }: { title: string }) {
  const ctx = HttpContext.getOrFail()

  return (
    <div>
      <h1>
        {title} - {ctx.request.url()}
      </h1>
    </div>
  )
}

@arthurfiorette
Copy link
Member

Awesome! I'm mainly developing kitajs/html for kita.js.org, which uses fastify under the hood, so a more specific implementation towards https://github.com/fastify/fastify-request-context should be done here, however we must also document usage with other libraries.

@RomainLanz
Copy link

Keep a note that using ALS is quite expensive and will decrease the performance of your application.

If we can avoid using ALS, it would be better.

@arthurfiorette
Copy link
Member

The only way to avoid ALS is by not avoiding prop-drilling, which is the topic of this issue. Buut, i'm 100% with prop drilling, as its more verbose but hides any kind of magic may happeng.

Also, it is expensive at the point of frameworks having to revert their ALS adoption.

@arthurfiorette
Copy link
Member

arthurfiorette commented Jan 4, 2024

As our only way to support concurrent renders and avoid prop drilling is to use ASL, but its slower, I'll close this issue until newer ways are released.

Also, hooks are a "mentality" of react, this library, unless attached to another framework, should be thought as a template engine, not a frontend framework.

@arthurfiorette arthurfiorette reopened this Jan 4, 2024
@arthurfiorette arthurfiorette closed this as not planned Won't fix, can't repro, duplicate, stale Jan 4, 2024
@eelkevdbos
Copy link
Author

Just putting this here, because if implemented, it would be a decent performance improvement to the async local storage: nodejs/node#48528

@arthurfiorette
Copy link
Member

awesome!

@arthurfiorette arthurfiorette pinned this issue Feb 8, 2024
@RomainLanz
Copy link

FYI, we wrote a blog post on how to use Kita with AdonisJS and someone made a package to have a better DX!

@arthurfiorette
Copy link
Member

Hey @RomainLanz, awesome post!!!

I would greatly appreciate it if you could also provide guidance on preventing XSS. This can be achieved either by incorporating the xss-scan script from @kitajs/ts-html-plugin into the package.json test script, or by configuring IDE IntelliSense.

While using kita/html correctly has no XSS vulnerability, overlooking certain steps can be quite risky. I'm kindly requesting everyone who showcases kitajs/html to include this information, as it's not a common practice for other libraries.

https://github.com/kitajs/ts-html-plugin?tab=readme-ov-file#running-as-cli
https://github.com/kitajs/html?tab=readme-ov-file#installing

@RomainLanz
Copy link

Hey @RomainLanz, awesome post!!!

I would greatly appreciate it if you could also provide guidance on preventing XSS. This can be achieved either by incorporating the xss-scan script from @kitajs/ts-html-plugin into the package.json test script, or by configuring IDE IntelliSense.

While using kita/html correctly has no XSS vulnerability, overlooking certain steps can be quite risky. I'm kindly requesting everyone who showcases kitajs/html to include this information, as it's not a common practice for other libraries.

https://github.com/kitajs/ts-html-plugin?tab=readme-ov-file#running-as-cli https://github.com/kitajs/html?tab=readme-ov-file#installing

Yes, I will add those tomorrow!

@RomainLanz
Copy link

I have updated the article; if you have any other feedback about missing things, I would happily add them too! 👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
Status: Done
Development

No branches or pull requests

3 participants