I have a react component rendering on page load. The content includes lots of rich media that I want to lazy load only when the content is on screen and subsequently unload it when it's not. More content is loaded as the user scrolls.
I'm using a combination of techniques to handle lazy loading iframes, videos, and images and it works well outside of content rendered via React. Mostly custom jQuery and the Lazy Load Anything library.
My main issue is that I can't get my lazy load function to trigger on content just placed into the dom. It works once the user resizes/scrolls (I have a events for this that are triggered appropriately). How do I get it to trigger when the content is available?
I've tried triggering it from componentDidMount but this doesn't seem to work as the content has yet to be placed into the DOM.
I suppose I could just check for content every n seconds but I'd like to avoid this for performance reasons.
Here's my simplified code:
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
// What do I put here to trigger lazy load? for the rendered content?
myLazyLoad(); // does not work on the new content.
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
With the npm package react-lazy-load-image-component, you just have to wrap the components that you want to lazy load with <LazyLoadComponent> and it will work without any other configuration.
import { LazyLoadComponent } from 'react-lazy-load-image-component';
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<LazyLoadComponent>
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
</LazyLoadComponent>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
Disclaimer: I'm the author of the package.
If you are trying to use the jquery plugin you may end with a DOM out of sync with that rendered by React. Also in your case the lazy load function should be called in the EntriesList component, not from its parent.
You could use a very simple component as react-lazy-load:
https://github.com/loktar00/react-lazy-load
or just take inspiration from its source code to implement your own.
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<LazyLoad>
<img src="1px.gif" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</LazyLoad>
</div>
);
});
return(<div>{entries}</div>);
}
});
Try to check on scroll event to your div parent container (the div that you render on App class:
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
myLazyLoad: function(e) {
// here do actions that you need: load new content, do ajax request, ...
// example: check you scrolling and load new content from page 1, 2, 3 ... N
var self = this;
$.get('path/to/json/?page=N', function(data) {
self.setState({entryItems: data.entries});
});
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div onScroll={this.myLazyLoad}><EntriesList items={this.state.entryItems} /></div>);
}
});
Lazy Loading React
A component can lazily load dependencies without its consumer knowing using higher order functions, or a consumer can lazily load its children without its children knowing using a component that takes a function and collection of modules, or some combination of both.
https://webpack.js.org/guides/lazy-load-react/
Related
I am having an issue when enabling deferred updates in the knockout library. I have implemented Jquery datatables as a component, when navigating to a view that has this component i can see the following methods being called in order.
Getview>Activate>Attach
everything works as expected
But if i press f5 and refresh the page rather than navigating to it from another page it breaks and the following methods are called
Getview>Activate>Attach>Getview>Activate>Attach>Detach>Detach (not sure why its called twice in the end)
and it breaks, no table shows on the UI at all as it does not render from what i can tell, i think it has something to do with durandal transitions and there being a difference between navigating to a page and refreshing a page kinda grasping at straws tho.
This is a minimal class that replicates the problem for me, note i dont have an HTML file for this component i want to use the getView method to pass in some dynamic HTML from JQueryDT
I created a quick sample project with the bare minimum needed to replicate the problem.
https://bitbucket.org/dchosking1988/deferred-update-example
If you pull that and run it you will see that the "hello world" will disappear when you refresh the page but it wont if you navigate between tabs.
the general steps i used to replicate the issue are
1) download sample project
2) add test component (see repo above for the sample file)
3) enable deferred updates
4) disable view caching
4) try compose new instance of the component
Edits to give more info
*This is not a JQuery Datatable problem, it is replicated with the following
So you dont have to download the gitRepo, this is the code i can replicate the problem with in the sample project following the above steps.
define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
if (!currentView) {
currentView = $(defaultViewHtml)[0];
}
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
Then Add this HTML to the index.html, also dont forget to create an instance of the class in the index.js
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>
This is occurred because it call code twice and second called the currentView stay empty in test.js, I commented the stretch where you set the currentView and code work.
self.getView = function () {
console.log('GetView');
//if (!currentView) {
// currentView = $(defaultViewHtml)[0];
//}
return currentView;
};
-
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>
define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
currentView stay empty in test.js,
So i have a small react component which renders an iframe. I need to determine when all the content (html etc) has fully loaded.
The iframe is loading an orbeon form which can takes a few seconds to fully load.
I have tried hooking into the 'load' event form the iframe which works but triggers the even the second the iframe is loaded and not when the iframes content has loaded.
I have read some posts about listening to the 'DOMContentLoaded' even but cannot seem to get that to work.
here is the react component which renders the Iframe.
basically I need to trigger the documentDrawerLoaded function once all of the iframe content has been rendered.
return React.createClass({
displayName: 'FilePickerColourSelector',
getInitialState: function() {
return {
listVisible: false
};
},
componentWillMount: function () {
console.log('loading...')
},
componentDidMount: function () {
this.refs.documentDrawer.getDOMNode().addEventListener('load', this.documentDrawerLoaded);
},
documentDrawerLoaded: function () {
console.log('drawer has been loaded');
document.getElementById('js-document-drawer-overlay').classList.toggle('active');
document.getElementById('js-document-drawer').classList.toggle('active');
},
render: function() {
var documentDrawerStyles = {
height: '100%',
width: '100%',
border: 'none'
}
return <iFrame
src={this.props.url}
style={documentDrawerStyles}
ref="documentDrawer"
scrolling="no"
>
</iFrame>;
},
});
If you are controlling the content of the iframe you may use cross-origin communication. Or in other words the site in your iframe knows when its content is fully loaded and broadcast that information to the parent page.
You can use Jquery for that:
$(function(){
$('#MainPopupIframe').on('load', function(){
$(this).show();
console.log('load the iframe')
});
$('#click').on('click', function(){
$('#MainPopupIframe').attr('src', 'http://heera.it');
});
});
as see in the example:
How can I detect whether an iframe is loaded?
I have a JS Fiddle with the below code.
I have a dynamic component setup, which does work. If I do var Component = Switch or set Component to some other React component, it works. But I want to be able to have it switch when I click on it.
So I set up an onClick event. Yet its not firing. I get no log statements or any change. Any ideas why?
var Other = React.createClass({
render: function () {
return <h2>Test this craziness</h2>;
}
});
var Switch = React.createClass({
render: function () {
return <h2>A different one to switch with</h2>;
}
});
var Hello = React.createClass({
getInitialState: function() {
return {on: true};
},
handleClick: function() {
console.log('handleclick');
this.setState({on: ! this.state.on});
},
render: function() {
console.log(this.state);
var Component = this.state.on ? this.props.component.name : Switch;
return <Component onClick={this.handleClick} />;
}
});
ReactDOM.render(
<Hello component={{name: Other}} />,
document.getElementById('container')
);
Easy! When onClick is put directly on a Component, like you have done, it is NOT set as the onClick function. It is instead placed in this.props.onClick. You still need to add the onClick to your actual DOM Elements. See the attached JSFiddle!
You need to add the onClick attribute to an actual HTML element, i.e.
var Other = React.createClass({
render: function () {
return <h2 onClick={this.props.onClick}>Test this craziness</h2>;
}
});
I'm trying to learn the basics of facebook's react.js library and I've been lately thinking about a few stuff I can do with it just to get used to the way it works . I'm trying to make a div that contains 2 buttons one is OPEN and the other is CLOSE, when you click the OPEN the react will render a div containing anything (Like a msg saying "You clicked"), this is fine up to now but I cannot figure out how to make it disappear once clicked on the CLOSE button, does anyone know how to do that ? Thanks ^^
There are at least four ways, that depends on the real problem you need to solve:
1) Add #your-div-id.hidden { display:none } styles and add/remove hidden class on click (maybe not React way)
2) Change view state (i.e. opened flag). That's a React way and maybe the simplest choice
onOpen() {
this.setState({ opened: true });
}
onClose() {
this.setState({ opened: false });
}
render() {
var div = (this.state.opened) ? (<div>Your Div Content</div>) : undefined;
return (
//some your view content besides div
{div}
);
}
3) If you use Flux. Move state to Store and subscribe to changes. That maybe useful if you gonna show your div at many parts of your app (i.e. implement error popups which may be shown at any part of an application).
So, first of all let's keep warnings at the store:
var CHANGE_EVENT = 'change';
const warnings = [];
var WarningsStore = assign({}, EventEmitter.prototype, {
getWarnings: () => return warnings,
emitChange: () => this.emit(CHANGE_EVENT),
addChangeListener: callback => this.on(CHANGE_EVENT, callback),
removeChangeListener: callback => this.removeListener(CHANGE_EVENT, callback)
});
WarningsStore.dispatchToken = AppDispatcher.register(action => {
switch(action.type) {
case ActionTypes.ADD_WARNING:
warnings.push(action.warning);
WarningsStore.emitChange();
break;
case ActionTypes.DISMISS_WARNING:
_.remove(warnings, {type: action.warningType}); //that's lodash or underscore method
WarningsStore.emitChange();
break;
}
});
After we have a warnings store, you may subscribe to it from YourView and show popup on each AppDispatcher.dispatch({type: ADD_WARNING, warningType: 'Error'});
var YourView = React.createClass({
componentDidMount: function() {
WarningsStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
WarningsStore.removeChangeListener(this._onChange);
},
_onChange() {
this.setState({ warnings: WarningsStore.getWarnings() });
},
render() {
//if we have warnigns - show popup
const warnings = this.state.warnings,
onCloseCallback = () => AppDispatcher.dispatch({type: DISSMISS_WARNING, warningType: warnings[0].warningType});
popup = (warnings.length) ? <YourPopupComponent warning={warnings[0]} onClose={onCloseCallback}> : undefined;
return (
//here the main content of your view
{popup}
);
}
})
4) If you simplified your example, but actually instead of div you need to show/hide another page - you should use react-router
Here's how I would do this:
var foo = React.CreateClass({
getInitialState: function() {
return {
showDiv: false, //Or true if you need it displayed immediately on open
}
},
showIt: function() {
this.setState({showDiv: true});
},
hideIt: function() {
this.setState({showDiv: false});
},
render: function() {
return (<div style={{display: this.state.showDiv ? 'block' : 'none'}}>Your content here</div>);
}
});
What this will do is on state change, the style block of the div will be re-evaluated. If the showDiv state variable is true, it'll display as a block element. Otherwise it'll display none. You could, in theory, do this with CSS as well.
Here's a jsFiddle showing this being done with both CSS classes AND the style attribute on the div element.
I'm trying to unmount a React.js node with this._rootNodeID
handleClick: function() {
React.unmountComponentAtNode(this._rootNodeID)
}
But it returns false.
The handleClick is fired when I click on an element, and should unmount the root-node. Documentation on unmountComponentAtNode here
I've tried this as well:
React.unmountComponentAtNode($('*[data-reactid="'+this._rootNodeID+'"]')[0])
That selector works with jQuery.hide(), but not with unmounting it, while the documentation states it should be a DOMElement, like you would use for React.renderComponent
After a few more tests it turns out it works on some elements/selectors.
It somehow works with the selector: document.getElementById('maindiv'), where maindiv is an element not generated with React.js, and just plain html. Then it returns true.
But as soon as I try and select a different ElementById that is generated with React.js it returns false. And it wont work with document.body either, though they all essentially return the same thing if I console.log them (getElementsByClassName('bla')[0] also doesn't work)
There should be a simple way to select the node via this, without having to resort to jQuery or other selectors, I know it's in there somewhere..
Unmount components from the same DOM element that you mount them in. So if you did something like:
ReactDOM.render(<SampleComponent />, document.getElementById('container'));
Then you would unmount it with:
ReactDOM.unmountComponentAtNode(document.getElementById('container'));
Here is a simple JSFiddle where we mount the component and then unmount it after 3 seconds.
This worked for me. You may want to take extra precautions if findDOMNode returns null.
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
The example I use:
unmount: function() {
var node = this.getDOMNode();
React.unmountComponentAtNode(node);
$(node).remove();
},
handleClick: function() {
this.unmount();
}
You don't need to unmount the component the simple solution it's change the state and render a empty div
const AlertMessages = React.createClass({
getInitialState() {
return {
alertVisible: true
};
},
handleAlertDismiss() {
this.setState({alertVisible: false});
},
render() {
if (this.state.alertVisible) {
return (
<Alert bsStyle="danger" onDismiss={this.handleAlertDismiss}>
<h4>Oh snap! You got an error!</h4>
</Alert>
);
}
return <div></div>
}
});
As mentioned in the GitHub issue you filed, if you want access to a component's DOM node, you can use this.getDOMNode(). However a component can not unmount itself. See Michael's answer for the correct way to do it.
First , i am new to reactjs ,too . Of course we can control the Component all by switch the state , but as I try and test , i get that , the React.unmountComponentAtNode(parentNode) can only unmount the component which is rendered by React.render(<SubComponent>,parentNode). So <SubComponent> to be removed must be appened by React.render() method , so I write the code
<script type="text/jsx">
var SubComponent = React.createClass({
render:function(){
return (
<div><h1>SubComponent to be unmouned</h1></div>
);
},
componentWillMount:function(){
console.log("componentWillMount");
},
componentDidMount:function(){
console.log("componentDidMount");
},
componentWillUnmount:function(){
console.log("componentWillUnmount");
}
});
var App = React.createClass({
unmountSubComponent:function(){
var node = React.findDOMNode(this.subCom);
var container = node.parentNode;
React.unmountComponentAtNode(container);
container.parentNode.removeChild(container)
},
componentDidMount:function(){
var el = React.findDOMNode(this)
var container = el.querySelector('.container');
this.subCom = React.render(<SubComponent/> , container);
},
render:function(){
return (
<div className="app">
<div className="container"></div>
<button onClick={this.unmountSubComponent}>Unmount</button>
</div>
)
}
});
React.render(<App/> , document.body);
</script>
Run the sample code in jsFiddle , and have a try .
Note: in the sample code React.findDOMNode is replaced by getDOMNode as the reactjs version problem .