I am calling a React component BarChart with two props, a name and a value. As you can see in the below code, the variable value is set to a new random number every second:
let random1;
function setRandom() {
random1 = Math.floor(Math.random() * 10) + 1;
}
setRandom();
setInterval(setRandom, 1000);
return (
<div className="Content">
<BarChart name1={"A"} value1={random1}/>
</div>
)
}
Inside the React component I call it by using this.props.value1. When I do a console.log(this.props.value1) each second inside the React component, I get an error that the variable is undefined after the first print is made. So, it prints to the console 1 time and then it just prints an error for all of the rest attempts.
This is how I print the variable inside the component:
setRandom() {
console.log(this.props.value1)
}
componentDidMount() {
this.setRandom();
setInterval(this.setRandom, 1000);
}
What I really want to do is that whenever a new random value is generated outside the component, the component should see that the variable has changed and refresh the component and use the new prop.
Could you please advise me?
The standard way to do this is to make random1 a piece of state information, and then use this.setState to update it.
The first link above has an example of a ticking clock, which is virtually identical to your example of a random number every second. Here's that example, which you can readily adapt to your task:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
constructor(props) {
super(props);
//innitialize the random number in the state
this.state = {random: Math.floor(Math.random() * 10) + 1};
}
//generate the random number and keep in on the state
setRandom() {
this.setState({random: Math.floor(Math.random() * 10) + 1})
}
//clear the timer when component unmount
componentWillUnmount() {
clearInterval(this.timer);
}
componentDidMount() {
//start the timer when component mount
this.timer = setInterval(()=>this.setRandom(), 1000);
}
//pass the random value from state as props to the component BarChart
return (
<div className="Content">
<BarChart name1={"A"} value1={this.state.random}/>
</div>
)
}
Related
I'm trying to build a component with auto-updating value based on cookies:
let cookies = 0;
(function count() {
cookies = document.cookie.split("?");
setTimeout(count, 10);
return cookies;
})();
class CartButton extends React.Component {
state = {quantity: cookies.length}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>
);
}
}
'count' function works as expected, component is rendered with the latest value returned. Unfortunately, it does not auto-update when 'cookies' are changed. It returns this error:
Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.
I have tried various variations here but still can't figure it out :/
componentDidMount will get execute only once when your component loads first time. This is the correct place to write any logic which we need to execute after page load.
Try this,
class CartButton extends React.Component {
//It is good to have a constructor for the component which has state
constructor(props){
super(props);
this.state = {quantity: cookies.length}
this.updateQuantity;
}
componentDidMount(){
this.updateQuantity = setInterval(()=> {
cookies = document.cookie.split("?");
this.setState({quantity: cookies.length})
},10)
}
//Don't forget to clear any setInterval like below
componentWillUnmount(){
clearInterval(this.updateQuantity);
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}
Here your CartButton is not updating even though count is working fine because CartButton is not listening to your cookies variable. React component updates only when there is either props or state change.
You can something like this..
class CartButton extends React.Component {
state = {quantity: cookies.length}
componentDidMount(){
setInterval(function count() {
cookies = document.cookie.split("?");
this.setState({quantity: cookies})
}.bind(this), 10)
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}
I have a React component with a prop 'total' that changes every time the component is updated:
function MyView(props) {
const total = props.data.loading ? 0 : props.data.total;
return (
<p> total </p>
);
}
The first time the component mounts the total is say 10. Every time the component is updated because of a prop change the total goes up.
Is there a way I can display the original total (in this example 10)?
I have tried setting it in this.total inside componentDidMount, but props.data.total is not yet available when componentDidMount is called. Same with the constructor. The total only becomes available when props.data.loading is false.
In order to get access to lifecycle features, you must move from function, stateless component, to a class component.
in the below example, InitialTotal is initialized in the construstor lifecycle method and it never changes.
currentTotal, is incremented each time the render function is called - when the component is re-rendered (because of props change or state changes)
it should look something like that:
class MyView extends React.Component {
constructor(props) {
super(props)
this.initialTotal = 10;
this.currentTotal = 10;
}
render() {
this.currentTotal+=1;
return (
<p>InitialToal: {this.initialTotal}</p>
<p>Current Total: {this.currentTotal}</p>
);
}
}
You could create a stateful component and store the initial total in the component state.
Example
class MyView extends React.Component {
state = {
initialTotal: this.props.total
};
render() {
const { total } = this.props;
const { initialTotal } = this.state;
return (
<div>
<p> Total: {total} </p>
<p> Initial total: {initialTotal} </p>
</div>
);
}
}
class App extends React.Component {
state = {
total: 10
};
componentDidMount() {
this.interval = setInterval(() => {
this.setState(({ total }) => {
return { total: total + 1 };
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <MyView total={this.state.total} />;
}
}
If I understand your requirements correctly...
function MyView(props) {
// if you only need to set the value once on load just use useState.
// the const total will be the value you pass in to useState.
const [total, setTotal] = useState(props.data.loading ? 0 : props.data.total)
// if its possible that the value is not available on initial load and
// you want to set it only once, when it becomes available, you can use
// useEffect with useState
useEffect(() => {
// some condition to know if data is ready to set
if (!props.data.loading) {
setTotal(props.data.total)
}
}, [props.data.total, setTotal, props.data.loading]
// this array allows you to limit useEffect to only be called when
// one of these values change. ( when total changes in this case,
// as the const function setTotal will not change
// ( but react will fuss if its used and not in the list ).
return (
<p> {total} </p>
);
}
I have the same need. With a functional component, I need to store the inital snapshot of states, let user play with different state values and see their results immediately, eventually, they can just cancel and go back to the initial states. Apply the same structure to your problem, this is how it looks:
import React from 'react';
import { useEffect, useState } from "react";
const TestView = (props: { data: any }) => {
// setting default will help type issues if TS is used
const [initialTotal, setInitialTotal] = useState(props.data.total)
useEffect(() => {
// some condition to know if data is ready to set
setInitialTotal(props.data.total);
// Critical: use empty array to ensure this useEffect is called only once.
}, [])
return (
<div>
<p> { initialTotal } </p>
<p> { props.data.total } </p>
</div>
);
}
export default TestView
You can use getDerivedStateFromProps life cycle method.
static getDerivedStateFromProps(props, state){
if(props.data.total && (props.data.total==10)){
return {
total : props.total // show total only when its 10
}
}else{
return null; // does not update state
}
}
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
I was reading the tutorial on the official react website. In the example about life cycle methods, under the componentDidMount method, a timerID is set to the setInterval function.
My question is even though the timerID was initialized, it was never called throughout the application, how does the application work without explicitly calling timerID anywhere in the application. Here is the code below.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
this.timerID is a numeric, non-zero value which identifies the timer created by the call to setInterval(); this value can be passed to clearInterval to clear the timer.
So when calling the setInterval in componentDidMount like
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
you want to execute the tick() function every 1 sec after the component has mounted. Now when you navigate to another component and your current component has unmounted, if you do not clear the interval call to tick() function, it will continue to be executed.
Hence in the componentWillUnmount function you timer is cleared which is identified by the numeric value returned by setInterval which is stored in this.timerID
componentWillUnmount() {
clearInterval(this.timerID);
}
so the complete code as provided in the React docs is
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
It's Simple. As soon as it React executes componentDidMount() life cycle method, the timer starts running.
this.timerID = setInterval(
() => this.tick(),
1000
);
The above timer will run until the component gets unmounted (according to your code). It's not a surprise that your code works that way.
In this react document it's written that
We will tear down the timer in the componentWillUnmount() lifecycle
method
So, this.timerID will be used in componentWillUnmount() lifecycle method to stop timer.
I have a loading spinner in a stateless functional component that I'm using while I check for props.
I'd like to use setTimeout to have the loading spinner display for 5 seconds and then change the content if props are still not available, but this code doesn't seem to work:
function LoadingIndicator() {
let content = <span>Loading Spinner Here</span>;
setTimeout(() => {
console.log('6 second delay');
content = <span>Page could not be loaded.</span>;
}, 6000);
return (
<div>
{content}
</div>
);
}
I believe this doesn't work because nothing tells react to re-render this component, but I'd prefer not to upgrade to a container if possible.
Move the timer to the parent. Have the timer change a state value and in its render, pass that state value as a prop to your LoadingIndicator.
Make your component stateful, so you can change its state easily.
class SpinnerComponent extends React.Component {
constructor() {
super();
this.state = { tooLong: false };
}
componentDidMount() {
var thiz = this;
setTimeout(function () {
thiz.setState({ tooLong: true });
}, 1000);
}
render() {
let content = 'Spinner...';
if (this.state.tooLong) {
content = 'It takes too much time...';
}
return (
<div>{content}</div>
);
}
};
ReactDOM.render(
<SpinnerComponent />,
document.getElementById("app")
);
<div id="app"></div>
<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>