Reputation: 7966
I know that using Elm
components ("modules"? still learning parlance) within React
is an established pattern with libraries like react-elm-components
, but is there a way to do the inverse?
e.g., something like this is possible:
Top-level React App
-> React layout/component
-> Elm components
But what about this:
Top-level Elm App
-> Elm layout/components
-> React components
If it is possible, is the 'elm-at-the-bottom' pattern superior? Trying to reason why there doesn't seem to be literature on it & that's a possible explanation.
Upvotes: 12
Views: 2926
Reputation: 1073
As pointed out, this doesn't work as-is because of onload
, but may help someone else looking for something similar
You can always render a React component--'til you get around to rewriting it ;)--inside an Elm node. The trick is going to be mounting it. Shooting from the hip: it'd be a little hacky (and a bit expensive with a Json.Encode.encode
and JSON.parse
since you have to get the data out of Elm and into a consumable JS format for the component), but assuming you have ThatComponent
, React
, and ReactDOM
on the window
object you could try something along the lines of
import Html exposing (Html, div)
import Html.Attributes as Attr exposing (attribute)
import Json.Encode as Encode exposing (Value)
reactComponent : String -> (a -> Value) -> a -> Html msg
reactComponent componentName encoder data =
let
jsonProps : String
jsonProps =
Encode.encode 0 <| encoder data
-- a script to load the React component on `this` which is
-- the `div` element in this case.
onLoad : String
onLoad =
String.join ""
[ "javascript:"
, "window.ReactDOM.render("
, "window.React.createElement(window[\""
, componentName
, "\"], JSON.parse("
, jsonProps
, ")), this)"
]
in
div [ attribute "onload" onLoad ] []
What you will have though is a problem with it maintaining its own state--which is obviously bad and runs against the Elm architecture (but you probably know that and want to reuse something pre-built). You can use ports to send in data back in, but you'd want to wrap that reactComponent
in Html.Lazy.lazy3
so it doesn't rerender itself (which is why you'd send in both the encoder and data separately).
Upvotes: 4
Reputation: 21005
I've been trying to do the same. I took a popular React date picker and started by interoperating via ports. It's still work in progress but does work
import React from 'react';
import ReactDOM from 'react-dom';
import * as Datetime from 'react-datetime'
function datepicker(d, cb) {
return requestAnimationFrame( () => {
ReactDOM.render(Welcome(d, (res) => {
cb(res);
}), document.getElementById('reactDate'));
});
}
function Welcome(initialdate, cb) {
return (
<div>
<h1>{initialdate}</h1>
<Datetime onChange={cb} value={initialdate} />
</div>
);
}
export default datepicker;
Then my ports code looks like
import datepicker from './reactDPPort.jsx';
elm.ports.trigger.subscribe( (s) => {
datepicker(s, res => app.ports.toElm.send(res.toString()));
});
Upvotes: 2
Reputation: 30015
Don't do that. The whole point of Elm is hexagonal design. Elm is intended to function as a closed system, one in which the evils of JS cannot penetrate. This is why embedding Elm in JS is first class, and you are having such trouble finding the inverse. The idea of embedding React into Elm is to hand over the value of Elm's determinism and assurances. You might as well not use Elm at all.
If you really just want to do functional programming in the browser and use React components with the Elm architecture, check out PureScript PUX. PUX is designed for that use-case and is going to give you better results than hacking Elm to do things Elm was specifically designed to prevent.
If my comments are a non-starter for you I can offer the following advice for being evil in this way, without being toooo evil.
First, you can accomplish this with ports
. Write your Elm application to fire events on a port to JavaScript when you want to render your react component. Then render the react component inside of the Elm Dom space, in the standard way with JavaScript.
Second (this is the closest to doing it properly you are going to get), you can write this using Elm Native modules. This will require you to learn Elm's undocumented runtime, and write code that interacts with it. What you will do is write a custom Html element in Elm's low level system, that rendered React inside itself and manages re-rendering.
Good luck!
Upvotes: 11