Passing children from XHP to ReactJS - reactjs

I have an XHP component:
final class :common:message-stripe extends :ui:base {
use XHPReact;
protected function compose() {
$this->constructReactInstance( "MessageStripe", Map {} );
return <div id={$this->getID()} />;
}
}
that should look like this in my .php file:
<common:messagestripe>
This is my message :)
</common:messagestripe>
On the ReactJS side, the component looks something like this:
var MessageStripe = React.createClass({
render: function() {
return (
<div className="message"> {this.props.children} </div>
);
}
});
However, I get errors about rendering null in my ReactJS component, which means that children are not sent correctly. So, my question: how can I pass the children from XHP to ReactJS?

You're not including your children when rendering your XHP component.
final class :common:message-stripe extends :ui:base {
use XHPReact;
protected function compose() {
$this->constructReactInstance( "MessageStripe", Map {} );
return <div>{$this->getChildren()}</div>;
}
}
Additionally, (assuming you built your :ui:base element with attribute forwarding as outlined here: Building a Good UI Framework with XHP) you don't need to manually set the ID of your root element, XHPJS::element() will do that for you.

Related

Passing the translate value to the React component using react2angular

This is the component that I'm using:
<my-component
data="vm.data"
></my-component>
I would like to pass it a translated string, but I get a syntax error:
<my-component
data="vm.data"
string="{{ 'TOP_FIVE' | translate }}" // throws error in browser console
></my-component>
How can I pass along the translated string value?
Since you are using angular-translate the best way is to inject the $translate service to your react2angular component and extend your react component with a function that return the translated string:
// INJECT SERVICE
angular
.module("components", [])
.component(
"myComponent",
react2angular(MyComponent), ["data"], ["$translate"])
)
// REACT COMPONENT
class MyComponent extends React.Component {
translate = key => {
return this.props.$translate.instant(key)
}
render() {
return (
<p>{this.translate('TOP_FIVE')}</p>
)
}
}

Conditionally render list with Higher Order Component

My app has a feature toggle functionality that tells my UI whether or not a piece of UI should be rendered. I would like to create a Higher Order Component to conditionally render these types of components. In one scenario, I'm trying to conditionally render a list, but I'm running into this error:
ConditionalRender(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
This makes sense since I just am trying to render the children of this component. Here's what I've got so far:
https://jsfiddle.net/fmpeyton/cykmyabL/
var settings = { showHello: true, showGoodbye: false};
function ConditionalRender (props) {
var output = null;
if(props.shouldRender) output = props.children;
console.log(output);
// return (<li>{output}</li>); // This works, but isn't desired html structure
return ({output});
}
function App (props) {
return (
<ul>
<ConditionalRender shouldRender={props.settings.showHello}>
<li>Hello!</li>
</ConditionalRender>
<ConditionalRender shouldRender={props.settings.showGoodbye}>
<li>Goodbye...</li>
</ConditionalRender>
</ul>
);
}
ReactDOM.render(
<App settings={settings} />,
document.getElementById('container')
);
If I can help it, I would just like to render the children without any additional logic.This HOC would also handle more complex children down the line. Something like this:
<ConditionalRender shouldRender={props.settings.showHello}>
<div>
<p> blah blah blah</p>
<table>
<!-- ... -->
</table>
</div>
</ConditionalRender>
Any ideas?
Try this:
function App(props) {
return (
<ul>
{props.settings.showHello && <li>Hello!</li>}
{props.settings.showGoodbye && <li>Goodbye...</li>}
</ul>
);
}
P.S. Your code doesn't work because of this line:
return ({output});
Assuming you have es2015 support, it would be treated as object property shorthand. So it's the same as:
return {output: output};
which is not what React expects to get from the render method.
You could try this:
function ConditionalRender(props) {
if (props.shouldRender) {
// Make sure we have only a single child
return React.Children.only(props.children);
} else {
return null;
}
}
But this is not the simplest way. See more here.
P.P.S. Your ConditionalRender component is not what is called Higher-Order Component. According to the docs, HOC is a function that takes a component and returns a new component.

How do properly use Onsen's Navigator in React?

I'm trying to implement a simple Onsen Navigator in React.
So far I'm receiving an error 'route is not defined' and I was looking through the examples & docs but I only saw the initialRoute prop was provided, how & where does the route prop generated or something? Cause it seems like its not specified.
Here is my the code of my component:
import React, {PropTypes} from 'react';
import ons from 'onsenui';
import * as Ons from 'react-onsenui';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
index : 0
};
this.renderPage = this.renderPage.bind(this);
this.pushPage = this.pushPage.bind(this);
}
pushPage(navigator) {
navigator.pushPage({
title: `Another page ${this.state.index}`,
hasBackButton: true
});
this.setState({index: this.state.index++});
}
renderPage(route, navigator) {
return (
<Ons.Page key={route.title}>
<section style={{margin: '16px', textAlign: 'center'}}>
<Ons.Button onClick={this.pushPage}>
Push Page
</Ons.Button>
</section>
</Ons.Page>
);
}
render() {
return (
<Ons.Page key={route.title}>
<Ons.Navigator
renderPage={this.renderPage}
initialRoute={{
title: 'First page',
hasBackButton: false
}}
/>
</Ons.Page>
);
}
};
SignUp.propTypes = {
'data-pageName': PropTypes.string.isRequired
};
export default SignUp;
Is this the right syntax in ES6? Have I missed something?
When using Ons.Navigator in react the two required properties are:
initialRoute - it should be an object.
renderPage - method which receives 2 arguments - route and navigator. The route should be an object similar to the initialRoute one. You provide that object when you are calling pushPage and similar methods.
It seems that you already know these 2, but there still 2 other things which you need to be careful about. They are not directly onsen related, but come up a lot when using react in general.
Whenever you have a list of dom elements (for example an array of Ons.Page tags) each of those should have a unique key property.
Whenever you use a method you need to make sure you are binding it if you need some extra arguments.
It seems you also know these two. So the only thing left is to make sure you follow them.
Your syntax is correct - the only thing missing is the route variable in SignUp.render. Maybe you originally copied the renderPage method and that is how you have a leftover Ons.Page.
If you're not putting the SignUp component inside some other navigator, tabbar or splitter then you don't actually need the Ons.Page in its render method. Those are the only cases when they are needed. If you it happens to have one of those components as a parent then you can just specify the key.
PS: I think there should be a React Component Inspector (something like this) which you can install - then I think you may be able to see the place where the error occurs. I think if you knew on which line the problem was you would have been able to solve it. :)
For me, with the object I was passing to initialRoute(), it needed a props property, which itself was an object with a key property. See the before and after below.
Before fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage}}
renderPage={this.renderPage}
/>
);
}
}
This was causing the following console warning:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Navigator`.
After fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage, props: {key: 'DataEntryPage'}}}
renderPage={this.renderPage}
/>
);
}
}
Notice that the difference I needed to make was the addition of , props: {key: 'DataEntryPage'}.
Feel free to check out this medium article for more information.

creating table with reactjs

I am trying to create a table using reactjs. I wrote a class and using this as directive, passing config and data and trying to populate the table.
function createRows() {
let self = this;
return <tbody>{_.each(self.props.tableData, function(r) {
<tr>
{_.each(self.props.columnConfigs, function(c) {
<td>{r[c.columnName]}</td>
})}
</tr>
})}</tbody>;
}
function createHeaders() {
let self = this;
return <thead>
{_.each(self.props.columnConfigs, function(c) {
<th>c.displayName</th>
})}
</thead>
}
class TableComponent extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (<table>
{createHeaders.call(this)}
{createRows.call(this)}
</table>)
}
}
function TableComponentFactory() {
return TableComponent;
}
export default TableComponentFactory;
But this is not working. it throws an error:-
Invariant Violation: Objects are not valid as a React child (found: object with keys {columnName, displayName, isSortable, path, hidden, columnStyle, editable, isId}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `TableComponent`.
which is actually the columnConfig. I am not able to understand why this happening. What mistake I am doing, also a bit of explanation will be great.
Instead of using 'each' (from which function no return value is expected), you should use 'map' and use 'return (<JSX Element>)' in its body. Each should be use to apply an action on every item in an array without returning data. But you want to return data, because you want the function to supply rows and columns.

How to setState to one particular component instance in RefluxJS?

I'm currently doing one small project in React + Flux (RefluxJS) and I faced wit one issue I can't solve. I would be very gratefully if someone of you can give me a hand.
Here you have the link to GitHub with the whole project in order to facilitate your help, it's a simplest version just to reproduce the problem I faced.
My doubt is, how can I use one component in the same view with different content. Let me explain:
I have on component in, "components/threads.jsx" which in summary render this peace of code getting the data from the store ("stores/thread-store.jsx") throug a fake API ("utils/api.jsx"):
renderThreads: function() {
return this.state.thread_content.map(function(thread, i) {
return (
<div key={thread.id}>
<div className="row">
<div className="col-md-10">
<a data-toggle="collapse" href={'#thread-' + thread.id} className="faq-question">{thread.name}</a>: <small>{thread.content}</small>
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div id={'thread-' + thread.id} className="panel-collapse collapse ">
<Posts id={thread.id} />
</div>
</div>
</div>
</div>
);
});
},
As yo can see, I have another component nested called "Posts" in "components/thread-posts.jsx" which is rendered for each thread is mapped. In the "Posts" component I have this peace of code:
module.exports = React.createClass({
mixins: [
Reflux.listenTo(PostsStore, 'onChange'),
],
getInitialState: function() {
return {
posts: []
}
},
componentDidMount: function() {
Actions.getPosts(this.props.id);
},
render: function() {
return (
<div>
{this.renderPosts()}
</div>
);
},
renderPosts: function() {
if(this.state.posts.comments != undefined){
return this.state.posts.comments.map(function(post, i) {
return(
<div key={i} className="faq-answer">
<p>
{post.content}
</p>
</div>
);
});
}
},
onChange: function(event, posts) {
this.setState({
posts: posts,
});
}
Here comes the problem. When finish the render, all the threads have the same posts, in particular the lasts ones were set. I think is related with the states, if they change it will be change in all the components were rendered.
So, my question is, how can I deal with it in order to have the posts according to its thread but using the same component? If it's not posible, which is the best solution to do that?
I hope explained myself as well as enough to understand me.
I will be very gratefully if you can give me a hand in this issue.
Thanks in advance.
Yes if you share the Store with your View then latest one will be overwrite your previous Component's data
you have to create a separate variable which can hold the data of each API Call and when call API you have to pass the Key like
let's take one example
I have one Component it's called MainComponent
I want to use same Component on same page twice but the data should be different for both component
I have one Store for MainComponent called MainStore
In MainStore I have one Method called getData like
getData:function(key)
{
//API Call
}
now I am calling this method from my MainComponent from componentDidMount event like
componentDidMount:function()
{
//if you used Action then you have to called like
MainComponentAction.getData(this.props.Key);
// if you directly called
MainComponentStore.getData(this.props.Key);
}
here this.props.Key you have to pass from the parent component which should 1 or 2
now come to store we have passed the Key so now we have to check condition while we received a data from API
suppose I have taken one variable in which I am storing the data which is return by API like
now you have to create two methods for store the data based on key
var _data,_data1
function loaddata(APIdata,key)
{
if(key===1)
{
_data=APIdata
}
else
{
_data1=APIdata
}
}
and in your store you to methods for getting the data like
getData:function()
{
return _data;
},
getData1:function()
{
return _data1;
}
now your getintialState of MainComponent Should be
getintialState:function()
{
return{
Data:JSON.Parse(MainComponentStore.getData()),
Data1:JSON.Parse(MainComponentStore.getData1()),
}
}
and your MainComponent render function should be
render:function(){
var LatestData;
if(this.props.Key===1)
{
LatestData=this.state.Data
}
else if(this.props.Key===2)
{
LatestData=this.state.Data1
}
return(
<div>
{LatestData}
</div>
)
}

Resources