Trying to run function only if state changes - reactjs

I want to run the set totals function only if the hour's state has changed. It is running every time the component mounts instead of only if the value changes. The this.state is apart of a context file that is extremely large so I only pasted the function being used
context.js (Class Component)
set Total
if (this.state.hours > 0) {
this.setState((prevState) => {
if (prevState.hours !== this.state.hours) {
console.log(prevState.hours);
}
return {
total: this.state.total + this.state.hours * ratePerHour * Math.PI,
};
});
console.log(this.state.total, '+', this.state.hours, '*', ratePerHour);
}
This is my component tha
import React, { useState, useEffect, useContext,useRef } from 'react';
import { ProductContext } from '../pages/oniContext';
import { Container,Badge } from 'reactstrap';
import {
Subtitle,
Description,
Titlespan2,
} from '../components/common/title/index';
import { total } from '../components/total';
export const FinalQuote = () => {
const pCR = useContext(ProductContext);
const prevCountRef = useRef();
useEffect(() => {
alert('Run')
console.log(pCR.hours, 'Final Quote Run', pCR.total);
pCR.setTotal();
console.error(pCR.hours);
}, [pCR.hours]);
return (
<section className="testimonial-wrapper gradient-color" id="testimonial">
<Container>
<div className="main-title-wrapper">
<Subtitle Class="site-subtitle gradient-color" Name="Your Quote" />
<Titlespan2
Class="sitemain-subtitle"
Name={`$${Math.round(pCR.total)}`}
/>
<Description
Class="site-dec"
Name="The Shown Price is only an estimate and may increase or decrease based on demand and extent of work"
/>
{pCR.activeAddOns.map((service, index) => (
<Badge color="info" pill>
{service.title}
</Badge>
))}
</div>
</Container>
</section>
);
};

You can achieve this by using componentDidUpdate life cycle function in your component class. As per the docs
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Means, whenever the state of the component will change, the componentDidUpdate code block will be called. So we can place an if condition in the block to compare the new state with the previous state, can calculate total and recommit it to the state. Code 👇
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
hours: 0,
total: 0,
ratePerHour: 10
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.hours !== this.state.hours) {
// Calculate total
this.setState({
total: this.state.total + this.state.hours * this.state.ratePerHour * Math.PI
}
}
}
render() {
return <AnotherComponent />;
}
}
Plus it is important to note that (ref: docs)
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.
In case of any other query, please feel free to reach out in the comments.

It's been a minute since I've worked with newer React features but when I can I use useEffect in my functional components. The second parameter is the the variable you want to watch for changes. If you don't supply a second parameter it'll run similar to componentDidMount and componentDidUpdate. An example of possible use:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [test, setTest] = useState('');
// Specify to watch count because we have more than one state variable
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Here's some of their documentation: https://reactjs.org/docs/hooks-effect.html

In my case, even when I added the second argument to useEffect which is an array of dependencies. It is running every time the component mounts instead of only if the value changes and I guess this is because I initialized my state variable like so
const[myStateVariable, setMyStateVariable] = React.useState('');
so I had to make this condition in my useEffect function
React.useEffect(() => {
if(myStateVariable !==''){
getMyData()
}
}, [myStateVariable]);

Related

Usage of State in functional component of React

How to use State in functional component of React ?
I have a component like below
import React, { useState } from 'react';
function App() {
const [count] = React.useState(0); //Set State here
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
this.setState({ count: applicable.length }) //Update State here
}
return (
<div className="App">
<input type="submit" id="button" value="{ this.state.count }"/> //Use State here
</div>
);
}
export default App;
But State is not working here.
states should be like this
const [count,setCount] = React.useState(0); //setState
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
setCount(applicable.length) //Update State here
}
setCount sets the state count.
Updating the state in a functional component is different from a class component. The useState hook returns an array consisting of the value and a setter.
You then call the setter function to update the value of the state.
const [count, setCount] = React.useState(0);
function count_checkbox() {
...
setCount(applicable.length)
}
this.setState is unsed to update state in class components. In order to update state in functional component you need to update it with appropriate function.
useState returns 2 values first is current state value and second one function to update that state
const [count, setCount] = React.useState(0);
Whenever you want to update count state you need to update with setCount function
function count_checkbox() {
...
setCount(applicable.length); //Update State here
}
You can access count in your useEffect like below:-
useEffect(() => {
alert(count);
}, [count])
A functional component is just a plain javascript function which takes props as an argument and returns a react element.
A stateless component has no state, it means that you can’t reach this.state and this.setState() inside it. It also has no lifecycle so you can’t use componentDidMount and other hooks.
import React, { useState } from 'react';
function App() {
const [count, setCount] = React.useState(0); //Set initial state here
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
setCount(applicable.length) //Update State here
}
return (
<div className="App">
<input type="submit" value={`Apply tax to ${ count } item(s)`} /> //You Can't use this.state.count here
</div>
);
}
export default App;

React rerendering children in functional component despite using memo and not having any prop changed

I have an Icon component that draw an icon and which is blinking because the parent is making it rerender for nothing. I don't understand why this is happening and how to prevent this.
Here is a snack that shows the issue.
We emulate the parent changes with a setInterval.
We emulate the icon rerendering by logging 'rerender' in the console.
Here is the code:
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
// or any pure javascript modules available in npm
let interval = null
const Child = ({name}) => {
//Why would this child still rerender, and how to prevent it?
console.log('rerender')
return <Text>{name}</Text>
}
const ChildContainer = ({name}) => {
const Memo = React.memo(Child, () => true)
return <Memo name={name}/>
}
export default function App() {
const [state, setState] = React.useState(0)
const name = 'constant'
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s+1), 1000)
return () => clearInterval(interval)
}, [])
return (
<View>
<ChildContainer name={name} />
</View>
);
}
If you could explain me why this is happening and what is the proper way to fix it, that would be awesome!
If you move const Memo = React.memo(Child, () => true) outside the ChildContainer your code will work as expected.
While ChildContainer is not a memoized component, it will be re-rendered and create a memoized Child component on every parent re-render.
By moving the memoization outside of the ChildContainer, you safely memoize your component Child once, and no matter how many times ChildContainer will be called, Child will only run one time.
Here is a working demo. I also added a log on the App to track every re-render, and one log to the ChildComponent so you can see that this function is called on every re-render without actually touching Child anymore.
You could also wrap Child with React.memo directly:
import * as React from "react";
import { Text, View, StyleSheet } from "react-native";
// or any pure javascript modules available in npm
let interval = null;
const Child = React.memo(({ name }) => {
//Why would this child still rerender, and how to prevent it?
console.log("memoized component rerender");
return <Text>{name}</Text>;
}, () => true);
const ChildContainer = ({ name }) => {
console.log("ChildContainer component rerender");
return <Child name={name} />;
};
export default function App() {
const [state, setState] = React.useState(0);
const name = "constant";
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s + 1), 1000);
return () => clearInterval(interval);
}, []);
console.log("App rerender");
return (
<View>
<ChildContainer name={name} />
</View>
);
}

Render issue caused by setState executed every 0.1 sec in setInterval

I'm trying to click on a div that has the onClick function associated with it, but the function doesnt getting called due to the fact that I have a setState inside a setInterval called every 0.1 sec. This updates the DOM and doesnt let me call the function onClick.
I tried to use PureComponent and React.memo to avoid re-renders nested Components, but it didn't work; I could not use them properly though.
Inside the father constructor I have, basically, this:
setInterval(()=> {
this.setState({state1: 0})
}, 100)
}
EDIT
I'm proud of showing you the minimum (almost) code to test the issue (note the functional component: if you remove it and replace the < F /> with its content, it will work properly. Also, if you debug with google chrome, you will see what's going on the DOM):
class App extends Component {
constructor() {
super()
this.state = {state1: 0}
setInterval(() => {this.setState({state1: this.state.state1 + 1})}, 100)
}
render() {
const F = () => (
<button onClick={()=> alert("this function will be called... sometimes")}>
test: {this.state.state1}
</button>
)
return <div> <F/> </div>
}
}
EDIT 2
If I write the functional component as a pure function, it will work. here's the example:
class App extends Component {
constructor() {
super()
this.state = { state1: 0}
setInterval(() => {this.setState({state1: this.state.state1 + 1})}, 100)
}
render() {
const F = ({state1}) => (
<button onClick={()=> {alert("called sometimes")}}> test (will be called sometimes): {state1} </button>
)
function f(state1) {
return <button onClick={()=> {alert("called always")}}> test(will be called always): {state1} </button>
}
return <div> <F state1={this.state.state1}/> {f(this.state.state1)}</div>
}
}
setState will, by default rerender components that are impacted by said state.
This was answered here.
I would suggest moving away from setting state that often. That's quite expensive and I'm betting there is a far more efficient way to accomplish whatever it is that you're trying to do without the interval.
If you are using React 16.8^ then you could use Hooks to make multiple state changes. Note that instead of using setInterval i used setTimeout for each cycle
import React, {useState, useEffect} from 'react';
const App = (props) => {
const [numberShown, setNumberShown] = useState(0);
useEffect(() => {
setTimeout(() => setNumberShown(numberShown + 1), 100);
}, [numberShown]);
return (<div>
{numberShown}
</div>)
}
Hope it helps
EDIT : I found a way to do it the Component Way:
class App extends Component {
constructor() {
super()
this.state = {state1: 0}
this.startTimer = this.startTimer.bind(this)
this.startTimer()
}
startTimer = () => {
setInterval(() => {this.setState({state1: this.state.state1 + 1})}, 100)
}
render() {
return <button onClick={()=> alert("this function will be called... sometimes")}>
test: {this.state.state1}
</button>
}
}
By experimenting i noticed that if you render the button like this:
render() {
const F = () => (
<button onClick={()=> alert("this function will be called... sometimes")}>
test: {this.state.state1}
</button>
)
return <div> <F/> </div>
}
Instead of directly returning the button tag, the nested onClick function triggering the alert won't always go off but if you do it like in my example it will always trigger.

setTimeout() outputs a non expected number/timer

I'm trying to call the creator of action inside my Action.js to remove the alert after 3000ms with setTimeout(), and it outputs a number/timer 61 at the end of the message.
How can I remove this 61.
Output:
Password too short (min 6 characs.)61
Code:
const Alert = props => {
return (
<div>
{props.alert && (
<div className={`alert alert-${props.alert.type}`}>
<i className="fas fa-info-circle"> {props.alert.msg}</i>
{setTimeout(() => props.removeAlert(), 3000)}
</div>
)}
</div>
);
};
Thank you.
You should structure your code correctly like this:
const setAlert = props =>
{
if(props.alert){
setTimeout(() => props.removeAlert(), 3000)
return (props.alert.msg);
}
};
Don't put everything under the return statement if it is just action and is not generating any value for you
I strongly recommend you to read the book 'Clean Code' by Robert C. Martin
I hope this can help you:
Try to separate concerns in your component, in this case the render and the set time out:
What version of react are you using?
If you have higher that 16.8
Hooks run after each render, including the first one so: you can try something like this:
import React, { useEffect } from 'react';
useEffect(() => {
// Run your setTimeout here!!!
});
return (
// Render your component
);
Or if you are using a lower version of react you can convert your component in a class component and instead of calling the setTimeout in the render method, use it in the componentDidMount()
class Alert extends React.Component {
constructor(props){
super(props)
}
componentDidMount() {
setTimeout(() => this.props.removeAlert(), 3000)
}
render() {
return (
// Set your conditions &&
// Render your message or component
);
}
}

ReactJS lifecycle method inside a function Component

Instead of writing my components inside a class, I'd like to use the function syntax.
How do I override componentDidMount, componentWillMount inside function components?
Is it even possible?
const grid = (props) => {
console.log(props);
let {skuRules} = props;
const componentDidMount = () => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
};
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
Edit: With the introduction of Hooks it is possible to implement a lifecycle kind of behavior as well as the state in the functional Components. Currently
Hooks are a new feature proposal that lets you use state and other
React features without writing a class. They are released in React as a part of v16.8.0
useEffect hook can be used to replicate lifecycle behavior, and useState can be used to store state in a function component.
Basic syntax:
useEffect(callbackFunction, [dependentProps]) => cleanupFunction
You can implement your use case in hooks like
const grid = (props) => {
console.log(props);
let {skuRules} = props;
useEffect(() => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
}, []); // passing an empty array as second argument triggers the callback in useEffect only after the initial render thus replicating `componentDidMount` lifecycle behaviour
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
useEffect can also return a function that will be run when the component is unmounted. This can be used to unsubscribe to listeners, replicating the behavior of componentWillUnmount:
Eg: componentWillUnmount
useEffect(() => {
window.addEventListener('unhandledRejection', handler);
return () => {
window.removeEventListener('unhandledRejection', handler);
}
}, [])
To make useEffect conditional on specific events, you may provide it with an array of values to check for changes:
Eg: componentDidUpdate
componentDidUpdate(prevProps, prevState) {
const { counter } = this.props;
if (this.props.counter !== prevState.counter) {
// some action here
}
}
Hooks Equivalent
useEffect(() => {
// action here
}, [props.counter]); // checks for changes in the values in this array
If you include this array, make sure to include all values from the component scope that change over time (props, state), or you may end up referencing values from previous renders.
There are some subtleties to using useEffect; check out the API Here.
Before v16.7.0
The property of function components is that they don't have access to Reacts lifecycle functions or the this keyword. You need to extend the React.Component class if you want to use the lifecycle function.
class Grid extends React.Component {
constructor(props) {
super(props)
}
componentDidMount () {
if(!this.props.fetched) {
this.props.fetchRules();
}
console.log('mount it!');
}
render() {
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
}
Function components are useful when you only want to render your Component without the need of extra logic.
You can use react-pure-lifecycle to add lifecycle functions to functional components.
Example:
import React, { Component } from 'react';
import lifecycle from 'react-pure-lifecycle';
const methods = {
componentDidMount(props) {
console.log('I mounted! Here are my props: ', props);
}
};
const Channels = props => (
<h1>Hello</h1>
)
export default lifecycle(methods)(Channels);
You can make your own "lifecycle methods" using hooks for maximum nostalgia.
Utility functions:
import { useEffect, useRef } from "react";
export const useComponentDidMount = handler => {
return useEffect(() => handler(), []);
};
export const useComponentDidUpdate = (handler, deps) => {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
return handler();
}, deps);
};
export const useComponentWillUnmount = handler => {
return useEffect(() => handler, []);
};
Usage:
import {
useComponentDidMount,
useComponentDidUpdate,
useComponentWillUnmount
} from "./utils";
export const MyComponent = ({ myProp }) => {
useComponentDidMount(() => {
console.log("Component did mount!");
});
useComponentDidUpdate(() => {
console.log("Component did update!");
});
useComponentDidUpdate(() => {
console.log("myProp did update!");
}, [myProp]);
useComponentWillUnmount(() => {
console.log("Component will unmount!");
});
return <div>Hello world</div>;
};
Solution One:
You can use new react HOOKS API. Currently in React v16.8.0
Hooks let you use more of React’s features without classes.
Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
Hooks solves all the problems addressed with Recompose.
A Note from the Author of recompose (acdlite, Oct 25 2018):
Hi! I created Recompose about three years ago. About a year after
that, I joined the React team. Today, we announced a proposal for
Hooks. Hooks solves all the problems I attempted to address with
Recompose three years ago, and more on top of that. I will be
discontinuing active maintenance of this package (excluding perhaps
bugfixes or patches for compatibility with future React releases), and
recommending that people use Hooks instead. Your existing code with
Recompose will still work, just don't expect any new features.
Solution Two:
If you are using react version that does not support hooks, no worries, use recompose(A React utility belt for function components and higher-order components.) instead. You can use recompose for attaching lifecycle hooks, state, handlers etc to a function component.
Here’s a render-less component that attaches lifecycle methods via the lifecycle HOC (from recompose).
// taken from https://gist.github.com/tsnieman/056af4bb9e87748c514d#file-auth-js-L33
function RenderlessComponent() {
return null;
}
export default lifecycle({
componentDidMount() {
const { checkIfAuthed } = this.props;
// Do they have an active session? ("Remember me")
checkIfAuthed();
},
componentWillReceiveProps(nextProps) {
const {
loadUser,
} = this.props;
// Various 'indicators'..
const becameAuthed = (!(this.props.auth) && nextProps.auth);
const isCurrentUser = (this.props.currentUser !== null);
if (becameAuthed) {
loadUser(nextProps.auth.uid);
}
const shouldSetCurrentUser = (!isCurrentUser && nextProps.auth);
if (shouldSetCurrentUser) {
const currentUser = nextProps.users[nextProps.auth.uid];
if (currentUser) {
this.props.setCurrentUser({
'id': nextProps.auth.uid,
...currentUser,
});
}
}
}
})(RenderlessComponent);
componentDidMount
useEffect(()=>{
// code here
})
componentWillMount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
According to the documentation:
import React, { useState, useEffect } from 'react'
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
});
see React documentation
Short and sweet answer
componentDidMount
useEffect(()=>{
// code here
})
componentWillUnmount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
You can make use of create-react-class module.
Official documentation
Of course you must first install it
npm install create-react-class
Here is a working example
import React from "react";
import ReactDOM from "react-dom"
let createReactClass = require('create-react-class')
let Clock = createReactClass({
getInitialState:function(){
return {date:new Date()}
},
render:function(){
return (
<h1>{this.state.date.toLocaleTimeString()}</h1>
)
},
componentDidMount:function(){
this.timerId = setInterval(()=>this.setState({date:new Date()}),1000)
},
componentWillUnmount:function(){
clearInterval(this.timerId)
}
})
ReactDOM.render(
<Clock/>,
document.getElementById('root')
)
if you using react 16.8 you can use react Hooks...
React Hooks are functions that let you “hook into” React state and lifecycle features from function components...
docs
import React, { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
// componentDidMount
useEffect(() => {
console.log("The use effect ran");
}, []);
// // componentDidUpdate
useEffect(() => {
console.log("The use effect ran");
}, [count, count2]);
// componentWillUnmount
useEffect(() => {
console.log("The use effect ran");
return () => {
console.log("the return is being ran");
};
}, []);
useEffect(() => {
console.log(`The count has updated to ${count}`);
return () => {
console.log(`we are in the cleanup - the count is ${count}`);
};
}, [count]);
return (
<div>
<h6> Counter </h6>
<p> current count: {count} </p>
<button onClick={() => setCount(count + 1)}>increment the count</button>
<button onClick={() => setCount2(count2 + 1)}>increment count 2</button>
</div>
);
};
export default Counter;

Resources