Closed Mati365 closed 3 years ago
What exactly do you mean by that?
You can, for example, render every part of you app in a new JavaScript task. Like here.
You can also use Nano.render()
inside a componenet, to start rendering another component. Nano JSX is very flexible in that manner.
try adding something like Query component inside tree. That component should execute promise and render its result into children. It would be nice to support it also in SSR.
example: https://www.apollographql.com/docs/react/data/queries/
I see. I have just tried to implement async rendering on the server. And it did work well. I will make some more tests in the next days, but I guess this is a nice feature to add.
I will also try to implement something similar on the client side.
The example below works as expected. The App Component returns a promise that will resolve 1 second later.
// mock fetch request
const fetch = (_url: string): Promise<any> => {
const data = [{ title: 'cool title' }, { title: 'nano jsx' }]
return new Promise((resolve) => {
setTimeout(() => {
resolve(data)
}, 1000)
})
}
class App extends Component {
async fetch() {
const data = await fetch('https://someurl.com')
return (
<ol>
{data.map((d: any) => (
<li>{d.title}</li>
))}
</ol>
)
}
// @ts-ignore
render() {
return this.fetch()
}
}
renderSSRAsync(<App />).then((html) => {
console.log(html)
})
Have you tried doing something like it:
<>
<QueryA />
<QueryB />
</>
is it executing parallel or QueryB is waiting for QueryA to be done?
Well, if queryA and queryB return a Promise you should be able to use Promise.all()
. JSX is not different han JavaScript. You can do everything you want. For example this:
<>
<QueryAll>
<QueryA />
<QueryB />
<QueryAll>
</>
But, I will have a look and see what I can implement.
I think it would maybe make sense to switch to async rendering by default. For now, a component can return fragments, arrays, functional components, and class components. With async rendering, it would be possible to also return Promises and Promis Arrays.
I think this would be a great feature :D
What we usually call asynchronous rendering is not Promise.all. It is similar to react fiber and Suspense. Instead of any promises, it uses queues to achieve concurrency.
react fiber and Suspense
I don't know react fiber and I have never used Suspense.
Since Nano JSX does not use a vDOM and does not keep track of the tree, I don't think I would implement a global render queue. Isn't promise a good idea?
Take a look at the example below (my quick try of a Suspense implementation). You just need to pass a promise
and a fallback
to <Suspense />
. Once the promise resolves, <Suspense />
renders its children and passes the resolved promise as a data
props to them. <Comments />
simply has to work with props.data
. What do you think? I think it is a clean solution and the tree stops rendering until the promise resolves.
import * as Nano from '../core'
import { Component } from '../component'
// mock a data fetch
const fetchComments = (): Promise<string[]> => {
const comments = ['comment one', 'comment two']
return new Promise((resolve) => {
setTimeout(() => {
resolve(comments)
}, 1000)
})
}
// the suspense component
class Suspense extends Component {
data: any = null
async didMount() {
// resolve the promise
this.data = await this.props.promise
// add data as props to children
this.props.children.forEach((child: any) => {
if (child.props) child.props = { ...child.props, data: this.data }
})
// update the component
this.update()
}
render() {
return !this.data ? this.props.fallback : this.props.children
}
}
// Comments component
const Comments = (props: { data?: string[] }) => {
console.log('Render Comments')
const comments = props.data?.map((c) => <li>{c}</li>)
return <ul>{comments}</ul>
}
// Loading component
const Loading = () => <div>loading...</div>
// App component
const App = () => (
<div>
<h2>Comments</h2>
<Suspense promise={fetchComments()} fallback={<Loading />}>
<Comments />
</Suspense>
</div>
)
// Render to the root element
Nano.render(<App />, document.getElementById('root'))
This looks even cleaner:
<Suspense />
gets the comments
as promise and passes it to <Comments />
as props
.
I'm really happy with that approach.
const Comments = ({ comments }) => {
const tmp = comments?.map((c) => <li>{c}</li>)
return <ul>{tmp}</ul>
}
const Loading = () => <div>loading...</div>
const App = () => (
<div>
<h2>Comments</h2>
<Suspense comments={fetchComments()} fallback={<Loading />}>
<Comments />
</Suspense>
</div>
)
Well, that's not what I'm trying to say.
Because I need isomorphism, now I have implemented fiber asynchronous rendering on the client. If promise is used on the server, the rendering on the client will be destroyed.
Isomorphism requires that the rendering mechanism of client and server will not interfere with each other.
Because I need isomorphism, now I have implemented fiber asynchronous rendering on the client. If promise is used on the server, the rendering on the client will be destroyed.
I don't understand what you mean by that.
I just made <Suspense />
fully isomorphic. Take a look:
The App component is nice and clean and works 100% isomorphic.
// fetch comments
export const fetchComments = (): Promise<string[]> => {
const comments = ['comment one', 'comment two']
return new Promise((resolve) => {
setTimeout(() => {
resolve(comments)
}, 1000)
})
}
// App Component
export class App extends Component {
static fetchComments(): any {}
render() {
return (
<div>
<h2>Comments</h2>
<Suspense cache comments={App.fetchComments() || fetchComments} fallback={<Loading />}>
<Comments />
</Suspense>
</div>
)
}
}
Client-Side:
Nano.render(<App />, document.getElementById('root'))
Server-Side:
const server = async () => {
// prefetch comments
const comments = await fetchComments()
// add to static method
App.fetchComments = () => () => comments
// render
renderSSR(<App />)
}
server()
I'm very happy with the current implementation of <Suspense />
and will not continue working on another async rendering method for now.
See some <Suspense />
examples here: https://nanojsx.github.io/examples.html
Add parallel async rendering, it might be killer feature