I have a react app, and in one of the components I would like to render a Phosphor desktop (http://phosphorjs.github.io).
Web sources suggest the other way around, running React component inside a Phosphor Widget, but I need to wrap the Phosphor desktop with widgets inside a React component instead. Not sure how to go about it.
This is my first attempt:
import React, { Component } from 'react';
import './App.css';
import { DockPanel } from 'phosphor-dockpanel';
import { Message } from 'phosphor-messaging';
import { TabPanel } from 'phosphor-tabs';
import { ResizeMessage, Widget } from 'phosphor-widget';
class MyWidget extends Widget {
static createNode() {
console.log('widget:createNode');
var node = document.createElement('div');
var app = document.createElement('div');
app.className = 'todoapp';
node.appendChild(app);
return node;
}
constructor(model) {
super();
this.addClass('TodoWidget');
this._model = model;
console.log('widget:constructor');
}
get model() {
console.log('widget:getModel');
return this._model;
}
onAfterAttach(msg) {
console.log('onAfterAttach');
//this._model.subscribe(() => this.update());
this.update();
}
onUpdateRequest(msg) {
console.log('widget:onUpdateRequest');
var data = { model: this._model };
var host = this.node.firstChild;
//React.render(React.createElement(app.TodoApp, data), host);
}
}
class ReactDockPanel extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.panel = new DockPanel();
this.panel.id = this.props.id;
this.widget = new MyWidget();
this.panel.insertLeft(this.widget);
}
componentDidMount() {
this.panel.attach(this.node);
}
render() {
return (
<div ref={el=>this.node=el}>
</div>
);
}
}
class App extends Component {
render() {
return (
<ReactDockPanel id='main'>
</ReactDockPanel>
);
}
}
export default App;
I'm not sure how to replace React.render(React.createElement(app.TodoApp, data), host); Phosphor docs are for reference but not for learning so I'm going by their only example code.
Ideally I'd like to be able to render like this:
<ReactDockPanel id='mydesktop'>
<SomeWidget insert='left' />
<SomeOtherWidget insert='right' />
</ReactDockPanel>
Not sure it can be done.
I was able to make something similar work with React portals. In my case I'm trying to render React Component children instead of Widget children but hopefully it's still helpful. The steps I used are
1) In the initial render of my outer React component, use ref to get a reference to the element.
2) In componentDidMount, create a new DockPanel and attach it to the element. Then, loop through the widgets you want to have and a) Add each widget to the DockPanel, and b) Build up a list of nodes in which you will render React components and do this.setState with the list.
3) In the next render call, loop over the list of nodes in this.state and create portals.
The code looks something like this:
import {DockPanel, Widget} from "#phosphor/widgets";
import {createPortal} from "react-dom";
interface IWidgetInfo {
component: JSX.Element;
node: HTMLElement;
}
interface IDockState {
widgetInfos: IWidgetInfo[];
}
interface IDockProps {
children: JSX.Element[];
}
class WrapperWidget extends Widget {
constructor(name: string, node: HTMLElement) {
super({node});
this.setFlag(Widget.Flag.DisallowLayout);
this.title.label = name;
}
}
export class Dock extends React.PureComponent<IDockProps, IDockState> {
elem: HTMLElement;
dock: DockPanel;
componentDidMount() {
this.dock = new DockPanel(this.context.store);
let widgetInfos = [];
for (let component of this.props.children) {
let node = document.createElement("div");
let widget = new WrapperWidget("Widget Name", node);
this.dock.addWidget(widget);
widgetInfos.push({node, component});
}
this.setState({...this.state, widgetInfos});
Widget.attach(this.dock, this.elem);
}
render() {
return (
<div ref={(c) => this.elem = c}>
{this.state.widgetsInfos.map(widgetInfo => {
return createPortal(widgetInfo.component, widgetInfo.node);
})}
</div>
);
}
}
Related
I am very new to react.js and I have been working on a component class (child) that has functions and a single state object and my end goal is to use this class in a parent class so it, in turn, can call its functions and update the state.
The problem I have been running into is that:
I wasn't aware of a component's lifecycle, and
I come from a heavy C# background
Meaning: I have been treating these component classes like I would any C# class instead of JavaScript. I know that now.
But I need help evaluating my approach and solving this issue I keep seeing:
This is my child Class component
import React, { Component } from 'react';
import axios from 'axios';
export default class ClassB extends Component {
constructor() {
super();
console.log("ClassB constructor got called");
this.state = {
users: [{ name: '', email: '' }]
};
}
getUsers() {
let URL = "https://localhost:5001/api/FooController/FooAction"
let myParam = 100;
axios.get(URL,
{
params: { myParam }
})
.then(response => {
// handle logic here...
}).catch(function (error) {
console.log('What happened? ' + error.response.data);
});
}
addUserData(name, email) {
this.setState(this.state, { users: [name, email] });
}
componentDidMount() {
console.log("ClassB componentDidMount got called");
}
render() {
console.log("ClassB render got called");
return ( null )
}
}
And in my parent class (Home.js) I am instantiating the child class (ClassB.js) and using its instance as such:
import React, { Component } from 'react';
import ClassB from './ClassB'
import ClassC from './ClassC'
const classBComponent = new ClassB();
export class Home extends Component {
static displayName = Home.name;
constructor() {
super();
}
componentDidMount() {
this.timerID = setInterval(() => {
classBComponent.getUserValues();
}, 3000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
render() {
myComponent.render();
return (
<div className="container">
<h1>My Other Component:</h1>
<div className="row">
<div className="col-sm">
<ClassC name={[{ component: classBComponent, info: ['John', 'john123#123.com'] }]} />
</div>
</div>
</div>
);
}
}
In this parent class I intend to call the "getUserValues() methods from the ClassB component. I also have another child component (ClassC), which is a functional component, and I want to pass the instantiated ClassB component so it can access its functions and states as well. But, in ClassC component, when I call "addUserData()" method it gives me the error I pasted above (see image).
Here is how I have my ClassC set up:
import React, { useEffect } from 'react';
import axios from 'axios';
const ClassC = (props) => {
// variables
let user = props.name[0].info[0];
let email = props.name[0].info[1];
// component
const component = props.name[0].component;
// renders component
function componentMount() {
component.addSimModelNodeInfo(user, email);
}
// leaves the component
function componentUnmount() {
}
useEffect(() => {
componentMount();
return () => {
componentUnmount();
}
}, [])
return (
<div className="card shadow-sm p-3 mb-5 bg-white rounded">
<div className="card-header">
<h5>{name}</h5>
<h5>{email}</h5>
</div>
</div>
);
}
export default ClassC;
I mentioned earlier how I didn't have a solid grasp on components' lifecycles. I placed those console.logs in ClassB only to realize that the only method getting called is the constructor. The componentDidMount() function never gets called and neither does the render(). Why is that? I know its linked to that error which is why my ClassB component never gets "mounted". What am I doing wrong? Many thanks in advance.
Here's an example of calling a parent component function from a child component. I'm using functional components for brevity, but this is totally possible with class-based as well.
If you're not familiar with functional components, you can consider props to be like this.props, and the function itself is similar to the render() method in classful components. There's more to it, but for this small example that should help you if you need to translate.
const ChildComponent = (props) => <button onClick={props.onClick} />
const ParentComponent = () => {
const onClick = () => {
console.log("Click Clack");
}
return <ChildComponent onClick={onClick} />;
};
im having a real hard time learning react and I can't quite understand how to solve this problem. I have Tournaments.js which reads tournament names from an API and displays them as clickable elements with a reference to templates.js in which I would like to simply display the name of the element that was clicked.
Tournaments.js:
import React, { Component } from "react";
const API = 'http://localhost:8080/api/tournaments';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
componentDidMount() {
fetch(API)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
})
})
}
testfun(e) {
var target = e.target;
console.log(target)
}
render() {
return(
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="jumbotron text-center">
{
this.state.data.map((dynamicData, key) =>
<div>
<a key={dynamicData.id} href={"/#/template"} onClick={this.testfun}>{dynamicData.name}</a>
</div>
)
}
</div>
</div>
</div>
</div>
)
}
}
export default Tournaments;
template.js:
import React, { Component } from "react";
import Tournaments from "./Tournaments";
const API = 'http://localhost:8080/api/tournaments';
class template extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
render() {
return(
<div>
<h1>clicked item</h1>
</div>
)
}
}
export default template;
And my API stores data looking like this:
[{"name":"abc","id":1,"organizer":"kla"},{"name":"fsdfs","id":2,"organizer":"fsdf"}]
I've tried to use onclick to get the value that was clicked but not sure how to display it in template.js.
Thanks for any help
I think you want to put user clicked item from Tournaments.js and display in Template.js
You can try to put Template inside Tournaments
Tournaments.js
import React, { Component } from "react";
import Template from './template';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
clickedData: []
}
}
testfun(e) {
var target = e.target;
console.log(target);
let newClickedData = [];
newClickedData.push(target);
this.setState({
clickedData: newClickedData
});
}
render() {
return (
<div>
...
...
<Template clickedData={this.state.clickedData} />
</div>
);
}
}
export default Tournaments;
Template.js
import _ from 'lodash';
import React, { Component } from "react";
class template extends Component {
render() {
let clickedData = this.props.clickedData;
let displayClickedData = _.map(clickedData, (item) => {
return <div>item</div>;
});
return(
<div>
{displayClickedData}
</div>
)
}
}
export default template;
I would suggest picking one of the following options:
Wrap both Tournaments and Tournament in some parent component. Then if you click on any specific tournament, you can change its ID in state. If ID is null, you render list of tournaments, if ID is defined, you render your tournament component. That way you would lose the URL effect.
You can pass ID as GET parameter in URL and then read it in your Tournament component, so your code would look similar to this <a href={`/#/template?id=${dynamicData.id}`}>{dynamicData.name}</a>
You can try out React Router which would handle all that for you
Below is the code that I have implemented:
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import {Table, Column} from '../../Layout/components/Table'
import ReactDOM from 'react-dom'
// import ExternalPortal from '../../Sites/components/ExternalPortal'
export const TableName = 'ProjectDashboard'
class MyWindowPortal extends Component {
static propTypes = {
children: PropTypes.node,
closeWindowPortal: PropTypes.func
}
constructor (props) {
super(props)
this.containerEl = document.createElement('div') // STEP 1: create an empty div
this.externalWindow = null
}
componentDidMount () {
// STEP 3: open a new browser window and store a reference to it
this.externalWindow = window.open('', '', 'width=600,height=400')
// STEP 4: append the container <div> (that has props.children appended to it) to the body of the new window
this.externalWindow.document.body.appendChild(this.containerEl)
this.externalWindow.document.title = 'A React portal window'
// copyStyles(document, this.externalWindow.document)
// update the state in the parent component if the user closes the
// new window
this.externalWindow.addEventListener('beforeunload', () => {
this.props.closeWindowPortal()
})
}
componentWillUnmount () {
// This will fire when this.state.showWindowPortal in the parent component becomes false
// So we tidy up by just closing the window
this.externalWindow.close()
}
render () {
// STEP 2: append props.children to the container <div> that isn't mounted anywhere yet
return ReactDOM.createPortal(this.props.children, this.containerEl)
}
}
export default class ProjectTable extends Component {
constructor (props) {
super(props)
this.state={showWindowPortal:false}
this.toggleWindowPortal = this.toggleWindowPortal.bind(this)
this.closeWindowPortal = this.closeWindowPortal.bind(this)
}
static propTypes = {
data: PropTypes.object.isRequired,
loginId: PropTypes.string.isRequired
}
componentDidMount () {
window.addEventListener('beforeunload', () => {
this.closeWindowPortal()
})
}
toggleWindowPortal () {
this.setState({showWindowPortal: !this.state.showWindowPortal})
}
closeWindowPortal () {
this.setState({showWindowPortal: false})
}
render () {
return (
<div>
<div>
<p>This div is just for testing click here to see the portal</p>
{
this.state.showWindowPortal &&
(
<MyWindowPortal closeWindowPortal={this.closeWindowPortal}>
<button
onClick={() => this.closeWindowPortal()}
>
Close
</button>
</MyWindowPortal>
)
}
<button onClick={this.toggleWindowPortal}>
{this.state.showWindowPortal ? 'Close the' : 'Open a'} Portal
</button>
</div>
</div>
)
}
}
The button inside the portal doesn't trigger anything on click. It doesn't fire at all. Please note that MyWindowPortal opens a new window and the button gets rendered in that window. Tried testing it on Firefox and Chrome. I am not sure if I am doing anything wrong.
The solution has been posted here
https://github.com/facebook/react/issues/12355
The issue is that you're first creating a DOM node in one document, but then moving it to another one. However React has already bound the event handlers to the first document. React doesn't expect that you would move a DOM node between documents while something is rendered into it
To fix it, you can change your code so that you move the node before rendering something into in React
class Window extends React.Component {
constructor(props) {
super(props);
this.state = { win: null, el: null };
}
componentDidMount() {
let win = window.open('', '', 'width=600,height=400');
win.document.title = 'A React portal window';
let el = document.createElement('div');
win.document.body.appendChild(el);
this.setState({ win, el });
}
componentWillUnmount() {
this.state.win.close();
}
render() {
const { el } = this.state;
if (!el) {
return null;
}
return ReactDOM.createPortal(this.props.children, el);
}
}
I've faiced the same problems with onClick handlers but in my case I used component inside WindowPortal.
My workaround for the problem was usage of ref's and manual assignment of onclick event handlers to each of button elements I had inside my component.
Here is snippet example:
class SomeComponent extends Component {
constructor(props) {
super(props);
this.ref = (element) => {
element.querySelector('button.one').onclick = () => {
alert( 'One!' );
}
element.querySelector('button.two').onclick = () => {
alert( 'Two!' );
}
}
}
render() {
return <div className="some-component" ref={this.ref}>
<button type='button' className='one'>Click One</button>
<button type='button' className='two'>Click Two</button>
</div>;
}
}
Btw, I'm using redux and SomeComponent successfully connects to store and allows dispatching of actions.
Given this component :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionView extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<section className="subscription-view">
<h3>Loading...</h3>
</section>
);
}
return (
<section className="subscription-view">
{ this.props.children }
</section>
);
}
};
And another component :
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<SubscriptionView subscription="allFoo">
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
</SubscriptionView>
);
The first Subscription is re-rendered when the data is available, however the second one is rendered only once and nothing more. If I place a console.log(this.props.subscription, ready); inside the render function of SubscriptionView, I see
allFoo false
allFoo true
singleBar false
and that's it.
On the server side, both publish methods are
Meteor.publish('allFoo', function () {
console.log("Subscribing foos");
return Foos.find();
});
Meteor.publish('singleBar', function (id) {
console.log("Subscribing bar", id);
return Bars.find({ _id: id });
});
Both of the publish methods are being called.
Why isn't the second SubscriptionView reactive?
* Solution *
This is based on alexi2's comment :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionLoader extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
done: false,
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
componentDidUpdate() {
if (!this.state.done) {
this.setState({ done: true });
this.props.onReady && this.props.onReady();
}
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<div>Loading...</div>
);
}
return null;
}
};
Then, inside the parent component's render method :
<section className="inventory-item-view">
<SubscriptionLoader subscription='singleBar' params={ this.props.id } onReady={ this.setReady.bind(this, 'barReady') } />
<SubscriptionLoader subscription='allFoos' onReady={ this.setReady.bind(this, 'foosReady') } />
{ content }
</section>
Where setReady merely sets the component's state, and content has a value only if this.state.barReady && this.state.foosReady is true.
It works!
Try separating out your SubscriptionView Components like this:
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<div>
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
<SubscriptionView subscription="allFoo">
<div>Rendered Again!</div>
</SubscriptionView>
</div>
);
Edit from comments conversation
Not sure if I am on the right track but you could build Foo as a 'smart' component that passes props to each SubscriptionView as required, and then use Foo as a reusable component.
Let's say that what I need to render is FooBarForm, which requires both Foos and Bars to be registered, in that specific use case. How would you do that?
You could create Components Foos and Bars that took props as required and create a parent component FooBarForm that contained those Components and passed the necessary data.
FooBarForm would handle the state and as that changed pass it to the props of its child components.
Now state is being centrally managed by the parent component, and the child components render using props passed from the parent.
The child components would re-render as their props changed depending on whether the state being passed from the parent component had changed.
I'm developing my first app and still learning the flow.
So suppose I have a component called:
Parent which holds a method HelloWorld() like the following example:
import React, { Component } from 'react';
class Parent extends Component {
Helloworld() {
console.log('Hello world');
}
render () {
return (
<View>{this.props.children}</View>
)
}
}
module.exports = Parent;
and then i want to import this in to another component and use its method then how do I do it?
Ill write another short example of how I would implement it.
import React, { Component } from 'react';
import { Parent } from 'path to parent';
//or
const Parent = require('path to parent');
//which of these is better?
class Home extends Component {
Helloworld() {
console.log('Hello world');
}
render () {
return (
<Parent>
// this is what i need
<Button onClick={parent.Helloword()}>Some Button</Button>
</Parent>
)
}
}
module.exports = Home;
Thank you in advanced for your help.
Usually you should pass info from parent to child through props.
parent.jsx:
import Child from './child';
class Parent extends Component {
constructor(props) {
super(props);
this.helloWorld = this.helloWorld.bind(this);
}
helloWorld() {
console.log('Hello world!');
}
render() {
return (
<View><Child method={this.helloWorld} /></View>
);
}
}
child.jsx:
class Child extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Button onClick={this.props.method} />
);
}
}
Edit: about preference between import and require, I believe it's a matter of taste, but I think import is cleaner.
You can read React Native-Tutorial-What's going on here? about import. and here
We can pass a prop in the child class:
And then call it from the child: this.props.propName()
We can pass string, numbers, functions, array, objects in prop
import React from 'react';
import {
View,
Text,
} from 'react-native';
var Parent = React.createClass({
render: function() {
return (
<Child foo={()=>this.func1()} bar={()=>this.func2()} />
);
},
func1: function(){
//the func does not return a renderable component
console.log('Printed from the parent!');
}
func2: function(){
//the func returns a renderable component
return <Text>I come from parent!</Text>;
}
});
var Child = React.createClass({
render: function() {
this.props.foo();
return (
<Text>Dummy</Text>
{this.props.bar()}
);
},
});
module.exports = Parent;