React: The great Abstraction
React + JSX was launched at JSConf in 2013, I was one of the fortunate Javascript developers to attend the conference, and I will say the reaction and response on the initial announcement was pessimistic at best. The JS nation revolted against this XML like syntax in their JS for like 6 months, then the rest is history. React has grown into the most popular front-end library in the community.
React the planet! 🌎
Hack the planet was the nerd rallying cry in the movie Hackers 1995
Over the last 8 years, we have seen React implemented in just about every presentation layer you can think of, mobile, tvs, terminal, etc. We have also seen React implement music, animations, and more. React creates a declarative composable model on top of Javascript.
Why not servers? 🖥️
There is a project called React-Nil, this project simply lets you create react components that return null
. What? What good does that do? Well, this allows developers to use React on the server.
Mad Science 🧬
Disclaimer: I have no idea if this is a good idea or not, but lets have some fun! 🤓
What if we could use React to express our server API?
render(
<App>
<Request path="/" handler={hello} />
<Listen port={3000} />
</App>
)
Why not? 🤷
With React-Nil we can!
In this demo we are using Deno 1.8 with Opine web framework
create new folder called :hyper-sauce
mkdir hyper-sauce
cd hyper-saurce
touch hyper.js
touch mod.jsx
touch import_map.json
setup our dependencies in import_map.json
{
"imports": {
"react": "https://cdn.skypack.dev/react?dts",
"react-nil": "https://cdn.skypack.dev/react-nil?dts",
"opine": "https://deno.land/x/opine@1.1.0/mod.ts"
}
}
modify hyper.js
with our server components
import React from 'react'
import opine from 'opine'
export function Listen({app, port}) {
app.listen(port)
console.log('server listening on port ', port)
return null
}
export function Request({app, method='get', path="/", handler}) {
app[method](path, handler)
return null
}
pretty cool, we are writing react functional components
export function App(props) {
const app = opine()
return React.Children.map(props.children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { app })
}
return child
})
}
save hyper.js
mod.jsx
NOTE: Deno automatically handles JSX or TSX, just name your file with .jsx or .tsx extension. ✨
import React from 'react'
import { render } from 'react-nil'
import { App, Request, Listen } from './hyper.js'
render(
<App>
<Request path="/" handler={hello} />
<Listen port={3000} />
</App>
)
function hello(req, res) {
res.send('Hello from React on the Server!')
}
Let's run it!
deno run --allow-read --allow-net --import-map=./import_map.json mod.js
Open up a browser and navigate to http://localhost:3000
You should see 'Hello World'!
One step further
How about we take this a step further, and instead of passing a handler function, lets use a child component.
render(
<App>
<Request path="/">
<Show />
</Request>
<Request path="/_add" method="POST">
<Inc />
</Request>
<Request path="/_sub" method="POST">
<Dec />
</Request>
</App>
)
We can modify the Request component to pass a handler function to the child components.
export function Request({app, method="get", path="/", children}) {
return React.Children.map(children,
child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, {
handler: fn => app[method](path, fn)
})
}
return child
})
}
Then we can implement our handlers using components:
function Hello({handler}) {
handler((req, res) => res.send('Hello!'))
return null
}
You can run the full demo at https://github.com/hyper63/react-server-demo.git
Summary
React is a powerful abstraction, with context, hooks, suspense, and so much more, it could possibly be useful on the server, with the React-Nil library we can experiment and push the boundaries and see what shakes out!