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
Related
If I return jsx of a Component from some method and call that method in another component's render, then is a new object created on every render?
class MyComponent extends React.Component {
render = () =>(<h3>My Component</h3>)
}
function getEl(){ return (<MyComponent />) }
class Comp extends React.Component{
componentDidMount = () =>{
let ct = 100;
const update = () => {
if(ct-- < 0) return;
this.setState({}, update);
}
update();
}
render(){
return (<div> {getEl()} </div>)
}
}
If Comp renders 100 times, is a new instance of MyComponent created 100 times? And what if the props passed to MyComponent changes. Then?
Looking for some good in-depth explanation of why this happens
This stuff can be really confusing at first. Or even after you've done your first few bits of work. :-)
The JSX <MyComponent /> is shorthand for React.createElement(MyComponent). What that does is create an object (a "React element") giving React the information it needs to create or update an instance of the component. It doesn't always create a new component instance; if an instance already exists for where that object is used to render something, the previous instance is updated. The object from the JSX itself isn't the component (and in fact, you can reuse them).
For completeness: Even when the component is re-rendered, that doesn't necessarily mean all of the DOM nodes for it are recreated; they may be updated or, if it's an unnecessary render, left completely alone.
Let's expand that code to include a prop on MyComponent and throw some instrumenting calls at it (fancy talk for console.log and a MutationObserver in this case):
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(`MyComponent constructor, counter = ${this.props.counter}`);
}
render() {
const { counter } = this.props;
console.log(`MyComponent render, counter = ${counter}`);
return <h3>My Component: {counter}</h3>;
}
}
function getEl(counter) {
console.log(`getEl, counter = ${counter}`);
return <MyComponent counter={counter} />;
}
class Comp extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
componentDidMount() {
let counter = 0;
const handle = setInterval(() => {
++counter;
this.setState({counter});
if (counter === 5) {
clearInterval(handle);
}
}, 250);
}
render() {
const { counter } = this.state;
console.log(`Comp render, counter = ${counter}`);
return <div>{getEl(counter)}</div>;
}
}
const root = document.getElementById("root");
const ob = new MutationObserver(records => {
for (const record of records) {
console.log(`DOM modification type: ${record.type} on ${record.target.nodeName}:`);
if (record.type === "characterData") {
console.log(`* Value: Changed from ${record.oldValue} to ${record.target.nodeValue}`);
} else if (record.type === "attributes") {
// We aren't listening for these
} else {
for (const node of record.removedNodes) {
console.log(`* Removed: ${node.nodeName} with ${node.innerHTML || node.nodeValue || "(no value)"}`);
}
for (const node of record.addedNodes) {
console.log(`* Added: ${node.nodeName} with ${node.innerHTML || node.nodeValue || "(no value)"}`);
}
}
}
});
ob.observe(root, {
childList: true,
subtree: true,
characterData: true,
characterDataOldValue: true
});
ReactDOM.render(<Comp/>, root);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
As you can see, the MyComponent constructor was only called once to create a single instance of MyComponent, initially with counter = 0. After that, React updated that existing component instance five times with the counter values 1 through 5 (I used 5 instead of 100).
You can also see the DOM modifications thanks to the MutationObserver: The root component gets a new H3 with a couple of text nodes with the initial text, and then just the text node with the counter is updated as the counter changes.
I am trying to change the image displayed every 1 second the first image appears then switches to the alt display and does not continue switching the pictures
export default class Slideshow extends Component {
constructor(props) {
super(props);
this.getImageId = this.getImageId.bind(this);
this.switchImage = this.switchImage.bind(this);
this.init = this.init.bind(this);
this.state = {
currentImage: 0,
image: 0
};
}
getImageId() {
if(this.currentImage < 3) {
this.setState({
currentImage: this.state.currentImage +1
})
} else {
this.setState({
currentImage: 0
})
}
return this.currentImage;
}
switchImage() {
this.setState({
image: this.getImageId()
});
}
init() {
setInterval(this.switchImage, 1000)
}
render() {
const imagePath = [guy, girl, wash, swifer];
this.init();
return (
<div className="slideshow-container">
<img src={imagePath[this.state.image]} alt="cleaning images"/>
</div>
);
}
}
Pictures will switch every 1 seconds to the next picture in the array and go back to original after going through whole array
Try something like this instead: https://codesandbox.io/s/naughty-sara-q3m16
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.switchImage = this.switchImage.bind(this);
this.state = {
currentImage: 0,
images: [
"https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
"https://img.purch.com/w/660/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzEwNC84MzAvb3JpZ2luYWwvc2h1dHRlcnN0b2NrXzExMTA1NzIxNTkuanBn",
"https://d17fnq9dkz9hgj.cloudfront.net/uploads/2012/11/152964589-welcome-home-new-cat-632x475.jpg",
"https://i.ytimg.com/vi/jpsGLsaZKS0/maxresdefault.jpg"
]
};
}
switchImage() {
if (this.state.currentImage < this.state.images.length - 1) {
this.setState({
currentImage: this.state.currentImage + 1
});
} else {
this.setState({
currentImage: 0
});
}
return this.currentImage;
}
componentDidMount() {
setInterval(this.switchImage, 1000);
}
render() {
return (
<div className="slideshow-container">
<img
src={this.state.images[this.state.currentImage]}
alt="cleaning images"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
We can simplify your code by doing a couple of things:
Put the images-array in the state, so that we can iterate over
the image-paths and keep track of the current images index.
So now we can consolidate switchImage and getImageId into a
single function that serves the same purpose. We just check the
currentImage (index) against the length of the array.
React also has a life-cycle method called componentDidMount()
which executes logic right after a component is rendered the
first-time. I used this to replace the init() function. There is an issue with calling init() in render(). Every time a component re-renders, it executes the logic in render(), which means you would be creating a new setInterval() on every subsequent re-render. componentDidMount() only triggers a single time, making it perfect for defining intervals.
The main issue with your code is that you called init function with in render function, whenever state get update render executes as well, so init function call again and again each time render function execute
the solution is to set intervals in componentDidMount function
componentDidMount run only one time after component mount in the DOM, for help related to react life cycle function do visit the official documentation
https://reactjs.org/docs/react-component.html
also have a look this post image
https://reactjs.org/docs/react-component.html
Here I try to clear ingredient when users click purchasing cancel, and set state purchasing false then ingredient clean, but state seems to be true. and doesn't clear ingredient from orders with realtime, even modal window cl what should i need to do ?
///Root Component
state = {
purchasing: false
}
purchaseCancleHandler = () => {
this.setState({purchasing: false
});
}
<OrderSummary
purchaseContinue = {
this.purchaseContinuewHandler
}
purchaseCancle = {
this.purchaseCancleHandler
}
/>
//Child component
import React, {Component} from 'react'
import Button from '../../UI/Button/Button'
import Aux from '../../../hoc/Aux'
class OrderSummary extends Component {
componentWillUpdate() {
//console.log('[OrderSummer] willupdate')
}
render ()
{
const ingredientSummary =Object.keys(this.props.ingredients)
.map(igkey => {
return <li key={igkey}><span style={{textTransform:'capitalize'}}>{igkey}</span>: {this.props.ingredients[igkey]}</li>
});
return(
<Aux>
<h3>Your Order</h3>
<p> A delicious Burger with the following ingredient</p>
<ul>
{ingredientSummary}
</ul>
<p>Total Price :<strong>{this.props.totalprice.toFixed(2)}</strong></p>
<p>Continure To Checkout ?</p>
<Button btnType="Danger" clicked={this.props.purchaseCancle}>CANCEL</Button>
<Button btnType="Success" clicked={this.props.purchaseContinue}>CONTINUE</Button>
</Aux>
);
}
}
export default OrderSummary;
state = {
purchasing: false
}
purchaseCancleHandler = () => {
this.setState({purchasing: false
});
}
<Button btnType="Danger" clicked={this.purchaseCancleHandler}>CANCEL</Button>
You don't need .props if it's declare in the same local state. Also, your function wasn't invoked spelled correctly (it was missing "Handler")
You're wanting to call a method defined on the component itself, not a prop passed to the component, so you need to use this.purchaseCancleHandler.
state = {
purchasing: false
}
purchaseCancleHandler = () => {
this.setState({
purchasing: false
});
}
<Button btnType="Danger" clicked={this.purchaseCancleHandler}>CANCEL</Button>
constructor(props) {
super(props)
this.state = {
purchasing: null
}
}
purchaseCancleHandler = () => {
this.setState({
purchasing: false // need set this value difference with initial value
});
}
// Child component
clicked = () => {
this.props.purchaseCancle()
}
<Button btnType="Danger" clicked={this.clicked}>CANCEL</Button>
To make sure component will render after setState, need set value for purchasing difference with initial value.
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
My node.js server sends with socket.io new data each 10s. In my web application I update this.state each time that my server sends data and force to update with forceUpdate()
However, my react component doesn't refresh, I don't know why. I followed the doc but I missed something...
Parent :
class DataAnalytics extends React.Component {
constructor(props) {
super(props);
socket = this.props.socket;
this.state = {data: []};
socket.on('dataCharts', (res) => {
console.log("new data charts : "+res);
var data = JSON.parse(res);
this.setState({data: data});
this.forceUpdate();
});
}
componentWillUnmount() {
socket.off('dataCharts');
}
render() {
return (
<div id="dataAnalytics">
<Stats data={this.state.data}></Stats>
</div>
);
}
}
export default DataAnalytics;
Child :
class Stats extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="stats" style={{textAlign:'center'}}>
<h4>Number: </h4>
</div>
);
}
componentDidUpdate() {
var data = this.props.data;
if(!jQuery.isEmptyObject(data)) {
$( ".stats" ).html("<h4>Number : data['nb']['counterIn']</h4>");
}
}
}
export default Stats;
Anyone know how to refresh automatically my React component.
The React component doesn't update because it doesn't realize that it's state changes. You can force an update on a React component by creating it each time with a different key attribute.
render() {
return (
<div id="dataAnalytics">
<Stats key={this.uniqueId()} data={this.state.data}></Stats>
</div>
);
}
// Example of a function that generates a unique ID each time
uniqueId: function () {
return new Date().getTime();
}
I usually do it like -
function MyComponent() {
const [_, refresh] = useState()
useEffect(() => {
// Code that's supposed to run on refresh
}, [refresh])
return
<>
{/* Rest of the code */}
<button onclick={() => refresh(true)}>Refresh</button>
</>
}
The idea is to define a state and use it as a dependency of useEffects (or useMemos and useCallbacks).
If there are multiple effect hooks, add refresh to all of them as a dependency.