Closed dkoontz closed 8 years ago
The easiest way would be to add a renderToReactClass
method to Pux that takes a component and returns a react class to use, which takes its initial state from the component props.
In your Pux component you'd have a method that exports a react class:
toReact :: forall eff. Eff eff ReactClass
toReact = do
app <- start config
renderToReactClass app.html
and then in your JS React component:
var puxComponent = require('./purs/someComponent.purs').toReact()()
However, using a React component in a Pux component is another thing entirely and most likely not possible without some sort of purescript wrapper.
The main use case I have is using existing React components from within Purescript so it sounds like a wrapper is the way to go. Do any major issues spring to mind with that approach? My plan was to replace our top-level component with a Pux component, continue rendering all our sub-components and then proceed down each component chain replacing one layer of React component at a time.
With regards to rendering, is the result of the render function of a React component the same as the VirtualDOM type that Pux produces from the view function? That is, would a reasonable type signature for a React component's render function be something like () -> VirtualDOM
or just VirtualDOM
assuming I'm not going to try and capture the side effects of the render function in the type signature?
Using React components from within PureScript/Pux might be a little more difficult, depending on the situation.
The approach would be to create an FFI function that has signature: foreign import fromReact :: Array (Attribute a) -> Array (Html a) -> Html a
. You'd then need to transform the array of attributes into an object that is passed to React.createElement
. Internally, the Attribute
type is a JS array ["key", "val"]
, and children (Html a
) are react elements. Does that make sense? Once you've written that function you should be able to embed your JS components in your Pux views, and pass attributes or children to them. It might help to look at the render function in src/Pux.js
. Internally, Html a
is represented by a React element returned from React.createElement
.
Yes that is very helpful thanks!
Something like this might work, though I haven't tested it:
foreign import fromReact :: String -> Array (Attribute a) -> Array (Html a) -> Html a
exports.fromReact = function (modulePath) {
var comp = require(modulePath);
return function (attributes) {
var props = attributes.reduce(function (obj, attr) {
var key = attr[0];
var val = attr[1];
obj[key] = val;
return obj;
}, {});
return function (children) {
return React.createElement(comp, props, children);
};
};
};
It'd be great for Pux to have toReact
and fromReact
.
I may have spoken too soon. During the final render, element event handlers are mutated to inject the action input channel, so this will interfere with any event handlers in the virtual DOM tree returned by the external JS React component. Not sure the best approach to this at the moment.
With Pux v3.0.0
I've added toReact
and fromReact
methods. You can now use external react components and vice-versa. See the section on React Interop in the guide for information on how to do this. I'd like to get a more comprehensive example published when I have some time.
I tested toReact replacing the version I had written myself and it worked great! One question that I was trying to figure out when rolling my own version was how to allow a React component to pass down props into a Pux component. Obviously the main function (or toReact in the case of the docs) can accept parameters, but since that is only called once that wouldn't work for anything dynamic. Receiving props from a parent component feels like it should be handled some sort of propsUpdated
function with the same signature as update that could produce a new state. Any ideas on how this could be done?
Instead of
var Counter = PS.Counter.toReact();
it should be
var Counter = function Counter(state) {
return PS.Counter.toReact(state);
};
That should work if I understand the issue correctly.
So what you are suggesting does work for passing params to toReact, I did have to add a parens since toReact was returning a React constructor function and not the component.
function render() {
const Component = TestComponent.toReact(isEditing)();
return (
<div>
<Component />
</div>
);
}
This works and my component can correctly render based on the input, however if isEditing changes, then the entire state of my component is reset because toReact
is going to call start
again with a new initial state. This is why I was looking for a way to pass along some sort of props-like thing and have them merged into the component's state via a updateViaProps :: ExternalProps -> State -> State
type function.
I will work on more use cases to understand if I can accomplish what I'm trying to do without this kind of functionality and come back with hopefully a more concrete example.
Thanks for the update. I'll have to think about this more as well.
So thinking about this some more, I believe all of my current use cases would be covered by having a facility to send an action to the top-level component. In this way, when some async or UI thing happens outside the component the React component can forward that to the Pux component via an action. This interaction is ripe for issues if the JS layer sends in an invalid action but it has the nice property of not requiring the component to support any sort of new way of updating. Maybe there is a facility for sending actions to the ReactComponent created by Pux.toReact
already but I didn't see it. A custom signal could be passed in to the inputs
array of start
that comes from FFI and thus could be written to by JS, but it would be really nice to have that already wired up. Does that seem like a good approach?
That seems like a good approach. Have a signal passed to inputs
array of start
that comes from FFI. I can't think of any cleaner way to do that. Pux could make that easier by somehow doing that for you, but I'm not sure what that would look like. I'm open to pull requests.
I have since wired up a new signal that is passed into my top level component and everything works great that way.
I had looked around for information about using Pux with existing React components but was not successful in finding anything. I have an existing React system and I'd like to start converting it over bit by bit into Purescript. Is there an existing way to have a view render existing components including passing down props? If not, where would be a good place to look to add such functionality?