Originally posted by **yahia-berashish** October 25, 2023
Hello, I tried to migrate React Context API to VanJS. The key steps:
- create a class name and unique id for context provider, and store provide value
- find the ancestor with class name by element.closest() when use context
- get the unique id from dom element id, then get the provide value from store
Current drawback: it will always get the default context when component render, because the framework hasn't bind the reactive state and dom, and it's a little tricky to find the ancestor context provider.
Try it on sandbox: https://codesandbox.io/p/sandbox/vanjs-context-provider-poc-qsgdr8?file=%2Fsrc%2Fmain.ts%3A5%2C1
I want to know if there any recommendations about this.
Hello @Hunter-Gu Can you update us on the progress of the issue?
Hello @yahia-berashish
I have updated the example by using VanX reactive, it works really good and simple.
I'm still trying to fix the problem: it will always get the initial value at first time painting. The key is we can't get the time when a dom mounted to the document. That make me think about Web Component. I'm working on this way now.
I think the approach you are using is wrong, even if the problem of the first painting was solved, there are still issues of passing props and having to create a separate context for actions, etc.
I chatted a bit with ChatGPT about this issue, and after much trial and error I think I found a better way, I will try and open a PR as soon as possible,
The code has some issues currently, but I think the approach is promising.
This is the Stackblitz project containing the code @Hunter-Gu
give it a try, would like to hear your opinion:
VanJS Context API test
Thank you for your help and time @Hunter-Gu
That's weird, it seems when a child component is rendered conditionally it uses the state of the last provider.
I suppose this is the last issue with the implementation, I will try to fix it, but will appreciate if you can take a look at it too @Hunter-Gu
Actually, this is why I want to find a tree structure to represent the component tree.
I find there is no 'real' component in VanJS internally, the VanJS component is a way to provide childDOM, there are no lifecycles, no component instance, etc. So it will be a little hard to implement Context in VanJS.
That's true, but the lack of a virtual DOM like the one you described is the main benefit of VanJS, a virtual DOM bloats the package and makes it very hard for the developer using it to know what is actually happening inside of the app.
But the features provided by the vDOM can be replaced with lightweight implementations that don't require such a complex base.
Hope the context API we are working on will be one of them.
it will call getProvider when render functional component, but the DOM hasn't rendered, so getProvider will return default value firstly
when DOM get committed to the document, I will update with the correct context value and this can work correctly. But VanJS will delay all updates to next render task, so the user can still see the page render with default value at first time.
I don't have a clear idea yet. I will let you know if there are any updates.
I create a new version, sandbox , there are still issues of removing unmounted component node.
The key steps:
createComponent: create a component node, pass node and util functions as props
we can get parent/children in current component by node
commit util function: render in current component node scope, which can keep the correct scope of conditional rendering. we can always render sub components by commit to keep consistent.
If integrate with Web Component, we can easily support functional component lifecycles also.
I think this gives us a glance about how to support context.
Give it a try if you have time, would like to hear your opinion. Thank you!
Hello @Hunter-Gu
The result looks great, and it works fine, but I think it changes the way VanJS is used too much, and I think it would be better to separate the component implementation into a different package.
This is the discussion to talk about that further.
Would like to hear your opinion.
Hello again.
Hi @Hunter-Gu
I was busy for a while, I would be happy to hear from you on anything new regarding the development of a Context API in VanJS.
Hopefully, I will be able to finish working on this little project soon.
With VanJS' design, I don't think the Context API approach is needed here. You could just create a reactive state externally and bind to it from anywhere else in the code. When it is updated, all components bound to it will update.
Here's an example of what I mean:
store.ts
import van from "vanjs-core"
export const theme = van.state("light")
topbar.ts
import van from "vanjs-core"
import theme from "./store.ts"
const { div, p, button } = van.tags;
export default function Topbar() {
return div(
p("Dark mode"),
input({
type: "checkbox",
checked: theme.val === "dark",
onchange: e => (theme.val = e.target.checked ? "dark" : "light")
})
);
}
app.ts
import van from "vanjs-core"
import theme from "./store.ts"
import Topbar from "./topbar.ts"
const { div, p } = van.tags;
export default function App() {
return div(
{ className: () => theme.val === "light" ? "theme-light" : "theme-dark" },
Topbar(),
// ... rest of app
);
}
I understand, the issue is in hopes of adding a Context-like API to Van, but with the current design, the state variables are private to the auth-provider file and nothing external can change it arbitrarily. We then export the required state and/or derived values as well as modification functions.
Any file which needs the auth context can simply import this object and work with it. This is the best way I think this can be handled similarly to how React context behaves. The only drawback here is, the context object can also be used by components not under the auth-provider in this case.
Hello @kwameopareasiedu
Your implementation looks to be a good starting point, me and @Hunter-Gu have made some prototypes in the past that almost achieved everything needed for the Context API but encountered issues when trying to handle reactivity in nested children components.
I think it is good to make the implementation requirements clear too:
The component consuming the context (the consumer) should be able to access the data directly as well as modify it reactively.
Any children of the direct consumer should be able to access the context's data as well without the need to mark them individually.
Each context can have multiple providers each one with its own data.
Consumers within multiple nested providers should access only the data of the nearest parent provider.
I'm currently working at @b-rad-c VanCone add-on which can help the context development too, as well as trying to add TypeScript support for it, and will appreciate any help.
Hello.
I have thought about this a bit and I think that implementing a React-like context API might be too complicated for a lightweight library like VanJS.
I took a look at Svelte's implementation, and I think Svelte-like stores implementation is a better fit.
Svelte provides writable, readable, and derived functions, these functions return objects similar to observables in RxJS, where you can subscribe to them, which can be used to update the DOM each time they are changed, this makes them a great choice for global data management.
They don't have the ability to provide different values to different parts of the DOM tree, but this functionality can be replaced with derived stores, granted, this reduces some of the flexibility React's contexts can provide, but it also reduces complexity, and believe me, the Context API can turn into a mess pretty quickly.
Maybe we can try this approach @Hunter-Gu
But it's really up to you since I'm a bit busy right now, so it's just a suggestion.
Discussed in https://github.com/vanjs-org/van/discussions/152
Hello @Hunter-Gu Can you update us on the progress of the issue?
Hello @yahia-berashish I have updated the example by using VanX
reactive
, it works really good and simple.I'm still trying to fix the problem: it will always get the initial value at first time painting. The key is we can't get the time when a dom mounted to the document. That make me think about Web Component. I'm working on this way now.
Can you share a CodeSandbox/Stackblitz link so I can check out the code
Sorry for late response.
The codesandbox link: https://codesandbox.io/p/sandbox/vanjs-context-provider-poc-base-on-webcomponent-c4qp5h
I haven't fix it. Because VanJS will batch all updates to next render task, so user will still see the first painting with default value.
I think the approach you are using is wrong, even if the problem of the first painting was solved, there are still issues of passing props and having to create a separate context for actions, etc. I chatted a bit with ChatGPT about this issue, and after much trial and error I think I found a better way, I will try and open a PR as soon as possible, The code has some issues currently, but I think the approach is promising. This is the Stackblitz project containing the code @Hunter-Gu give it a try, would like to hear your opinion: VanJS Context API test Thank you for your help and time @Hunter-Gu
Can this approach support to consume context in conditional rendering?
I try to consume context in conditional rendering, it can't render as expected.
VanJS Context API with conditional rendering You can search
! render context consumer in condition !
to find the issue.Thank you for your sharing. @yahia-berashish
That's weird, it seems when a child component is rendered conditionally it uses the state of the last provider. I suppose this is the last issue with the implementation, I will try to fix it, but will appreciate if you can take a look at it too @Hunter-Gu
Actually, this is why I want to find a tree structure to represent the component tree.
I find there is no 'real' component in VanJS internally, the VanJS component is a way to provide
childDOM
, there are no lifecycles, no component instance, etc. So it will be a little hard to implement Context in VanJS.I will keep trying to fix it on our versions. 😄
That's true, but the lack of a virtual DOM like the one you described is the main benefit of VanJS, a virtual DOM bloats the package and makes it very hard for the developer using it to know what is actually happening inside of the app. But the features provided by the vDOM can be replaced with lightweight implementations that don't require such a complex base. Hope the context API we are working on will be one of them.
Totally agree with you. I like VanJS because it is so tiny and it can run in browser directly with well-designed component-oriented development.
The key point of Context-API is we need to know the ancestor provider of consumer.
Here are two approachs I can think of:
How would you go about getting the scope?
Now, I can only know DOM tree is not a good way.
getProvider
when render functional component, but the DOM hasn't rendered, sogetProvider
will return default value firstlyI don't have a clear idea yet. I will let you know if there are any updates.
Hi @yahia-berashish,
I create a new version, sandbox , there are still issues of removing unmounted component node.
The key steps:
createComponent
: create a component node, passnode
and util functions as propsnode
commit
util function: render in current component node scope, which can keep the correct scope of conditional rendering. we can always render sub components bycommit
to keep consistent.If integrate with Web Component, we can easily support functional component lifecycles also.
I think this gives us a glance about how to support context.
Give it a try if you have time, would like to hear your opinion. Thank you!
Hello @Hunter-Gu The result looks great, and it works fine, but I think it changes the way VanJS is used too much, and I think it would be better to separate the component implementation into a different package. This is the discussion to talk about that further. Would like to hear your opinion.
My view is completely the same as yours.
A separated library can keep van-core tiny, and give VanJS the ability to support most modern features.
Let's talk about this. Thank you for your help.
Hello again. Hi @Hunter-Gu I was busy for a while, I would be happy to hear from you on anything new regarding the development of a Context API in VanJS. Hopefully, I will be able to finish working on this little project soon.
@yahia-berashish, I apologize for being busy with my personal matters recently and not responding in a timely manner.
If we don't consider implementation details for now, our ideas are completely aligned:
and these are most important.
So I think we can aim to implement it as soon as possible, and then improve it based on the requirements.
And this implementation should be simple, straightforward, and we can easily build upon it for further improvements.
With VanJS' design, I don't think the Context API approach is needed here. You could just create a reactive state externally and bind to it from anywhere else in the code. When it is updated, all components bound to it will update.
Here's an example of what I mean:
store.ts
topbar.ts
app.ts
@kwameopareasiedu Hi, thank you for your example.
Actually, your example is about state management, not context.
Let's review the definition of the Context API.
Context is a way to do state management, but state management is not Context.
Context can be very useful when we are creating complex component, and one example that comes to mind is form group.
I hope this helps you understand the intention behind this issue.
@Hunter-Gu Having reviewed the previous discussion thread, I think I have more context (pun intended) to this.
With VanJS' design, I think this may be a possible approach. Let's use a sample auth context for this example
auth-provider.ts
I understand, the issue is in hopes of adding a Context-like API to Van, but with the current design, the state variables are private to the
auth-provider
file and nothing external can change it arbitrarily. We then export the required state and/or derived values as well as modification functions.Any file which needs the auth context can simply import this object and work with it. This is the best way I think this can be handled similarly to how React context behaves. The only drawback here is, the context object can also be used by components not under the auth-provider in this case.
Hello @kwameopareasiedu Your implementation looks to be a good starting point, me and @Hunter-Gu have made some prototypes in the past that almost achieved everything needed for the Context API but encountered issues when trying to handle reactivity in nested children components.
I think it is good to make the implementation requirements clear too:
I'm currently working at @b-rad-c VanCone add-on which can help the context development too, as well as trying to add TypeScript support for it, and will appreciate any help.
Hello. I have thought about this a bit and I think that implementing a React-like context API might be too complicated for a lightweight library like VanJS. I took a look at Svelte's implementation, and I think Svelte-like stores implementation is a better fit. Svelte provides
writable
,readable
, andderived
functions, these functions return objects similar to observables in RxJS, where you can subscribe to them, which can be used to update the DOM each time they are changed, this makes them a great choice for global data management.Or maybe a shortcut syntax:
They don't have the ability to provide different values to different parts of the DOM tree, but this functionality can be replaced with derived stores, granted, this reduces some of the flexibility React's contexts can provide, but it also reduces complexity, and believe me, the Context API can turn into a mess pretty quickly.
Maybe we can try this approach @Hunter-Gu But it's really up to you since I'm a bit busy right now, so it's just a suggestion.