I'm exploring React 16. One of the new features of this version is Async Rendering (aka Fiber). It is said that componentWillMount is unsafe because it can be called multiple times and sometimes it will cause unwanted side effects. I'd read this document https://github.com/acdlite/react-fiber-architecture and watched this video https://www.youtube.com/watch?v=aV1271hd9ew&feature=youtu.be but I can't find working example of such behaviour of componentWillMount.
In this questions:
React componentWillUpdate getting called twice
Why might componentWillMount be called multiple times with React Fibre?
it is said that componentWillMount may be called several times when high priority event occurs during rendering process.
So I tried to make it myself using create-react-app and React.js 16.11.0. My idea is to build big react tree and add css animation on root component.
JSX:
class RecursiveComponent extends React.Component {
componentWillMountCalledTimes = 0;
componentWillMount() {
this.componentWillMountCalledTimes++;
if (this.componentWillMountCalledTimes > 1) {
console.log(`Mounting ${this.props.depth} call ${this.componentWillMountCalledTimes}`);
}
}
render() {
if (this.props.depth > 0) {
if (this.props.depth % 2) {
return (
<div>
<RecursiveComponent depth={this.props.depth - 1} />
</div>
);
} else {
return (
<span>
<RecursiveComponent depth={this.props.depth - 1} />
</span>
);
}
} else {
return <div>Hello world</div>;
}
}
}
class App extends React.Component {
state = {
depth: 1000,
}
componentDidMount() {
setInterval(() => {
this.setState({ depth: 1001 + this.state.depth % 10 });
}, 1000);
}
render() {
return (
<div className={'App'}>
<RecursiveComponent depth={this.state.depth} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
styles:
.App {
width: 400px;
height: 400px;
background-color: red;
animation-duration: 3s;
animation-name: slidein;
animation-iteration-count: infinite;
}
#keyframes slidein {
from {
margin-left: 300px;
}
to {
margin-left: 0;
}
}
I expect smooth animation and rare console output but there is no messages in console. What is my mistake? How can I demonstrate multiple calls of componentWillMount function?
Upd:
As #DoXicK mentioned Async Rendering is disabled by default in current version of React.JS. I'd followed by this guide https://reactjs.org/docs/concurrent-mode-adoption.html and write such example
import React from 'react';
import './App.css';
class Caption extends React.Component {
componentWillMountCalledTimes = 0;
componentWillMount() {
wait(100);
this.componentWillMountCalledTimes++;
if (this.componentWillMountCalledTimes > 1)
console.log(`Mounting ${this.props.depth} call ${this.componentWillMountCalledTimes}`);
}
render() {
return <div>{this.props.children}</div>;
}
}
class App extends React.Component {
state = { counter: 0 }
componentDidMount() {
setInterval(() => {
this.setState({ counter: this.state.counter + 1 });
}, 100);
}
render() {
if (this.state.counter % 10 === 0) {
return <div>Empty</div>;
} else {
return (
<div className={'App'}>
<Caption>{'Hello 1'}</Caption>
<Caption>{'Hello 2'}</Caption>
<Caption>{'Hello 3'}</Caption>
</div>
);
}
}
}
function wait(time) {
const start = Date.now();
while (Date.now() - start < time) {
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
I'd used CSS from the example above. I'd expected to get at least one message about multiple call of componentWillMount on one element (because each element renders more than , but I had no luck.
I think I'm missing something but I don't understand what.
I've found example in this article: https://0e39bf7b.github.io/posts/react-journey-componentwillmount-in-concurrent-mode/
let lastCounter = 0; // Global variable for multiple mounts detection
class Counter extends React.Component {
componentWillMount() {
if (lastCounter === this.props.value) {
console.log(`mount counter with value = ${this.props.value} multiple times`);
}
lastCounter = this.props.value;
// Syncronously wait for 100ms to emulate long work
const start = Date.now();
while (Date.now() - start < 100);
}
render() {
return <div>{this.props.value}</div>;
}
}
class App extends React.Component {
state = { counter: 0, showGreetings: true };
componentDidMount() {
this.interval = setInterval(() => {
this.setState({ counter: this.state.counter + 1 });
}, 500);
}
componentWillUnmount() {
clearInterval(this.interval);
}
toggleGreetings = () => {
this.setState({ showGreetings: !this.state.showGreetings });
};
render() {
// Use key attribute to force React.JS to remount Counter component
return (
<>
<button onClick={this.toggleGreetings}>Toggle greetings</button>
{this.state.showGreetings && <h1>Hello world!</h1>}
<Counter value={this.state.counter} key={`counter-${this.state.counter}`} />
<div>{this.state.counter2}</div>
</>
);
}
}
// Instead of regular initialization
// ReactDOM.render(<App />, document.getElementById('root'));
// use Concurrent Rendering for this component
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
My main misunderstanding was the idea that componentWillMount will be called multiple times for the same instance and as it's described in the article new instance of component is created and componentWillMount is called for it. Now the idea is clear.
Related
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
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
I want to use leader-line in my React web project. It is an external javascript library, but I don't know how to integrate it into the project with the JSX syntax.
For example, its documentation tells us the general implementation:
Html
<div id="start">start</div>
<div id="end">end</div>
Javascript
// Add new leader line from `start` to `end` (HTML/SVG elements, basically).
new LeaderLine(
document.getElementById('start'),
document.getElementById('end')
);
How should I write in JSX file?
I try to write below, but failed.
import React, { Component } from 'react';
import LeaderLine from 'leader-line'
class Page extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));
}
render() {
return (
<div className="Page">
<div id="start"></div>
<div id="end"></div>
</div>
)
}
}
export default Page;
This is the npm package page of leader-line.
Depending on what you are trying to achieve with leader-line, you may find that you can achieve it just as well with react-xarrows.
https://www.npmjs.com/package/react-xarrows
React-xarrows can be integrated into a React app much more easily (even using DOM identifiers rather than React Refs, if you prefer).
See this example code (taken directly from the link above), showing usage.
import React, { useRef } from "react";
import Xarrow from "react-xarrows";
const boxStyle = {
border: "grey solid 2px",
borderRadius: "10px",
padding: "5px",
};
function SimpleExample() {
const box1Ref = useRef(null);
return (
<div
style={{ display: "flex", justifyContent: "space-evenly", width: "100%" }}
>
<div ref={box1Ref} style={boxStyle}>
hey
</div>
<p id="elem2" style={boxStyle}>
hey2
</p>
<Xarrow
start={box1Ref} //can be react ref
end="elem2" //or an id
/>
</div>
);
}
I've made a small prototype to illustrate how it could be achieved.
class Line extends React.Component {
componentDidMount () {
this.waitWhenRefIsReady();
// scroll and resize listeners could be assigned here
}
componentWillUnmount () {
if(this.timer) {
clearInterval(this.timer);
}
}
shouldComponentUpdate () {
setTimeout(() => {
// skip current even loop and wait
// the end of parent's render call
if(this.line) {
this.line.position();
}
}, 0);
// you should disable react render at all
return false;
}
waitWhenRefIsReady () {
// refs are generated via mutations - wait for them
this.timer = setInterval(() => {
if(this.props.start.current) {
clearInterval(this.timer);
this.drawLine();
}
}, 5);
}
drawLine () {
const {start, end} = this.props;
this.line = new LeaderLine(start.current, end.current);
}
render () {
return null;
}
}
class App extends React.Component {
constructor (props) {
super(props);
this.state = {
left: 0,
};
this.myRef1 = React.createRef();
this.myRef2 = React.createRef();
}
componentDidMount() {
this.animateLine();
}
animateLine() {
setInterval(() => {
const limit = 200;
const {left} = this.state;
const x = ((left % limit) + limit) % limit;
this.setState({left: x + 10});
}, 1000);
}
render () {
const {left} = this.state;
const {myRef1, myRef2} = this;
return <div className="container">
<Line
start={this.myRef1}
end={this.myRef2} />
<div
id="start"
ref={this.myRef1}
style={{
left: `${left}px`
}}></div>
<div
id="end"
ref={this.myRef2}></div>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Leader Line + React JSX simple prototype
import LeaderLine from 'leader-line';
Add in leader-line.min.js (at end)
if (module && module.exports) { module.exports = LeaderLine }
Refer to this thread for how to integrate Leaderline into your react project :
https://github.com/anseki/leader-line/issues/8#issuecomment-370147614
in summary,
you cant just do
import LeaderLine from 'leader-line';
to import LeaderLine, because its not an ES2015 module yet!
similar to how #shilpa pointed out,
you can tweak the webpack config to include -
rules: [
{
test: require('path').resolve(__dirname, 'node_modules/leader-line/'),
use: [{
loader: 'skeleton-loader',
options: {procedure: content => `${content}export default LeaderLine`}
}]
}
and then inside componentDidMount, you could do
new LeaderLine(document.getElementById('start'),
document.getElementById('end'));
I'm trying to render a dynamic progress bar in react/typescript. I was able to do this in react but converting this into typescript gives me;
[ts] Property 'setInterval' does not exist on type 'Jockey'.
any
with a warning underline on setInterval call inside componentDidMount.
With react only I used this.interval = setInterval(this.timer, this.state.interval); inside componentDidMountand it worked but with typescript's strict typing I'm not sure how to do this.
Jockey.tsx
import * as React from 'react';
interface Props {
color: string;
avatar: string;
}
interface State {
interval: number;
progress: number;
}
export class Jockey extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
interval: Math.floor(Math.random() * 500),
progress: 0,
};
}
componentDidMount () {
this.setInterval(this.timer, this.state.interval);
}
timer () {
this.setState({ progress: this.state.progress + 1 });
console.log('anyhting');
(this.state.progress >= 99) ? this.setState({ progress: 100 }) : this.setState({ progress: 0 }) ;
}
render() {
return (
<div>
<div className="App-lane">
{/* <img src={ this.props.avatar } alt=""/> */}
<progress value={this.state.progress} color={this.props.color} max="100" />
</div>
</div>
);
}
}
this.setInterval() doesn't exist.
I assume you're trying to create an interval and save a reference so that you can clear it later.
See below for a rough example of how one might achieve this.
Set Interval
componentDidMount () {
this.interval = setInterval(this.timer, this.state.interval)
}
Clear Interval
componentWillUnmount() {
clearInterval(this.interval)
}
Further Reading
See the setInterval() references in React Docs: State and Lifecycle for more info.
All the best ✌️
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>
)
}