React functional component useEffect hook with dependency equal in class component lifecycles - javascript

I use the useEffect hook inside functional components with a dependency so that dependency changes , useEffect function will re-run like this :
const [show, setShow] = React.useState(false);
React.useEffect(() => {
console.log("Do something")
} , [show]);
I wanted to know what is available in react's class component to do exactly like this ?
Is there any lifecycle method to have this functionality ?

you can use combination of componentDidMount and componentDidUpdate:
componentDidMount(){ //use this method if you want to trigger the side effect first time
console.log("Do something")
}
componentDidUpdate(prevProps,prevState) {
if (this.state.show !== prevState.show) {
console.log("Do something");
}
}

To control your component use shouldComponentUpdate (link for the article). It has 2 arguments nextProps and nextState. You can compare this.state.field and nextState.field and if they are different make side effect:
class ClickButton extends React.Component {
constructor(props) {
super(props);
this.state = {class: "off", label: "press"};
this.press = this.press.bind(this);
}
shouldComponentUpdate(nextProps, nextState){
if(nextState.class !== this.state.class){
return true
}
return false;
}
press(){
var className = (this.state.class==="off")?"on":"off";
this.setState({class: className});
}
render() {
return <button onClick={this.press} className={this.state.class}>{this.state.label}</button>;
}
}
If ypu return true from this method, it says React that component should update, false in other way, Component won't update.
Also you can extends from PureComponent (PureComponent), it will be automatically follow props and state:
class ClickButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {class: "off", label: "press"};
this.press = this.press.bind(this);
}
press(){
var className = (this.state.class==="off")?"on":"off";
this.setState({class: className});
}
render() {
return <button onClick={this.press} className={this.state.class}>{this.state.label}</button>;
}
}
But it makes a superficial comparison (by reference). If you have nested fields in you state, and they are changing, PureComponent doesn't rerender Component.
There are other methods like componentDidUpdate (link) and componentDidMount (link). First, called when component rerender:
componentDidUpdate(prevState) {
if (this.state.userID !== prevState.userID) {
this.fetchData(this.state.userID);
}
}
Talking about second one, it will be called when component set in the DOM.
In your case use componentDidUpdate

Related

Why React Component class and functionnal component does not have the same behaviour?

I'm using React 17 and I wonder why the following components does not behave equally.
When using react component class, props inside methods are updated whereas with Functional component, they are not.
Using the React.Component class (visible props is updated inside check method)
class Clock extends React.Component {
constructor(props) {
super(props);
}
check() {
console.log(this.props.visible);
}
componentDidMount() {
this.timerID = setInterval(
() => this.check(),
5000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
render() {
return (
<div />
);
}
}
Using function with hooks (visible props is NOT updated inside check method)
function Comp(props) { // contains visible attr (false by default)
const check = () => {
console.log(props.visible); // stays as the default value when Comp mounted
};
useEffect(() => {
const timerId = setInterval(() => {
check();
}, 5000);
return () => clearInterval(timerId);
}, []);
return <div />;
}
Does anybody has an idea?
hello if I understood your question well here is a solution to your problem
https://codesandbox.io/s/crazy-wave-cj2n3?fontsize=14&hidenavigation=1&theme=dark
and to explain what's the problem in your code
u are not telling the child component to re-render when the props (visible) is changed and to do that u need to pass it in the array of useEffect function
if you want to understand that more uncomment the line in App component and remove visible from useEffect
you will see that the state is actually permuting from true to false in the parent but not in the child

React - passing 'this' as a prop

Is there any side effect I do not see by doing this ?
class App extends React.Component {
hello() {
console.log("hello")
}
render() {
return <Layout app={this}>
}
}
So later on I can refer to this.props.app.hello (and others) from Layout ?
This is not safe.
React will not know how to watch for changes, so you may miss re-renders. React uses === to check for state changes, and App will always be === to App, even when state or properties change.
Take this example:
class App extends React.Component {
constructor(props) {
super(props);
this.setState({text: 'default value'});
}
hello() {
this.setState({...this.state, text: 'new value'});
}
render() {
return (
<div onClick={this.hello}>
<Layout app={this}>
</div>
);
}
}
class Layout extends React.Component {
render() {
return <div>{this.app.state.text}</div>
}
}
When you click on the parent div, this.hello will be called, but the child component will not detect the state update, and may not re-render as expected. If it does re-render, it will be because the parent did. Relying on this will cause future bugs.
A safer pattern is to pass only what is needed into props:
class App extends React.Component {
//...
render() {
return (
<div onClick={this.hello}>
<Layout text={this.state.text}>
</div>
);
}
}
class Layout extends React.Component {
render() {
return <div>{this.props.text}</div>
}
}
This will update as expected.
Answer
There's nothing wrong in passing functions as props, as I can see in your example, the only thing you have to do is make sure your function is bound to the current component like the following example
Reference
React: Passing Functions to Components

React app: props do not get updated externally

I have a React app that gets initialized as simple as:
let globalTodos = some_fetch_from_localstorage();
...
function changeGlobalTodos() {
globalTodos = another_fetch_from_localstorage();
}
...
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
Inside of the app I'm doing the following:
constructor(props) {
super(props);
this.state = {
todos: []
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.todos !== prevState.todos) {
return { todos: nextProps.todos };
} else return null;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.todos !== this.props.todos) {
this.setState({ todos: this.props.todos });
}
}
The problem is that whenever I update globalTodos, the props on the React app don't get updated: it stays on the initial globalTodos's value.
I have tried playing with getDerivedStateFromProps is being called only on first setup of the props while componentDidUpdate never gets called :-/
What am I missing here?
I can't leave a comment, so I'll just post this here. React won't re-render unless you're updating a state.
I'd make globalTodos a state and add onto it from there using setState, then you can pass that on as a prop to the child component in your case ReactApp. You don't need to change them as states in your child component.
Example:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
globalTodos: initialFetchedArray
};
}
changeGlobalTodos() {
let newTodos = fetchNewArray;
this.setState({globalTodos: newTodos});
}
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
}
//You just need your prop here, here you can do whatever you want to do with the array if it's display you can use map
class Child extends Component {
render {
return(
{this.props.todos}
)
}
}
Really the main thing here is making your globalTodos a state, using setState to change that state, and just passing that state down as a prop.

Specialized shouldComponentUpdate on PureComponent

I am trying to create a component that shouldn't when a certain property is true, but should perform a shallow compare (the default for PureComponent).
I've tried doing the following behavior:
export default class ContentsListView extends PureComponent<Props> {
shouldComponentUpdate(props: Props) {
if (props.selecting) {
return false;
}
return super.shouldComponentUpdate(props);
}
render() {
}
}
However, super.shouldComponentUpdate is not defined. Is there some way to "tap into" the shallow compare of PureComponent without writing my own?
You should not implement shouldComponentUpdate when you are extending your component using PureComponent. If however you want to use the shouldComponentUpdate functionality you can simply write a wrapper component around your original component or use HOC to implement your custom shouldComponentUpdate and render the PureComponent
Sample code
class ContentsListView extends PureComponent {
render() {
console.log("render list");
return (
...
);
}
}
export default class Wrapper extends React.Component {
shouldComponentUpdate(props) {
if (props.selecting) {
return false;
}
return true;
}
render() {
return <ContentsListView {...this.props} />;
}
}
You can see a working demo on codesandbox here
There is no super.shouldComponentUpdate in PureComponent because it implements shallow checks by checking isPureReactComponent property, not with shouldComponentUpdate. A warning is issued when both isPureReactComponent and shouldComponentUpdate are in use because shouldComponentUpdate efficiently overrides the behaviour of isPureReactComponent.
React doesn't expose its shallowEqual implementation, third-party implementation should be used.
In case this becomes a common task, own PureComponent implementation can be used for extension:
import shallowequal from 'shallowequal';
class MyPureComponent extends Component {
shouldComponentUpdate(props, state) {
if (arguments.length < 2)
throw new Error('Do not mess super arguments up');
return !shallowequal(props, this.props) || !shallowequal(state, this.state);
}
}
class Foo extends MyPureComponent {
shouldComponentUpdate(props, state) {
if (props.selecting) {
return false;
}
return super.shouldComponentUpdate(props, state);
}
}
If you can consider writing your component in a function instead of class, try React.memo.
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
As a second argument, you can pass a function, where you can use prevProps, nextProps, and return false, if you want it to render, or return true if you don't.
import React, { memo } from "react";
const ContentsListView = ({ selecting }) => {
return <div />;
};
const shouldComponentUpdate = (prevProps, nextProps) => {
if (nextProps.selecting) { return true; }
return JSON.stringify(prevProps) === JSON.stringify(nextProps)
}
const Component = memo(ContentsListView, shouldComponentUpdate);
I was looking for the same thing as you can see from my comment in January, I had eventually settled on using the shallow-compare library - https://www.npmjs.com/package/shallow-compare
export default class ContentsListView extends Component<Props> {
shouldComponentUpdate(nextProps, nextState) {
if (props.selecting) {
return false;
}
return shallowCompare(this, nextProps, nextState);
}
render() {
}
}
however extend from Component, not PureComponent

Reactjs, parent component, state and props

I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.

Categories

Resources