Is it possible to use ClearInterval inside static getDerivedStateFromPRops method in React? - reactjs

I have set an Interval and did some Operation inside ComponentDidMount method. I want to clear the Interval after I get props.status is completed.
class A extends React.PureComponent {
constructor(props) {
super(props);
this.intervalTimer = '';
}
componentDidMount() {
this.intervalTimer = setTimeout(() => {
// do something;
}, 3000);
}
static getDerivedStateFromProps(np, ps) {
if(np.status === 'completed') {
clearInterval(A.intervalTimer);
}
}
}

You could create static variable and use that in getDerivedStateFromProps lifecycle method.
Here intervalTimer is the variable which is accessible inside static method.
Here is the working code:-
import React from "react";
import ReactDOM from "react-dom";
let intervalTimer = "";
class App extends React.PureComponent {
state = {
stop: false
};
componentDidMount() {
intervalTimer = setInterval(() => {
console.log("interval going on");
}, 3000);
}
handleClick = () => {
this.setState({ stop: true });
};
static getDerivedStateFromProps(props, state) {
if (state.stop) {
clearInterval(intervalTimer);
}
return null;
}
render() {
return (
<div>
<button onClick={this.handleClick}>Stop interval</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Or you could also use static variable instead of global variable
static intervalTimer = ''
componentDidMount() {
App.intervalTimer = setInterval(() => {
console.log("interval going on");
}, 3000);
}
static getDerivedStateFromProps(props, state) {
if (state.stop) {
clearInterval(App.intervalTimer);
}
return null;
}
Both approaches will work just fine.
Hope that helps!!!

You shouldn't allow getDerivedStateFromProps to change a none static variable or call a none static method. Its best to just return the new state from there. My suggestion will be for you to use a different life cycle method like
componentDidUpdate(prevProps) {
// compare status props and clear timer
if (this.props.status === 'completed') {
clearInterval(this.intervalTimer);
}
}
to be on a safe side also do the clearing inside
componentWillUnmount(){
clearInterval(this.intervalTimer)
}

Related

Rendering a new component inside componentDidMount - React

I will have to render a new component after all the expected components are loaded. I will need a timeout based on which the the new component has to be rendered. So this new component has to show up after 5 minutes after the page has loaded.
I need to render a component called new_component that extends React.component
public componentDidMount(): void {
if (visited) {
setTimeout(() => {
console.log('Reached the timeout')
//Render the new conponent here. (Not sure how to call the render function of another component here)
}, timeout);
}
Can someone help me call the render function of new_component inside componentDidMount please. i tried new_component.render(). But that does not seem to work.
You can use state to track this.
componentDidMount() {
setTimeout(() => {
this.setState({ showNewComponent: true })
})
}
and in render:
render() {
if (this.state.showNewComponent) {
return <NewComponent />
}
return null
}
You can go with this code, wait and then render new one:
cosnt FIVE_MIN = 5 * 60 * 1000
class Example {
this.state = { isShowComponent: false }
timer
componentDidMount() {
this.timer = setTimeout(() => {
this.setState({ isShowComponent: true })
}, FIVE_MIN)
}
componentWilllUnmount() {
clearTimeout(this.timer)
}
render() {
if (this.state.isShowComponent) return <NewComponent />
return <Component />
}
}
:)
you can render your component by your state.
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
isTimeout: false,
};
}
componentDidUpdate(prevProps, prevState) {
this.checkTimeout = setTimeout(() => {
this.setState(() => ({isTimeout: true}))
}, 500);
}
componentWillUnmount() {
// clean it up when the component is unmounted.
clearTimeout(this.checkTimeout);
}
render () {
if (isTimeout) {
return (k<h1>time is running out</h1>)
}
return (<h1>hello world.</h1>)
}
}

Switching between two components in React

rotateRender() {
if(false) {
return(
<TimerPage></TimerPage>
);
} else {
return(
<RepoPage></RepoPage>
);
}
}
I have two components called TimerPage and RepoPage.
I created a simple conditional render function as above, but cannot come up with a condition to make it render iteratively after a certain amount of time.
For example, I first want to render RepoPage and switch to TimerPage after 5 minutes and then stay in TimerPage for 15 mins before I switch again to the RepoPage.
Any way to do this?
Might not be that elegant, but this works
Actually I was thinking that this block might be more elegant than the first one
const FIRST_PAGE = '5_SECONDS';
const SECOND_PAGE = '15_SECONDS';
const FirstComponent = () => (
<div>5 SECONDS</div>
);
const SecondComponent = () => (
<div>15 SECONDS</div>
);
class App extends Component {
state = {
currentPage: FIRST_PAGE
};
componentDidUpdate() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
if (isFirst) {
this._showSecondPageDelayed();
} else {
this._showFirstPageDelayed();
}
}
componentDidMount() {
this._showSecondPageDelayed();
};
_showSecondPageDelayed = () => setTimeout(() => {this.setState({currentPage: SECOND_PAGE})}, 5000);
_showFirstPageDelayed = () => setTimeout(() => {this.setState({currentPage: FIRST_PAGE})}, 15000);
render() {
const {currentPage} = this.state;
const isFirst = currentPage === FIRST_PAGE;
const ComponentToRender = isFirst ? FirstComponent : SecondComponent;
return <ComponentToRender/>;
}
}
As stated in the comment section, you can create a higher order component that will cycle through your components based on the state of that component. Use setTimeout to handle the timer logic for the component.
state = {
timer: true
}
componentDidMount = () => {
setInterval(
() => {
this.setState({ timer: !this.state.timer })
}, 30000)
}
render(){
const {timer} = this.state
if(timer){
return <TimerPage />
} else {
return <RepoPage />
}
}
Edit
Changed setTimeout to setInterval so that it will loop every 5 minutes instead of just calling setState once
You could use the new context API to achieve this. The benefit is now I have a configurable, reusable provider to play with throughout my application. Here is a quick demo:
https://codesandbox.io/s/k2vvy54r8o
import React, { Component, createContext } from "react";
import { render } from "react-dom";
const ThemeContext = createContext({ alternativeTheme: false });
class ThemeWrapper extends Component {
state = {
alternativeTheme: false
};
themeInterval = null;
componentDidMount() {
this.themeInterval = setInterval(
() =>
this.setState(({ alternativeTheme }) => ({
alternativeTheme: !alternativeTheme
})),
this.props.intervalLength
);
}
componentWillUnmount() {
if (this.themeInterval) {
clearInterval(this.themeInterval);
}
}
render() {
return (
<ThemeContext.Provider value={this.state}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
const App = () => (
<ThemeWrapper intervalLength={2000}>
<ThemeContext.Consumer>
{({ alternativeTheme }) =>
alternativeTheme ? <p>Alternative Theme</p> : <p>Common Theme</p>
}
</ThemeContext.Consumer>
</ThemeWrapper>
);
render(<App />, document.getElementById("root"));
Whatever you do make sure on componentWillUnmount to clear your interval or timeout to avoid a memory leak.

React js Delay rendering causes error

I have form where I should render one of elements in a while. I use setTimeout for this aim in componentDidMount but I get warning setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
class Form extends React.Component {
constructor(props, context) {
this.state = {resend: false};
}
componentDidMount() {
const max = 3;
if (this.props.count < max) {
setTimeout(() => {
this.setState({resend: true});
}, 1000);
}
}
render() {
return (
<form>
...
{this.state.resend ? <Component/> : null}
</form>
);
}
}
Your component is probably unmounting at some point, then the timeout finishes, and it attempts to call setState after your component is no longer being rendered.
You need to cancel any timeouts you have running in componentWillUnmount. Store a reference to them and delete them.
class Form extends React.Component {
constructor(props, context) {
this.state = {resend: false};
this.timeouts = [];
}
componentWillUnmount(props, context) {
this.timeouts.forEach(t => window.clearTimeout(t));
}
componentDidMount() {
const max = 3;
if (this.props.count < max) {
const timeoutId = setTimeout(() => {
this.setState({resend: true});
}, 1000);
this.timeouts.push(timeoutId);
}
}
render() {
return (
<form>
...
{this.state.resend ? <Component/> : null}
</form>
);
}
}
This might be because the component doesn't exist when the setTimeout calls. Use a mounted flag to see if the component still exists.
constructor(props, context) {
this.state = {resend: false};
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
componentDidMount() {
const max = 3;
if (this.props.count < max) {
setTimeout(() => {
if (!this.mounted) return;
this.setState({resend: true});
}, 1000);
}
}

MobX observable with dynamic data

I have the following class
export default class BaseStore {
#observable model ;
#action updateStore(propertyName, newValue) {
this.model[propertyName] = newValue;
}
}
In child classes I add layers to the observable model, such as :
model.payment.type = 'credit card'
My react component doesn't render automatically when this happen, it does however, if I has a top level data such as:
model.Type = 'CreditCard'
I am new to MobX, and read that I need to make use of map() but I am unable to find a decent example that explain how to use it.
If you know all the keys that the model will have, you can just initialize them with a null value, and the observer components will re-render.
Example (JSBin)
class BaseStore {
#observable model = {
type: null
};
#action updateStore(propertyName, newValue) {
this.model[propertyName] = newValue;
}
}
const baseStore = new BaseStore();
#observer
class App extends Component {
componentDidMount() {
setTimeout(() => baseStore.model.type = 'CreditCard', 2000);
}
render() {
return <div> { baseStore.model.type } </div>;
}
}
If you don't know all the keys of model beforehand, you can use a map like you said:
Example (JSBin)
class BaseStore {
model = observable.map({});
#action updateStore(propertyName, newValue) {
this.model.set(propertyName, newValue);
}
}
const baseStore = new BaseStore();
#observer
class App extends Component {
componentDidMount() {
this.interval = setInterval(() => {
const key = Math.random().toString(36).substring(7);
const val = Math.random().toString(36).substring(7);
baseStore.updateStore(key, val);
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <div>
{ baseStore.model.entries().map(e => <div> {`${e[0]} ${e[1]}` } </div>) }
</div>;
}
}

React JS - updating state within an eventListener

I'm trying to update the state of my component inside of an eventListener. I'm getting the following console error:
'Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Header component'
This is my component code:
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
fixed: false
}
}
handleScroll(event) {
this.setState({
fixed: true
});
}
componentDidMount() {
window.addEventListener("scroll",() => {
this.handleScroll();
});
}
componentWillUnmount() {
window.removeEventListener("scroll",() => {
this.handleScroll();
});
}
render() {
var {
dispatch,
className = "",
headerTitle = "Default Header Title",
onReturn,
onContinue
} = this.props;
var renderLeftItem = () => {
if (typeof onReturn === 'function') {
return (
<MenuBarItem icon="navigation-back" onClick={onReturn}/>
)
}
};
var renderRightItem = () => {
if (typeof onContinue === 'function') {
return (
<MenuBarItem icon="navigation-check" onClick= {onContinue}/>
)
}
};
return (
<div className={"header " + className + this.state.fixed}>
{renderLeftItem()}
<div className="header-title">{headerTitle}</div>
{renderRightItem()}
</div>
)
}
}
Header.propTypes = {
};
let mapStateToProps = (state, ownProps) => {
return {};
};
export default connect(mapStateToProps)(Header);
IMHO this is because you do ont unregister the function as you expect it, and a scroll event is sent after an instance of this component has been unmounted
try this:
componentDidMount() {
this._handleScroll = this.handleScroll.bind(this)
window.addEventListener("scroll", this._handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this._handleScroll);
}

Resources