How to update state? - javascript

I am trying to update state with API.
I tried to update API state with using setouttime,
in render(), there is if statement which if timepassed is true, this.get_bus will be called to update state.
However I got warning cannot update during an existing state transition
Please let me know how to update...
this is my code.
export default class App extends React.Component {
constructor(){
super()
this.state = {
station1: [],
station2: [] ,
timePassed:false,
}
}
get_BUS(code,station){
return fetch('https://transportapi.com/v3/uk/bus/stop/'+code+'/live.json?
app_id=6ab4b5a0&app_key=7693c12908e7d566351e3610b4acfa9f
&group=route&nextbuses=yes')
.then((response) => response.json())
.then((responseJson) => {
for(var x in responseJson.departures){
this.setState({
state : this.state[station].push(responseJson.departures[x][0]["line"],":
",responseJson.departures[x][0]["aimed_departure_time"]," ")
});
}
})
.catch((error) => {
console.error(error);
});
}
componentWillMount(){
this.get_BUS('01000053207','station1')
this.get_BUS('01000053207','station2')
this.setTimePassed(false)
}
setTimePassed(abc) {
this.setState({timePassed: abc});
}
render() {
.....
let that = this;
setTimeout(function(){
that.setState({timePassed: true})}, 10000);
if(this.state.timePassed){
console.log("HI update")
this.get_BUS('01000053207','station1')
this.get_BUS('01000053207','station2')
this.setTimePassed(false)
}
return (
......
)

Here's why you would get that error: When you update state, you use
this.setState({ ... });
However, this.setState({ ... }) method will call render() method when the state has been updated. Therefore, you're repeatedly calling render() as you update states in it before the return block.
If I get it right, you want to call get_bus() every 10 seconds. Look at the following example first, in this example, I have state:
this.state = {
timer: null,
counter: 0
};
Where timer is a timer, it will call the function we want to execute every time some seconds have passed, and we use counter to count how many seconds have passed, look at the code below:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {
timer: null,
counter: 0
};
this.tick = this.tick.bind(this);
};
// Set up our timer here
componentDidMount() {
let timer = setInterval(this.tick, 1000);
this.setState({ timer });
}
// Add 1 to counter for every second passed
tick() {
this.setState({ counter: this.state.counter + 1 });
}
render() {
return (
<View style={{ ... }}>
<Text style={{ ... }}>
{this.state.counter}
</Text>
</View>
);
}
}
export default App;
As you can see, in componentDidMount(), we use setInterval() method to set up our timer. setInterval() methods takes 2 arguments, the first argument is the function you wish to execute, the second argument is the interval in milliseconds, this is what the app looks like:
In your case, if you wish to call get_bus() every 10 seconds, simple change the two arguments to this.get_bus(...) and 10000

Related

React : How to execute a method every 10s?

I woudlike to run my method "getNumber" every 10s for example How can I do this ?
class App extends Component {
constructor(){
super();
this.state={
web3: '',
}
this.getNumber = this.getNumbert.bind(this);
}
getNumber(){
let number = 0;
number = number + 1;
}
Some advice:
use native JS function setInterval() to run a given function every x milliseconds
you will need to store the number in state if you want to trigger a re-render every time it changes.
Working example:
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
web3: "",
number: 0
};
this.getNumber = this.getNumber.bind(this);
}
componentDidMount() {
setInterval(this.getNumber, 10000);
}
getNumber() {
this.setState({ number: this.state.number + 1 });
}
render() {
return (
<>
<div>Number: {this.state.number}</div>
<span>👆This will update every 10 seconds</span>
</>
);
}
}
export default App;
Live Demo
Inside componentDidMount you can start a setInterval - which takes 2 params, a method to invoke, and a time (in ms) for it to wait before running again.
https://reactjs.org/docs/react-component.html#componentdidmount
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
componentDidMount() {
setInterval(() => {
let number = 0;
number = number + 1;
}, 10000)
}
Although this would currently set number back to 0 every 10 seconds. You'd want to use a number that is saved as state.

React Set State From CountDown Child

I have a <CountDown/> component that I'd like to hold it's own logic so it can be reusable else where in the app.
I'm struggling to reason how could I setState to show:true on a sibling component i.e <List/> once the count has reached 0.
Currently, this is the hierarchy of the components:
export default class App extends Component {
state = { show: false };
render() {
return (
<div className="App">
<Countdown />
<List {...this.state} />
</div>
);
}
}
I'd like to show the contents of a <List/>
const fruits = ["banana", "apple", "peach"];
export const List = ({ show }) => fruits.map(fruit => <li className={show ? "show" : "hide"}>{fruit}</li>);
Once the currentCount = 0
import React, { Component } from "react";
export default class Countdown extends Component {
state = { currentCount: 3 };
// decrement timer method
timer() {
this.setState({
currentCount: this.state.currentCount - 1
});
//clear interval
if (this.state.currentCount < 1) {
clearInterval(this.intervalId);
}
}
// decrement every second i.e 1000
componentDidMount() {
this.intervalId = setInterval(this.timer.bind(this), 1000);
}
// Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
const { currentCount } = this.state;
return <h1>{currentCount}</h1>;
}
}
My struggle is that if I were to lift the state of currentCount to the main <App/> I'd lose the ability to control the <CountDown/> with its own state and lifecycle methods. Besides I'd like the CountDown to have its own set of logic so that It can be reusable and removable wherever I need in the app.
Question: How could I set the state of show (passed down as a prop) to true, once the countdown reaches 0?
Here's a code sandbox
Define a method in App that will set the state show to true:
onFinish = () => {
this.setState({ show: true });
};
Now send it as a props to CountDown:
<Countdown onFinish={this.onFinish} />
Now call it once your local state reached zero:
if (this.state.currentCount < 1) {
clearInterval(this.intervalId);
this.props.onFinish();
}
Here is your Sandbox's fork
I also moved that last part of code on setState's callback because setState works in an asynchronous way.
You can create showList() in <App> and pass it to <CountDown /> I have changed following part of code
timer() {
this.setState({
currentCount: this.state.currentCount - 1
});
//clear interval
if (this.state.currentCount < 1) {
clearInterval(this.intervalId);
//This line is edited
this.props.showList();
}
}
App Component
export default class App extends Component {
state = { show: false };
showList = () =>{
this.setState({show:true});
}
render() {
return (
<div className="App">
<Countdown showList={this.showList}/>
<List {...this.state} />
</div>
);
}
}
Codesandbox

Have been using React, how to setTimeout or create a Data Carousel like thing which shows new data from a graphql api every couple of mins?

querying data from a graphql api, storing it in an array, now have to display it using setTimeout or interval ( imagine a data carousel which changes automatically after a specific time) how do i achieve it?
It's important to make sure to clear your interval when your component un-mounts. Otherwise, you'll end up with a nasty set of error messages mentioning how you are setting state on an "unmounted" component.
import React, { Component } from 'react'
class Carousel extends Component {
constructor(props) {
super(props);
this.state = {
intervalId = 0,
updatedProps: {}
}
}
componentDidMount() {
let self = this;
let id = setInterval(function() {
// GRAPHQL => data
this.setState({updatedProps: data, intervalId: id})
})
}
componentWillUnMount() {
clearInterval(this.state.intervalId)
}
render() {
return (
<div>
<Carousel
props={this.state.updatedProps}
/>
</div>
)
}
}
Here it is:
this.setState is the key to your question
setInterval(() => {
call.to.api(data => {
this.setState({graphql_data});
// this will update your ui, as soon as state is set
})
},miliseconds)

React - method run right times via state but run double times when Parent Component changing state

I am trying to build a page with some data initialized at first time mounted, and update when websocket server give a response msg when certain button click event is triggered, also I need to ban the button aka. disabled, and tell the user in how many seconds the button is clickable again.
My first thought is, single component, update via states, give a state to the counter, then use setTimeout to count down 1 every 1000ms, turned out that the counter "banCount" worked well, until I add the websocket.send(), then it counted down 2 every time.
I thought that would be because when the websocket server responsed, the state is change, so the whole component is updated, the counter is messed up.
So, I had an idea, separating it into a child component, with its own state, but do nothing when in the life cycle of componentWillReceiveProps, and it will not receive props, so it will just work with it is own state. But the result is with or without separating the counter into a child component, they worked the same.
parent component:
import React from 'react';
import ReactDOM from 'react-dom';
import TestChild from './testChild/testChild';
class TestParent extends React.Component {
constructor(props) {
super(props);
this.state = {
wsData: null,
};
}
componentWillMount() {
this.wsClient = new WebSocket("ws://localhost:9000/server", 'echo-protocol');
this.wsClient.onmessage = msg => {
if (msg) {
this.setState({
wsData: msg.data
});
}
};
}
render() {
const data = () => {
if (this.state.wsData) {
return this.state.wsData;
} else {
return "waiting data";
}
};
return (
<div>
<div>{data()}</div>
<TestChild wsClient={this.wsClient}/>
</div>
);
}
}
ReactDOM.render(
<TestParent />,
document.getElementById('reactWrapper')
);
and the Child Component:
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.count = null;
this.state = {
banCount: this.count
};
this.wsClient = this.props.wsClient;
this.countupdate = 0;
}
banCount() {
this.setState({
banCount: this.count
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
this.banCount();
}
componentDidUpdate() {
if (this.count > 0) {
setTimeout(() => {
this.count -= 1;
this.banCount();
}, 1000);
} else if (this.count === 0) {
this.count = null;
this.banCount();
}
}
render() {
return <button onClick={() => this.callNext(3)}>click me {this.state.banCount}</button>;
}
}
export default TestChild;
Please ignore 'whether the server and websocket connection' works part, they are fine.
I don't know why, I even had not updated Child component, I am really new to React, I really do not know how to debug this, I read this code for hours, but it is just too complicated for me.
Why it counted down 2 every time? and for sure I am wrong, what is the right way.
Please help me with only React and vanilla Javascript, I had not use Redux or Flux and even did not know what they are, thank you.
This is NOT tested code, but should help you to build what you want, I didn't tested your component but I suspect that your setTimeout() is called several times.
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.state = {
count: null,
};
}
startCountDown() {
var newCount = this.state.count -1;
if(newCount === 0){
clearTimeout(this.timer);
}
this.setState({
count: newCount,
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.setState({
count: n,
});
this.timer = setTimeout(() => {
startCountDown();
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return <button disabled={this.state.count>0} onClick={() =>
this.callNext(3)}>click me {this.state.count}</button>;
}
}
export default TestChild;
Finally I worked it out.
It is because React will re-render all the child component with or without setting children's new states. The only way to stop it from re-render is to use ShouldComponentUpdate, so:
shouldComponentUpdate() {
return this.state.banCount !== null;
}
will work, as when the child component receiving props after websocket.send(), this.count is still null, but right after the websocket.send(), this.count is set to 3, so the child component will update since.
Also another workround:
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
}
componentWillReceiveProps(nextProps) {
this.data = nextProps.datas;
this.setState({
banCount: this.count
});
}
in this workround, without shouldComponentUpdate() the child component will always re-render when its parent receive websocket data, so in the click handler function, stop calling bancount(), so it would not update itself, but set the state when receive nextProps, that will trigger the re-render.
To sum all above:
child component will always re-render with or without setting state via new props unless shouldComponentUpdate return false, I alreay called bancount() in the click handler function, trigger child component to update the state itself, but after the parent component receiving websocket data, it triggered state updating again, that is why it run double times.

Update React component every second

I have been playing around with React and have the following time component that just renders Date.now() to the screen:
import React, { Component } from 'react';
class TimeComponent extends Component {
constructor(props){
super(props);
this.state = { time: Date.now() };
}
render(){
return(
<div> { this.state.time } </div>
);
}
componentDidMount() {
console.log("TimeComponent Mounted...")
}
}
export default TimeComponent;
What would be the best way to get this component to update every second to re-draw the time from a React perspective?
You need to use setInterval to trigger the change, but you also need to clear the timer when the component unmounts to prevent it leaving errors and leaking memory:
componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
#Waisky suggested:
You need to use setInterval to trigger the change, but you also need to clear the timer when the component unmounts to prevent it leaving errors and leaking memory:
If you'd like to do the same thing, using Hooks:
const [time, setTime] = useState(Date.now());
useEffect(() => {
const interval = setInterval(() => setTime(Date.now()), 1000);
return () => {
clearInterval(interval);
};
}, []);
Regarding the comments:
You don't need to pass anything inside []. If you pass time in the brackets, it means run the effect every time the value of time changes, i.e., it invokes a new setInterval every time, time changes, which is not what we're looking for. We want to only invoke setInterval once when the component gets mounted and then setInterval calls setTime(Date.now()) every 1000 seconds. Finally, we invoke clearInterval when the component is unmounted.
Note that the component gets updated, based on how you've used time in it, every time the value of time changes. That has nothing to do with putting time in [] of useEffect.
The following code is a modified example from React.js website.
Original code is available here: https://reactjs.org/#a-simple-component
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
seconds: parseInt(props.startTimeInSeconds, 10) || 0
};
}
tick() {
this.setState(state => ({
seconds: state.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
formatTime(secs) {
let hours = Math.floor(secs / 3600);
let minutes = Math.floor(secs / 60) % 60;
let seconds = secs % 60;
return [hours, minutes, seconds]
.map(v => ('' + v).padStart(2, '0'))
.filter((v,i) => v !== '00' || i > 0)
.join(':');
}
render() {
return (
<div>
Timer: {this.formatTime(this.state.seconds)}
</div>
);
}
}
ReactDOM.render(
<Timer startTimeInSeconds="300" />,
document.getElementById('timer-example')
);
In the component's componentDidMount lifecycle method, you can set an interval to call a function which updates the state.
componentDidMount() {
setInterval(() => this.setState({ time: Date.now()}), 1000)
}
class ShowDateTime extends React.Component {
constructor() {
super();
this.state = {
curTime : null
}
}
componentDidMount() {
setInterval( () => {
this.setState({
curTime : new Date().toLocaleString()
})
},1000)
}
render() {
return(
<div>
<h2>{this.state.curTime}</h2>
</div>
);
}
}
i myself like setTimeout more that setInterval but didn't find a solution in class based component .you could use sth like this in class based components:
class based component and setInterval:
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 (
this.state.date.toLocaleTimeString()
);
}
}
ReactDOM.render(
<Clock / > ,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />
function based component and setInterval:
https://codesandbox.io/s/sweet-diffie-wsu1t?file=/src/index.js
function based component and setTimeout:
https://codesandbox.io/s/youthful-lovelace-xw46p
So you were on the right track. Inside your componentDidMount() you could have finished the job by implementing setInterval() to trigger the change, but remember the way to update a components state is via setState(), so inside your componentDidMount() you could have done this:
componentDidMount() {
setInterval(() => {
this.setState({time: Date.now()})
}, 1000)
}
Also, you use Date.now() which works, with the componentDidMount() implementation I offered above, but you will get a long set of nasty numbers updating that is not human readable, but it is technically the time updating every second in milliseconds since January 1, 1970, but we want to make this time readable to how we humans read time, so in addition to learning and implementing setInterval you want to learn about new Date() and toLocaleTimeString() and you would implement it like so:
class TimeComponent extends Component {
state = { time: new Date().toLocaleTimeString() };
}
componentDidMount() {
setInterval(() => {
this.setState({ time: new Date().toLocaleTimeString() })
}, 1000)
}
Notice I also removed the constructor() function, you do not necessarily need it, my refactor is 100% equivalent to initializing site with the constructor() function.
Owing to changes in React V16 where componentWillReceiveProps() has been deprecated, this is the methodology that I use for updating a component.
Notice that the below example is in Typescript and uses the static getDerivedStateFromProps method to get the initial state and updated state whenever the Props are updated.
class SomeClass extends React.Component<Props, State> {
static getDerivedStateFromProps(nextProps: Readonly<Props>): Partial<State> | null {
return {
time: nextProps.time
};
}
timerInterval: any;
componentDidMount() {
this.timerInterval = setInterval(this.tick.bind(this), 1000);
}
tick() {
this.setState({ time: this.props.time });
}
componentWillUnmount() {
clearInterval(this.timerInterval);
}
render() {
return <div>{this.state.time}</div>;
}
}
This can be implemented even with the setTimeout instead of setInterval. As the useState re-renders the component, it will call the setTimeout again and again.
Here is my sample component which update the timer in every second. Also, Please let me know if I am making any mistake here.
import React, { useEffect, useState } from 'react'
export default function Footer() {
const [seconds, setSeconds] = useState((new Date()).getSeconds());
function GetTime() {
setSeconds((new Date()).getSeconds());
console.count(seconds);
}
setTimeout(() => {
console.log("Footer Rendered");
GetTime();
}, 1000);
return (
<footer>
<h2>Test Footer</h2>
<p>Copyright © {seconds}</p>
</footer>
)
}

Categories