ReactJS lifecycle method inside a function Component - reactjs

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;

Related

Invalid hook call. Hooks can only be called inside the body of a functional component

I am trying to create something like a background worker that monitors a database (RabbitMQ). If there is a new entry, it will notify the user in the UI.
I call my "background worker" from the appbar:
<AppBar>
<BGWorker />
</AppBar>
This is my BGWorker.js
import { Notify } from 'Notify.ts';
const { Client } = require('#stomp/stompjs');
class BGWorker extends Component {
componentDidMount(){
this.client = new Client();
this.client.configure({
brokerURL: 'ws://192.168.1.5:15555/ws',
connectHeaders:{
login: "guest",
passcode: "guest"
},
onConnect: () => {
this.client.suscribe('exchange/logs', message => {
console.log(message.body); //yes this does print out correctly
Notify(message.body, 'info'); //error here
},
});
this.client.activate();
}
render(){
return (
<div/>
);
}
}
export default BGWorker
and this is my Notify function located in Notify.ts
import { useDispatch } from 'react-redux';
import { showNotification } from 'react-admin';
export const Notify = (msg:string, type:string='info') => {
const dispatch = useDispatch();
return () => {
dispatch(showNotification(msg, type)); //this will call React-Admin's showNotification function
//... and other stuff
};
};
But I get a "Invalid hook call. Hooks can only be called inside the body of a functional component." whenever an entry comes in. How do I fix this? I read the react documentation and tried to use a custom hook but it doesn't work as well (not sure if I'm doing it correctly):
function useFoo(client){
if(client !== undefined)
{
client.suscribe('exchange/logs', message => { //I get an 'Uncaught (in promise) TypeError: Cannot read property of 'subscribe' of undefined
Notify(message.body, 'info');
},
}
}
class BGWorker extends Component {
...
onConnect: useFoo(this.client);
...
}
In order to use hooks, you should convert your class into functional component.
I assume you don't know about hooks and functional components.
This blog will help you understand the nuances of the conversion -> https://nimblewebdeveloper.com/blog/convert-react-class-to-function-component
Secondly, you need to know how hooks works. Refer to this -> https://reactjs.org/docs/hooks-intro.html
function BGWorker() {
const dispatch = useDispatch();
React.useEffect(() => {
const client = new Client();
client.configure({
brokerURL: 'ws://192.168.1.5:15555/ws',
connectHeaders:{
login: "guest",
passcode: "guest"
},
onConnect: () => {
client.suscribe('exchange/logs', message => {
// dispatch your actions here
dispatch()
}),
});
client.activate();
}, []);
return <div />
}
It is better if you read a few blogs and watch a few tutorials and understand hooks and functional components. And then try to solve your problem.
You may find your answer here and in the long run, you need to understand both of them.

Trying to run function only if state changes

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]);

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>
);
}

Using hooks in a higher order component

I would like to develop a new feature, which previously would have lived in a higher order component, using hooks. However, since according to the React Documentation:
"You can’t use Hooks inside of a class component, but you can definitely mix classes and function components with Hooks in a single tree."
So let's say I have some existing class component ExistingComponent that I want to extend with additional functionality, say, listening to window.resize. I would expect to do it like this.
// Existing Component
export class ExistingComponent extends React.Component {
render() {
return <div>I exist!</div>;
}
}
// Hook
export const useWindowResize = () => {
function resize() {
console.log('window resized!');
}
useEffect(() => {
window.addEventListener('resize', resize);
return function cleanup() {
window.removeEventListener('resize', resize);
};
});
};
// HOC
export const withWindowResize = component => {
useWindowResize();
return component;
};
// Extended Component
export const BetterComponent = withWindowResize(ExistingComponent);
However, this fails with Uncaught Invariant Violation: Hooks can only be called inside the body of a function component. I do use react-hot-loader, but I am still able to use hooks in component functions that don't return a class component. Also, I can remove the useWindowResize() from the function and it renders as expected.
I am also able to render the example provided in the docs, so I know it's not a problem with hooks generically:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Is this the wrong approach?
You can return a new function component from your withWindowResize HOC in which you call the hook and spread the props on the component you pass in.
You can also pass an empty array as second argument to useEffect to only have it run once after the initial render.
const { useEffect } = React;
class ExistingComponent extends React.Component {
render() {
return <div>I exist!</div>;
}
}
const useWindowResize = () => {
useEffect(() => {
function resize() {
console.log('window resized!');
}
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
};
}, []);
};
const withWindowResize = Component => {
return (props) => {
useWindowResize();
return <Component {...props} />;
}
};
const BetterComponent = withWindowResize(ExistingComponent);
ReactDOM.render(<BetterComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

How to add class in a common Header component based on the redux data

In my react JS application, I have a notification icon added in header component. I have created a separate component where I am doing api calls to get the data and display it. what I am trying to achieve here is to change the color of the icon in a Header component if there is some notification alerts.
My Header component-
import React from "react";
import { connect } from "react-redux";
import {
setPoiData,
getNotification,
updateNotification
} from "../../actions/action";
import { Link } from "react-router-dom";
const axios = require("axios");
class Notification extends React.Component {
render() {
const data = this.props.getNotificationStatus;
const highlightBellIcon = Object.keys((data.length === 0))
return (
<div className="notification-parent">
<Link to="/notification-details">
<span className={"glyphicon glyphicon-bell " + (!highlightBellIcon ? 'classA' : 'classB')} />
</Link>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect (mapStateToProps)(Notification)
Here, getNotificationStatus is the state that holds the value in Redux.
Notification-details Component-
import React from "react";
import { connect } from "react-redux";
import {
getNotification
} from "../../actions/action";
import { Spinner } from "../Spinner";
import { setTimeout } from "timers";
import NotificationTile from "../NotificationTile/NotificationTile";
const axios = require("axios");
class NotificationDetails extends React.Component {
componentDidMount = () => {
this.intervalId = setInterval(() => this.handleNotification(), 2000);
setTimeout(
() =>
this.setState({
loading: false
}),
10000
);
};
componentWillUnmount = () => {
clearInterval(this.intervalId);
};
handleNotification = () => {
let postData = {
//inputParams
}
//call to action
this.props.dispatch(getNotification(postData));
};
getNotificationDetails = data => {
const payloadData =
data.payLoad &&
data.payLoad.map(item => {
console.log(this);
return <NotificationTile {...item} history={this.props.history} />;
});
//console.log(payloadData);
return payloadData;
console.log("InitialState" + payloadData);
};
render() {
const { loading } = this.state;
const data = this.props.getNotificationStatus;
return (
<div className="notificationContainer container">
<div className="notification-alert">
{!loading ? (
this.getNotificationDetails(data)
) : (
<h1>
Waiting for notifications..
<Spinner />
</h1>
)}
</div>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect(mapStateToProps)(NotificationDetails);
The problem I am facing is always classB is getting added since the api call happens on click of the bell icon. So when I land to the page first time, api call doesn't happen unless I click on the bell icon. My code is absolutely working fine, It is just that I need to add the class to my Notification component (which is a global component) based on the response received in NotificationDetail Comp which is a sibling comp.Any suggestions where I am going wrong?
When you have to update your REDUX store, based on an Asynchronous call, you should be using something called Action Creators, These action creators will give you the ability
to dispatch an action after your response from you async call.
Use Redux-thunk
In this below code setTimeout is where your async call goes in
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
Update you REDUX Store and you call this action creator from componentDidMount()
so that you get your notifications the very first time.
notification-details is a separate component which fetches the data and adds it to store and
there is <Link to="/notification-details"> which loads this component, in this case, your store
initially will not have data until you click on the bell icon, which is correct behavior.
Solution:
Please go one step up in your component tree, I mean go to a component which is superior to
Notification ( its Container component ), and load notification-details in there ( you can use any creational lifecycle hook),
,also have a flag ( something like isNotificationLoaded ) which checks where state.root.getNotificationStatus
is filled with data and load the Notification panel only after that is true.
something like
render() {
const data = this.props.getNotificationStatus;
const noticationPanel = data.length > 0 ? <Notification/> : null;
return({noticationPanel});
}
This will make sure loads only when there is data, until then
you can show some activity indicator.
Hope this helps to solve your problem.

Resources