REACT.js and render but using innerHTML of element rendering into? - reactjs

I have a need and I can't seem to overcome it.
Because my page uses both templating rendering AND react... I have this div that has an id, lets say 'foo'.
Now, I'd like to take the contents of "foo", THEN render my component with the contents of foo. My React component needs to have a certain look, but the page template has another.... does that make sense?
ie.
page renders with something like:
<div id="foo">
<ul>
<li>Item One</li>
<li>Item two</li>
</ul>
</div>
then in my jsx file, I have:
var fooElem = document.getElementById('foo');
var contents = fooElem && fooElem.innerHTML || '';
if(fooElem) {
React.render(
<MyCOMPONENT data={contents} />,
fooElem
);
}
I figure it would be an easy thing to just get the innerHTML of the div and then render a component in it with 'new' component content surrounding the 'innerhtml' that I grabbed. As you can see, I take a prop of "data" and I use that within my component to spit it out again, but in a different format. etc... that "foo" div is handed back by the backend, so I can't alter it other than "trying to get the innerHTML, manipulate it, and render a component into that space..".
Any help would be appreciated.
I get errors that the element has been modified and such.. and seems to just go in some kind of loop.

You could do something along the lines of:
'use strict';
var React = require('react');
var myComponent = React.createClass({
getInitialState: function() {
return {
content: ''
};
},
// Learn more about React lifecycle methods: https://facebook.github.io/react/docs/component-specs.html
componentWillMount: function() {
// Get the content of foo
var content = document.getElementById('foo').innerHTML;
// Remove the old templated foo from the page
// This assumes foo is a direct child of document.body
document.body.removeChild(document.getElementById('foo'));
this.setState({
content: content
});
},
render: function() {
<div id={'foo'}>
{this.state.content}
</div>
}
});
// This assumes you want to render your component directly into document.body
React.render(myComponent, document.body);
This will:
Read the contents of foo into the React component's state.
Remove the foo element from the page.
Render the new React component (with the content of foo) into the page.

Related

Sending data to an indirect child in React.js on a click event

I have an application that has two major components (Landing and Skills):
App = React.createClass({
render() {
return (
<div>
<Landing />
<Skills category={category}/>
</div>
);
}
});
Within "Landing", I have a SocialMenu component, that has a list of items (the list of items is fed to SocialMenu like: <SocialMenu items={ ['Home', 'Services', 'About', 'Contact us']} />. On click, the item that is clicked is highlighted.
SocialMenu = React.createClass({
getInitialState: function(){
return { focused: 0 };
},
clicked: function(index){
this.setState({focused: index});
},
var self = this;
return (
<div>
<ul className="testblocks">{ this.props.items.map(function(m, index){
var style = '';
if(self.state.focused == index){
style = 'focused';
}
return <li key={index} className={style} onClick={self.clicked.bind(self, index)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
});
What I would like to have happen, is have the index data from SocialMenu, passed to the Skills component.
However, I am not sure how to do that, because SocialMenu is a child of Landing, and not of Skills. (pretty much I would like to keep the list in landing, but have the output of the click of that list be put in Skills).
How would I do this?
You need something like Flux to have communication betweeen components that don't have parent-child relationship:
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
So, for your case, you need to have a event like SELECT_SOCIAL that will pass the index information and your Landing component will have a listener to receive the index information.
You really don't need Flux for this. You probably will at some point, but if this is all you need to do, why don't you move the state up to the App component, and send it Landing & Skills components as props. You would probably also need to pass the clicked handler as a prop to SocialMenu.

ReactJS component rendering and how to append elements to the div the component is mounted on

I'm having this use case where there is a 'load more' button on the page to fetch more items from an API endpoint, and so I want these items to get appended to the DOM when they arrive. The thing is that the HTML page that is served by the web server comes with some extra list items (as seen below) within the same item-list div container I mount my React component, with empty data/props, on page load.
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
My assumption is that if I handle this in the ReactJS way, as soon as I fetch more items from the server (REST) and append those items to an 'itemList' state array, react will somehow replace all of the content that holds that 'item-list' div where the component was mounted on.
A quick workaround that I'm thinking would work and that it doesn't rely on the isomorphic stuff and pre-rendering the react component on the server, is to create a separate sibling div having the same div class name 'item-list' and adding an id attribute to mount the component on, so the resulting HTML would go like:
<div class="item-list">
<div class="item-list__child-item"></div>
<div class="item-list__child-item"></div>
...
<div class="item-list__child-item"></div>
</div>
<div class="item-list" id="react-component-name"></div>
Maybe there is a cleaner way to do it without getting into the isomorphic stuff, or maybe I'm not understanding the React concept and how it works. Anyways will appreciate any directions you may have on this.
OK, your question wasn't clear on this, but the data that is represented by what was generated in the HTML will be entirely different from the data that you will be getting via AJAX.
There's a simple solution to this. Instead of creating an entirely new DOM element that will be adjacent to your original DOM layout, what you will do is grab the data that was already there, store it into an array, and append the new data that you will grab via AJAX into that Array. This way, you will reap the benefit of React's DOM diffing. Why is it useful? Maybe you want to let the user sort the data, or interact with the data directly, while it will remain in full control of a parent React component.
So anyways, take a look at this fiddle: https://jsfiddle.net/x4jjry04/3/. It's based on Paul Booblic's fiddle.
var Page = React.createClass({
getDefaultProps: function () {
return {
items: []
}
},
getInitialState : function(){
return{
items : this.props.items
}
},
componentDidMount: function () {
// Mimics an AJAX call, but replace this with an actial AJAX call.
setTimeout(function () {
var dataFromAjax = ['one', 'two', 'three'];
this.setState({
items: this.state.items.concat(dataFromAjax)
});
}.bind(this));
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
var elements = document.querySelectorAll('.item-list__child-item');
var initialvalues = Array.prototype.slice
.call(elements)
.map(function (div) {
return div.innerHTML;
});
React.render(<Page items={initialvalues} />, document.body);
Check this simple fiddle demo:
https://jsfiddle.net/x4jjry04
var Page = React.createClass({
getInitialState : function(){
return{
items : ["one","two","three"]
}
},
addClick : function(){
this.state.items.push("more");
this.forceUpdate();
},
render : function(){
return <div>{this.state.items.map(function(item){return <div className="bla-bla-class">{item}</div>})}<br/><div onClick={this.addClick}>ADD</div></div>;
}
});
React.render(<Page />, document.body);
I assume you are using react serverside to render the list?
On page load you fetch the original list from the server and have the component "re-render" the elements. re-render is in quotes, because React wont actually update the list unless the list changes. Now you are setup with a component that works as expected, and you can add elements to the list as you want.
The general Idea with isomorphic/universal React is that you treat your app as a normal Single Page App, and let React handle the magic of dirty checking.
This also means that you can use the same component when rendering on the server, since your component doesn't contain any client specific code.

How to appendChild to a react component in the correct place within a jest test DOM

I have a react component consisting of a wrapper div and a checkbox inside, here is the render function:
render: function(){
return (
<div id="wrapperDiv">
<input type="checkbox" id="checkbox" />
</div>
)
}
On componentDidMount I swap out this checkbox for a div that will act as the checkbox, here is the componentDidMount function;
componentDidMount: function(){
var div = document.createElement("div");
div.id = "checkboxDiv";
this.getDOMNode().appendChild(div);
}
when rendering this on screen the empty div is added to the component and all works fine.
On my jest test I try this;
TestUtils.scryRenderedDOMComponentsWithTag(component, "div");
But the length of this array is 1, bringing back only the div with the id of "wrapperDiv" looking as though it has not added the checkboxDiv to the DOM. However when I try this in the test;
component.getDOMNode().parentNode.getElementsByTagName("div");
It brings back the wrapperDiv and the checkboxDiv
Is there a reason it doesn't add the div in the component, or is there an extra step needed in the test?
TestUtils.scryRenderedDOMComponentsWithTag() only looks at Reacts internal render tree. And since you're adding the DOM node without React knowing about it, the DOM node won't show up in Reacts render tree.

Is there another way to render in ReactJs

I am new to ReactJs. From the examples, I can see that one needs to call
React.render(elementToBeReadered, targetingElement). Is there a way to use the web components defined in React directly, like angularjs' directive? E.g.
<Hello />
var Hello = React.createClass({
render: function() {
return (
<div>
Hello World!
</div>
);
}
});
So that I don't need to add a target element like <div id='target-element'></div> and then render it with React.render(<Hello />, document.getElementById('target-element')). Why should I duplicate this everywhere?
You'll typically nest react components within each other. In angular, this would be similar to having many ng-app on many different elements.
If you want to have regular DOM, with react components only sparsely populated, then you'll have to render by element reference as you said. I would try to use react components to compose the entire app instead.
Is there a way to use the web components defined in React directly, like angularjs' directive?
Sure, you can build any system you like on top of React.render. You give it a react element and a dom node, and it does its thing. You could build an angular directive that renders the component you like, for example:
var reactComponents = {Foo: Foo};
module.directive('react', function($parse){
return {
link: function(scope, element, attrs){
var props = Object.keys(attrs).reduce(function(props, key){
if (key === "component") return props;
props[key] = $parse(attrs[key])(scope);
return props;
}, {});
var reactElement = React.createElement(reactComponents[attrs.component], props);
React.render(reactElement, element);
}
};
});
not tested
And in your template:
<react component="Foo" bar="1" baz="something.otherThing"></react>
If you add some watchers it'll respond to expressions changing, and you can do other things like error handling, resolving the component class with $injector rather than a static object hash, and handling '$destroy' (see React.unmountComponentAtNode).

react.js component: can't get keyDown events if component inside another one

JS Bin Available here
I have a component called ComponentB for which I want to get key down events:
var ComponentB = React.createClass({
render: function() {
return (
<div
contentEditable
onKeyDown={this.handleKeyDown}>{this.props.data}</div>
);
},
handleKeyDown: function(e) {
console.log("B: " + e.type +"-" + e.which);
}
});
I can get those events if this ComponentB is directly under the main/App component.
If I try to embed this component inside another component (componentA) I don't receive those events anymore (this.props.lines is an array of 3 strings) :
var ComponentA = React.createClass({
render: function(){
return (
<div
contentEditable>
{
this.props.lines.map(function(line,i) {
return <ComponentB key={i} data={line} />
})
}
</div>
);
}
});
The associated JS BIN shows this behavior : if you try to edit the lines in the component A section. no event will be emitted but they will if you edit the sinbgle instance of componentB below...
Looks to me like a bug in react.js but wanted to check here first.
As #ssorallen said in a comment, you can't have nested contentEditable elements, with or without react.
It seems to be a problem with nested contentEditable elements. Remove contentEditable from ComponentA, and it works as you expect.
One of the reasons this doesn't work, is because React doesn't really support contentEditable. Basically it sets the attribute, but it can't sensibly render into the contentEditable because when it tries to update it... the DOM has changed without its knowledge (which throws an error).
Rather you should treat contentEditable elements like an input element, which doesn't have children. Instead you update the innerHTML (see renderComponentToString and dangerouslySetInnerHTML) and listen to onInput events to get changes. See Stack Overflow: onChange event for contentEditable for details on how this works.
A proper handling of contentEditable has been discussed briefly, but no solution was arrived at. Feel free to suggest good ways to handle it.

Resources