Creates a Surface + LiveView container for rendering and updating React components.
- LiveView js hook which performs performs rendering or hydration of the component
- A LiveView React Context that is injected with each render/update providing access to LiveView js functions
- An optional SSR macro that can assist with generating placeholder or static react markup at compile time
- This runs a hydrate when mounting, as it was rendered server side
In the future:
- Integrate Surface with React Server Components if possible i.e use Surface for writing the React Server Component (Surface AST -> react/jsx?)
Create a components object to be passed into the hook generation function.
It is recommended to create a module that exports all your components used by react-surface.
assets/js/components/index.js
:
import Component1 from "./component1"
import Component2 from "./component2"
export default {
Component1,
Component2,
}
assets/js/app.js
:
import components from "./components"
import { buildHook } from "react-surface";
// pass component mapping to buildHook function
const reactSurfaceHook = buildHook(components)
// setup liveview as normal, merging your hooks with react-surface hooks.
let liveSocket = new LiveSocket("/live", Socket, {
{...otherhooks, ...reactSurfaceHook},
params: { _csrf_token: csrfToken },
});
If not performing SSR via use ReactSurface.SSR
this can be skipped.
mix gen_ssr_script
This is used to configure node_ssr
at compile time to understand where your components are, and how many instances you are running
config :node_ssr,
# REQUIRED - This be the folder with assets, and a package.json file - passed to erlexec `:cd` option
assets_path: "#{File.cwd!()}/assets",
# this is the name of the script to be invoked - defaults to "ssr.js", can change it here.
script_name: "ssr.js"
Optional requirements:
component_path: "js/components" # this is the default, relative path to your components directory from assets/.
component_ext: ".js" # this is the default, used with nodejs require statements
count: 1 # this is the number of workers in the nodejs cluster - likely not necessary to have more than 1, unless rendering lots of components
Where props
is a map of JSON serializable values.
<React component="HelloReactSurface" props={{ %{name: "Doug"} }}/>
Supply an rid
when rendering multiple of the same component on the same page. (translated to DOM id attrubute)
<React rid="unique_id" component="HelloReactSurface" props={{ %{name: "Doug"} }}/>
This will result in the following DOM being generated in Elixir.
<div
id="SHA1:8"
rs-c="HelloReactSurface"
rs-p="eyJuYW1lIjogIkRvdWcifQo"
rs-m="r"
phx-hook="_RS"
>
<div id="r <> SHA1:8" phx-update="ignore"></div>
</div>
The props are being base64 encoded (no padding) for the DOM attribute The ids are generated sha1 hashes, based on the component name, and optional rid prop
When server rendering it is similar - but with the rendered component contents as a child of the inner div on initial render, and a different hook
<div
id="SHA1:8"
rs-c="HelloReactSurface"
rs-p="eyJuYW1lIjogIkRvdWcifQo"
rs-m="h"
phx-hook="_RS"
>
<div id="r <> SHA1:8" phx-update="ignore"><!-- SSRed REACT ROOT --></div>
</div>
LiveView events can be accessed via the useLiveContext
React hook exported from the javascript package.
This hook returns an object with the functions: {handleEvent, pushEvent, pushEventTo}
See the HelloReactSurface.js component for an example.
If available in Hex, the package can be installed
by adding react_surface
to your list of dependencies in mix.exs
:
def deps do
[
{:react_surface, "~> 0.1.0"}
]
end
From github:
def deps do
[
{:react_surface, github: "harmon25/react_surface"}
]
end
Add react-surface
as a dep in your package.json
{
"react-surface": "file:../deps/react_surface"
}
In your assets dir:
npm install react react-dom --save
npm install @babel/preset-env @babel/preset-react --save-dev
Add "@babel/preset-react"
as a babel preset in .babelrc
Add the following to your assets/webpack.config.js
file to ensure only a single react + react-dom is included in your bundle:
module.exports = (env, options) => ({
// add:
resolve: {
alias: {
react: path.resolve(__dirname, './node_modules/react'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom')
}
}
// ...
});
This library is inspired by react-phoenix and phoenix_live_react.
Check em out if you want to use react components in an eex or leex templates.