use Waypoint with React to execute a function after scroll - javascript

I have a counter component that counts up to a number on the page. I need it to run ones the user scrolls to that point on the page. How do I do that with Waypoint? Or is there another solution?
import React, { Component } from 'react';
import Waypoint from 'react-waypoint';
var Counter2017 = React.createClass({
getInitialState: function() {
return {visionElapsed: 0};
},
tick: function() {
if (this.state.visionElapsed < 125) {
this.setState({visionElapsed: this.state.visionElapsed + 1});
}
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 15);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<span>{this.state.visionElapsed}</span>
);
}
});
export default Counter2017;

You can try with componentDidUpdate(prevProps, prevState). In that method you will get notified about change in state. Invoke there what you need when user reaches the point.
More in docs:
https://facebook.github.io/react/docs/react-component.html#componentdidupdate

You insert your waypoint where you want it to be (it is a DOM element).
So you would have something that looks like this:
<div>
<AnyComponents />
<Waypoint
onEnter={() => this.interval = yourInterval}
/>
</div>
Of course you can check if there is already an interval so you don't override it. You can also set an onLeave props on the Waypoint component. I suggest you to read about it here: https://github.com/brigade/react-waypoint

The best you can do is using the react-waypoint lib

Related

Update Component State (And UI) With Random Word From Array At Set Interval

I'd like to update a word in a React component every second with a random value from an array. The state seems to update at the interval just fine in DevTools, but there is a weird bug in the browser that I can't seem to pinpoint:
the text flashes, almost as if it's "scrolling" through the old strings to the new one. Any advice on how to smooth this out would be much appreciated!
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React, { Component } from 'react';
class App extends Component {
state = {
name: ""
}
getWord = () => {
let randomWords = ["Friend", "Enemy", "Santa"];
const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)];
this.setState({
name: randomWord
});
}
render() {
setInterval(this.getWord, 1000);
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
}
export default App;
You should make the getWord to do what the name implies: To get a word, return a string.. Not to set the state. To set a state in a function called getWord is misleading and would be considered a side-effect. You'll understand what I mean when you get more experienced :)
Then you should use componentDidMount to set up the timer. Something like this:
componentDidMount() {
var _this = this;
setInterval(function() {
var newName = _this.getWord();
_this .setState({name: newName });
}, 1000);
}
This is what react is about... You set new props or the state (either using state, or Redux or whatever) and let React to re-render.
The problem is the setInterval in render(). Every second you get another ine, so by the tenth flash, it's rerendering 10 times, hence the flicker. Render methods should have no side effects, as this is adding another interval every time it renders. Try it in componentDidMount:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props)
this.state = {
name: ""
}
}
componentDidMount = () => {
setInterval(this.getWord, 1000);
}
getWord = () => {
let randomWords = ["Friend", "Enemy", "Santa"];
const randomWord = randomWords[Math.floor(Math.random() * randomWords.length)];
console.log('new word', randomWord)
this.setState({
name: randomWord
});
}
render() {
return (
<div>
<h1>{this.state.name}</h1>
</div>
);
}
}
export default App

setTimeout in React Native

I'm trying to load a splash screen for an iOS app built in React Native. I'm trying to accomplish this through class states and then a setTimeout function as follows:
class CowtanApp extends Component {
constructor(props){
super(props);
this.state = {
timePassed: false
};
}
render() {
setTimeout(function(){this.setState({timePassed: true})}, 1000);
if (!this.state.timePassed){
return <LoadingPage/>;
}else{
return (
<NavigatorIOS
style = {styles.container}
initialRoute = {{
component: LoginPage,
title: 'Sign In',
}}/>
);
}
}
}
The loading page works for a second, and then I guess when setTimeout tries to change the state to true, my program crashes: 'undefined is not an object (evaluating this.setState)'. I've been going at this for a couple of hours, any ideas on how to fix it?
Classic javascript mistake.
setTimeout(function(){this.setState({timePassed: true})}, 1000)
When setTimeout runs this.setState, this is no longer CowtanApp, but window. If you define the function with the => notation, es6 will auto-bind this.
setTimeout(() => {this.setState({timePassed: true})}, 1000)
Alternatively, you could use a let that = this; at the top of your render, then switch your references to use the local variable.
render() {
let that = this;
setTimeout(function(){that.setState({timePassed: true})}, 1000);
If not working, use bind.
setTimeout(
function() {
this.setState({timePassed: true});
}
.bind(this),
1000
);
Write a new function for settimeout. Pls try this.
class CowtanApp extends Component {
constructor(props){
super(props);
this.state = {
timePassed: false
};
}
componentDidMount() {
this.setTimeout( () => {
this.setTimePassed();
},1000);
}
setTimePassed() {
this.setState({timePassed: true});
}
render() {
if (!this.state.timePassed){
return <LoadingPage/>;
}else{
return (
<NavigatorIOS
style = {styles.container}
initialRoute = {{
component: LoginPage,
title: 'Sign In',
}}/>
);
}
}
}
const getData = () => {
// some functionality
}
const that = this;
setTimeout(() => {
// write your functions
that.getData()
},6000);
Simple, Settimout function get triggered after 6000 milliseonds
In case anyone wants it, you can also make the timer async and await it:
export const delay = (ms) => new Promise((res) => setTimeout(res, ms));
Usage:
// do something
await delay(500); // wait 0.5 seconds
// do something else
Change this code:
setTimeout(function(){this.setState({timePassed: true})}, 1000);
to the following:
setTimeout(()=>{this.setState({timePassed: true})}, 1000);
On ReactNative .53, the following works for me:
this.timeoutCheck = setTimeout(() => {
this.setTimePassed();
}, 400);
'setTimeout' is the ReactNative library function.
'this.timeoutCheck' is my variable to hold the time out object.
'this.setTimePassed' is my function to invoke at the time out.
You can bind this to your function by adding .bind(this) directly to the end of your function definition. You would rewrite your code block as:
setTimeout(function () {
this.setState({ timePassed: true });
}.bind(this), 1000);
Never call setState inside render method
You should never ever call setState inside the render method. Why? calling setState eventually fires the render method again. That means you are calling setState (mentioned in your render block) in a loop that would never end. The correct way to do that is by using componentDidMount hook in React, like so:
class CowtanApp extends Component {
state = {
timePassed: false
}
componentDidMount () {
setTimeout(() => this.setState({timePassed: true}), 1000)
}
render () {
return this.state.timePassed ? (
<NavigatorIOS
style = {styles.container}
initialRoute = {{
component: LoginPage,
title: 'Sign In',
}}/>
) : <LoadingPage/>
}
}
PS Use ternary operators for cleaner, shorter and readable code.
import React, {Component} from 'react';
import {StyleSheet, View, Text} from 'react-native';
class App extends Component {
componentDidMount() {
setTimeout(() => {
this.props.navigation.replace('LoginScreen');
}, 2000);
}
render() {
return (
<View style={styles.MainView}>
<Text>React Native</Text>
</View>
);
}
}
const styles = StyleSheet.create({
MainView: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
There looks to be an issue when the time of the phone/emulator is different to the one of the server (where react-native packager is running). In my case there was a 1 minute difference between the time of the phone and the computer. After synchronizing them (didn't do anything fancy, the phone was set on manual time, and I just set it to use the network(sim) provided time), everything worked fine. This github issue helped me find the problem.
Same as above, might help some people.
setTimeout(() => {
if (pushToken!=null && deviceId!=null) {
console.log("pushToken & OS ");
this.setState({ pushToken: pushToken});
this.setState({ deviceId: deviceId });
console.log("pushToken & OS "+pushToken+"\n"+deviceId);
}
}, 1000);

React waiting for map function before deciding render to prevent div flash

I am having a bit of trouble preventing a div flash where react will render the error message then receive props and finally render the props.
In my EventsView component I have the following.
view.js
var view;
if (_.size(this.props.events) !== 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
} else {
view = <NoEventsView />
}
So the variable view is rendered, initially there is no this.props.events on page load as I have another part of the file creating it. The moment this.props.events is created the events are rendered. This is done throught a react.Backbone mixin.
I have tried doing something used some of the component lifecycle methods such as componentWillUpdate. The problem is they don't seem to want to work the way I want them to work and the error message is never rendered. Example of what I have tried (among others) below
I thought of doing something like this.
view.js
componentWillUpdate: function(nextprops, nextstate) {
if (_.size(nextprops.events) !== 0) {
var view = nextprops.events.map(function(year) {
return <YearView year={year} />;
});
this.setState({
view: view
});
} else {
this.setState({
view: <NoEventsView />
})
}
},
// render this.state.view
I would then set the getInitialState:view to the a this.props.events map.
*EDIT - A temporary and terrible workaround *
So the problem I was having was I had a top level component accepting a single prop which was a global variable like so
view.js
React.render(<EventsView events={theseEvents} />, document.getElementById('mainContent'));
theseEvents is actually a Backbone Collection global variable initialized in another file.
On document load theseEvents would be unpopulated so my view would flash with the <NoEventView /> and then rerender when theseEvents is then populated. This is handled by the backbone mixin shown below.
Dan's solution pointed me in the right direction. I set a global variable window.hasEvents to true or false depending on what I got back from my ajax request but by that time React was already rendering (the component would start as hasEvents would be true, by the time it's finished hasEvents might be false). Even if I made hasEvents a prop of <EventsView /> react wouldn't rerender when it changed.
So as a temporary workaround I have defaulted hasEvents to true and rendered EventsView. I then waited for the component to load and checked whether it was in sync with hasEvents. shown below. Honestly this workaround is not very pleasant.
view.js
ar EventsView = React.createBackboneClass({
mixins: [
React.BackboneMixin("events", "all")
],
getInitialState: function() {
return {
hasEvents: window.hasEvents
}
},
componentDidMount: function() {
var model = this;
// This waits after component loading to check whether hasEvents is the same
setTimeout(function() {
if(!(model.state.hasEvents == window.hasEvents)) {
model.setState({
hasEvents: window.hasEvents
})
};
}, 1000);
},
render: function() {
var view;
if (this.props.events.length > 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
}
if (!this.state.hasEvents) {
view = <NoEventsView />
}
return {
// other parts of the page were also rendered here
{view}
}
});
essentially componentDidMount waits for a while after component mounting and then double checks a global variable. Definitely not a great workaround especially since it relies on a 1 second theseEvents population and it only works on the initial component mounting.
Your second approach is not “the React way” (although technically it may be working) because you shouldn't put React elements into state. Only data should go there, and in render if need to figure out how that data corresponds to DOM stucture. It's also best to avoid state unless absolutely necessary.
That being said, I'm not quite sure what you're trying to achieve. Assuming you want to prevent "No Events" flash before events come through, the only way your component can do that is by knowing that data is being fetched. If, in addition to events prop, you pass isLoading boolean prop, you can do that:
render: function () {
var isEmpty = this.props.events.length === 0;
if (isEmpty) {
return this.props.isLoading ?
<p>Loading...</p> : // note you can also return null here to render nothing
<NoEventsView />;
}
return this.props.events.map(function (year) {
return <YearView year={year} />;
});
}
Since I do a request right when my app starts to check if the user is Authenticated this is how I've been handling it within my components. The parent components state is linked to my store, and it passes down authenticated as a property.
I set the components hasUpdated state to false initially, since it will receive props from the parent I wait until componentWillRecieveProps is triggered and then set hasUpdated to true since it means our call has returned. Then it renders the elements it needs to.
Not sure if this is the best approach but it has worked for our needs so far.
var SomeComponent= React.createClass({
displayName: 'SomeComponent',
getInitialState: function() {
return {
hasUpdated : false
};
},
getDefaultProps: function() {
return {
authenticated : false
};
},
componentWillReceiveProps: function(nextProps) {
this.setState({ hasUpdated : true });
},
render: function() {
var AuthElement;
if(this.state.hasUpdated){
if(this.props.authenticated){
AuthElement= <div>Authenticated</div>;
}else{
AuthElement= <div>Not Authenticated</div>;
}
}
return (
{AuthElement}
);
}
});

How do I keep document.title updated in React app?

Since React doesn't have any builtin way to manage document.title, I used to set it inside componentDidMount of my route handlers.
However now I need to amend the title based on state fetched asynchronously. I started putting assingments into componentDidUpdate, but every now and then I forget to put document.title assignment into some pages, and previous title sticks around until I finally notice it.
Ideally I'd like a way to express document.title declaratively, without having to assign it. Some kind of “fake” component would probably be most convenient, given that I want to be able to specify the document title at several nesting levels:
On top level (the default title);
On page level (for some of the pages, but not all);
Sometimes, on inner component level (e.g. user typing into a field).
Additional requirements:
Title specified in child should override title specified by parent;
Reliable (guarantees cleanup on route change);
Should not emit any DOM (i.e. no hacks with component returning <noscript>);
I'm using react-router but it's better if this component works with other routers too.
Anything I can use?
I wrote react-document-title just for that.
It provides a declarative way to specify document.title in a single-page app.
If you want to get title on server after rendering components to string, call DocumentTitle.rewind().
Features
Does not emit DOM, not even a <noscript>;
Like a normal React compoment, can use its parent's props and state;
Can be defined in many places throughout the application;
Supports arbitrary levels of nesting, so you can define app-wide and page-specific titles;
Works on client and server.
Example
Assuming you use something like react-router:
var App = React.createClass({
render: function () {
// Use "My Web App" if no child overrides this
return (
<DocumentTitle title='My Web App'>
<this.props.activeRouteHandler />
</DocumentTitle>
);
}
});
var HomePage = React.createClass({
render: function () {
// Use "Home" while this component is mounted
return (
<DocumentTitle title='Home'>
<h1>Home, sweet home.</h1>
</DocumentTitle>
);
}
});
var NewArticlePage = React.createClass({
mixins: [LinkStateMixin],
render: function () {
// Update using value from state while this component is mounted
return (
<DocumentTitle title={this.state.title || 'Untitled'}>
<div>
<h1>New Article</h1>
<input valueLink={this.linkState('title')} />
</div>
</DocumentTitle>
);
}
});
Source
I keep track of mounted instances and only use title given to the top DocumentTitle in the mounted instance stack whenever it updates, gets mounted or unmounted. On server, componentWillMount fires but we won't get didMount or willUnmount, so we introduce DocumentTitle.rewind() that returns a string and destroys state to prepare for next request.
var DocumentTitle = React.createClass({
propTypes: {
title: PropTypes.string
},
statics: {
mountedInstances: [],
rewind: function () {
var activeInstance = DocumentTitle.getActiveInstance();
DocumentTitle.mountedInstances.splice(0);
if (activeInstance) {
return activeInstance.props.title;
}
},
getActiveInstance: function () {
var length = DocumentTitle.mountedInstances.length;
if (length > 0) {
return DocumentTitle.mountedInstances[length - 1];
}
},
updateDocumentTitle: function () {
if (typeof document === 'undefined') {
return;
}
var activeInstance = DocumentTitle.getActiveInstance();
if (activeInstance) {
document.title = activeInstance.props.title;
}
}
},
getDefaultProps: function () {
return {
title: ''
};
},
isActive: function () {
return this === DocumentTitle.getActiveInstance();
},
componentWillMount: function () {
DocumentTitle.mountedInstances.push(this);
DocumentTitle.updateDocumentTitle();
},
componentDidUpdate: function (prevProps) {
if (this.isActive() && prevProps.title !== this.props.title) {
DocumentTitle.updateDocumentTitle();
}
},
componentWillUnmount: function () {
var index = DocumentTitle.mountedInstances.indexOf(this);
DocumentTitle.mountedInstances.splice(index, 1);
DocumentTitle.updateDocumentTitle();
},
render: function () {
if (this.props.children) {
return Children.only(this.props.children);
} else {
return null;
}
}
});
module.exports = DocumentTitle;
Take a look at the NFL's react-helmet.
class Layout extends React.Component {
constructor(props){
super(props);
document.title = this.props.title;
}
render(){
return(
<div>
</div>
);
}
}
and then <Layout title="My Title"/> that easy!
Try react-frozenhead, it's actually more sophisticated than react-document-title - it allows us change title, description and anything else in section.
Meanwhile, 3 years have gone! ;-)
If you want to manipulate other page headers than title (like description, canonical, etc.), react-document-meta NPM dependency could be a good thing to use.

React component classes meta-programming

I'm working with React and using React-Bootstrap components.
I found some issues in the React-Bootstrap library which I "fixed" (or "workarounded") by editing the react-bootstrap.js file. The problem is that if tomorrow a new version of react-bootstrap comes out, then I will have to copy-paste/re-write/whatever all the code I wrote in the react-bootstrap.js file to the new one. I don't want to do this, so I'm wandering if there is a way to modify the component classes (i.e. change the render function) provided by react-bootstrap without touching the react-bootstrap.js file. The problem is that I can't figure out how to do this, or at least I'm not finding easy to understand the inner working of the component classes. Any ideas on how could I accomplish this?
Thanks in advance!
You could use a wrapping component, that overrides methods of the original component after it's mounted:
function wrapComponent (originalComponent, override) {
return React.createClass({
componentDidMount: function () {
for (var property in override) {
if (override.hasOwnProperty(property)) {
this.refs.original[property] = override[property];
}
}
},
render: function () {
return this.transferPropsTo(
<originalComponent ref="original">{ this.props.children }</originalComponent>
);
}
});
}
var ConsoleSample = React.createClass({
// This method can still be used:
prefix: function (text) {
return "prefix: " + text;
},
// This method will be overridden:
output: function (text) {
console.log(this.prefix(text));
},
onClick: function () {
this.output("Hello world");
},
render: function () {
return <button onClick={this.onClick}>{ this.props.children }</button>
}
});
var Application = React.createClass({
render: function () {
var AlertSample = wrapComponent(ConsoleSample, {
output: function (text) {
alert(this.prefix(text));
}
});
return <div>
<ConsoleSample>This should console.log</ConsoleSample>
<AlertSample>This should alert</AlertSample>
</div>
}
});
React.renderComponent(<Application />, document.body.lastChild);
It's a simple hack though. I'd agree that the correct solution is to fork React-Bootstrap.

Categories

Resources