I need to know when Hidden Element did mount.
I use ref to check it did mount and control this element.
And use componentDidUpdate to check when Hidden Element did mount.
But use componentDidUpdate in a big project, some elements often trigger componentDidUpdate.
I'm afraid the efficiency will be bad.
Is there another way for me to know when Hidden Element did mount?
Appreciate your help.
In addition, why I need to know it because I need to use a Radium package to build the animation.
When the 'someState' is true, I will auto play the animation for the element.
I use the style animation-play-state : 'running'.
This animation will break in the safari, but it is okay that users visit it for the first time.
When the users refresh safari and have a cache, the users visit it again causing the animation can't autoplay.
So I set animation-play-state : paused.
When I confirm the element did mount, I will use ref change animation-play-state to running.
I find an issue with this problem.
Link: https://github.com/FormidableLabs/radium/issues/912
My sudo code.
import React from "react";
const initialState = {
someState: false
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
this.hiddenElement = null;
}
componentDidMount() {
console.log("componentDidMount");
}
componentDidUpdate() {
console.log("componentDidUpdate");
if (this.hiddenElement !== null) console.log("hiddenElement did Mount");
// I will change the properties of this.hiddenElement, or others in the next steps.
}
render() {
const { someState } = this.state;
return (
<div className="App">
<button onClick={() => this.setState({ someState: true })}>
Click Me
</button>
{someState && (
<div ref={r => (this.hiddenElement = r)}>Hidden Element</div>
)}
</div>
);
}
}
export default App;
Okay, so if you're using an external package and need to manipulate the element based on a state change, then you'll have to basically use componentDidUpdate().
Only worry about the performance when it becomes a bottleneck (after profiling things, etc.).
Since setting a ref doesn't cause componentDidUpdate (it's not a bit of state), you may wish to refactor the animation-mutating method to something like this (note how the update...() method is called in the ref callback).
class App extends React.Component {
constructor(props) {
super(props);
this.state = { someState: false };
this.hiddenElement = null;
}
updateElementAnimation() {
if (!this.hiddenElement) return; // not mounted yet
if (this.state.someState) {
this.hiddenElement.something();
} else {
this.hiddenElement.somethingElse();
}
}
componentDidUpdate() {
this.updateElementAnimation();
}
render() {
const { someState } = this.state;
return (
<div className="App">
<button onClick={() => this.setState({ someState: true })}>Click Me</button>
{someState && (
<div
ref={r => {
this.hiddenElement = r;
this.updateElementAnimation();
}}
>
Hidden Element
</div>
)}
</div>
);
}
}
You can check inside your componentDidUpdate the value of your someState. If this is true, then you are sure that the element you need is rendered, as componentDidUpdate is invoked after an update occurs (thus after the render method).
...
componentDidUpdate() {
if (this.state.someState) {
// your element is rendered, do what you need
}
}
...
Related
Problem
I've parent class which contains list of items and renders component for each item of the list. When some item has been changed (even only one), all items in the list are being rerendered.
So I've tried to implement shouldComponentUpdate(). I am using console.log() to see if it is called but I can't see the log. I've found question shouldComponentUpdate is not never called and tried to return return (JSON.stringify(this.props) !=JSON.stringify(nextProps)); but component still renders itself again. So I've tried just to return false (like do not ever update) but it still does. As the last try I've used PureComponent but it is still being rerendered.
Question
How can I stop children re-rendering if the parent list changes and why is ShouldComponentUpdate never called?
Edit
I've noticed something what I didn't mention in question, I'm sorry for that. I am using context. If I don't use context -> it's ok. Is there any chance to stop re-render while using context? (I'm not using context on updated item - values of context didn't change).
Example
I've parent class which iterates list and renders TaskPreview component for each item of list:
class Dashboard extends React.Component
{
constructor(props) {
this.state = {
tasks: {},
};
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: {...prevState.tasks, [task._id]: task}
}));
// ... some code
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: this.props.tasks.map((task,index) =>
<TaskPreview key={task._id} task={task} onChange={this.onTaskUpdate}/>
})}
</div>
)
}
}
and I've children TaskPreview class:
class TaskPreview extends React.Component
{
shouldComponentUpdate(nextProps) {
console.log('This log is never shown in console');
return false; // just never!
}
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
TaskPreview.contextType = TasksContext;
export default TaskPreview;
As #Nicolae Maties suggested I've tried to use Object.keys for iteration instead of direct map but it still doesn't call "shouldComponentUpdate" and still being re-rendered even if there is no changes.
Updated code:
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: Object.keys(this.props.tasks).map((key,index) => {
let task = this.props.tasks[key];
<TaskPreview key={task._id} task={task}/>
}
})}
</div>
)
}
Component is being re-rendered because of .contextType.
TaskPreview.contextType = TasksContext;
Also as is mentioned in documentation:
The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update. Source: reactjs.org/docs/context
You have to use context somehow else or do not use it at all.
You can use Context.Consumer which won't force re-render of current component but it might force re-render of its children.
<TasksContext.Consumer>
{value => /* render something based on the context value */}
</TasksContext.Consumer>
Instead of return (JSON.stringify(this.props) != JSON.stringify(nextProps)); in your shouldComponentUpdate() life cycle, try specifying tasks object like this return (JSON.stringify(this.props.tasks) != JSON.stringify(nextProps.tasks));
Maybe react is creating new instances of your component and replaces the old instances with them. That's why you're probably not getting your lifecycle method invoked. That can happen if the key property you're assigning in the map always changes.
use from pureComponent and array as state:
class Dashboard extends React.PureComponent
{
constructor(props) {
this.state = {
tasks: this.props.tasks
}
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: [...prevState.tasks, task] // render only new task
}));
render() {
const {tasks} = this.state
return (
<div>
{tasks.map(task => <TaskPreview key={task._id} task={task} />)}
</div>
)
}
}
class TaskPreview extends React.PureComponent
{
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
In the shouldComponentUpdate() method of your TaskPreview component, you should check if the next props have changes in comparison to the current props. Then if there are changes, return true to update the component, otherwise false.
The following example compares all the fields of props object with the new props object. But you can only check the props you are interested in.
shouldComponentUpdate(nextProps) {
return !!(Object.keys(nextProps).find(key => nextProps[key] !== this.props[key]));
}
I tried with below code snippet, shouldComponentUpdate worked as I expected. Could you share your Dashboard initial props ?
class Dashboard extends React.Component {
constructor(props) {
this.state = {
tasks: {}
};
}
onTaskUpdate = task =>
this.setState(prevState => ({
tasks: { ...prevState.tasks, [task._id]: task }
}));
// ... some code
render() {
return (
<div>
{!Object.entries(this.props.tasks).length
? null
: Object.keys(this.props.tasks).map((key, index) => {
let task = this.props.tasks[key];
return (
<TaskPreview
key={task._id}
task={task}
onChange={this.onTaskUpdate}
/>
);
})}
</div>
);
}
}
class TaskPreview extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("This log is never shown in console");
return nextProps.task._id != this.props.task._id;
}
render() {
console.log("task rendered:", this.props.task); // indicates rerender
return (
<button onClick={() => this.props.onChange(this.props.task)}>
Something from props
</button>
);
}
}
my initial props for Dashboard component is :
<Dashboard tasks={{test:{_id:'myId', description:'some description'}}}/>
I'm trying to build a component with auto-updating value based on cookies:
let cookies = 0;
(function count() {
cookies = document.cookie.split("?");
setTimeout(count, 10);
return cookies;
})();
class CartButton extends React.Component {
state = {quantity: cookies.length}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>
);
}
}
'count' function works as expected, component is rendered with the latest value returned. Unfortunately, it does not auto-update when 'cookies' are changed. It returns this error:
Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.
I have tried various variations here but still can't figure it out :/
componentDidMount will get execute only once when your component loads first time. This is the correct place to write any logic which we need to execute after page load.
Try this,
class CartButton extends React.Component {
//It is good to have a constructor for the component which has state
constructor(props){
super(props);
this.state = {quantity: cookies.length}
this.updateQuantity;
}
componentDidMount(){
this.updateQuantity = setInterval(()=> {
cookies = document.cookie.split("?");
this.setState({quantity: cookies.length})
},10)
}
//Don't forget to clear any setInterval like below
componentWillUnmount(){
clearInterval(this.updateQuantity);
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}
Here your CartButton is not updating even though count is working fine because CartButton is not listening to your cookies variable. React component updates only when there is either props or state change.
You can something like this..
class CartButton extends React.Component {
state = {quantity: cookies.length}
componentDidMount(){
setInterval(function count() {
cookies = document.cookie.split("?");
this.setState({quantity: cookies})
}.bind(this), 10)
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}
I'm trying to fetch videos from youtube and display on browser.. I'm able to get videos and update state property. ( checked with console log)
But SearchResults component is not re-rendering when I update the state property. Here is my component
class Application extends React.Component {
state = {videos: []};
handleFormSubmit = async term => {
const res = await youtubeApi.get("search", {
params: {q: term}
});
this.setState({videos: res.data.items})
};
render() {
return (
<div className="container">
<SearchForm onFormSubmit={this.handleFormSubmit}/>
<SearchResults videos={this.state.videos}/>
</div>
)
}
}
SearchResults component
class SearchResults extends React.Component {
state = {featured: null, suggested: []};
componentDidMount() {
if (this.props.videos.length > 0)
this.setState({featured: this.props.videos[0].id.videoId});
if (this.props.videos.length > 1)
this.setState({suggested: this.props.videos.slice(1)});
}
handleSidebarVideoClick = id => {
this.setState({featured: id})
};
render() {
return (
<div className="row">
<div className="col-md-8">
<Video video={this.state.featured}/>
</div>
<div className="col-md-4">
<Sidebar videos={this.state.suggested} onSidebarVideoClick={this.handleSidebarVideoClick}/>
</div>
</div>
)
}
}
You need to have getDerivedStateFromProps() in your SearchResults component in order to expect a new props value when it changes on parent component.
ComponentDidMount is called once the component is mounted, not when it receives prop values when it changes every time.
So you should have something like this:
static getDerivedStateFromProps(nextProps) {
if (nextProps.videos.length > 0) {
return {
featured: nextProps.videos[0].id.videoId
};
}
if (nextProps.videos.length > 1) {
return {
suggested: nextProps.videos.slice(1)
};
}
return null;
}
Ref: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is unlikely you checked the component's state property. You most likely checked this.state which does get updated. The question is what is this.
There is no evidence you bound your event handlers to the component as recommended in the dev guide. So the keyword this doesn't reflect the Component, it reflects the DOM element receiving the event.
To test this, console log the value of this in you event handlers.
To fix:
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleSidebarVideoClick = this.handleSidebarVideoClick.bind(this);
in their respective classes constructors.
Ref: https://reactjs.org/docs/handling-events.html
I have a React component and I want to toggle a css class when clicked.
So I have this:
export class myComponent extends React.Component {
constructor() {
super();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.clicked}><span ref="btn" className="glyphicon"> </span></div>
</div>
);
}
handleClick() {
this.refs.btn.classList.toggle('active');
}
componentDidMount() {
this.refs.btn.addEventListener('click', this.handleClick);
this.setState({
clicked: this.state.clicked = true,
});
}
componentWillUnmount() {
this.refs.btn.removeEventListener('click', this.handleClick);
this.setState({
clicked: this.state.clicked = false,
});
}
}
This problem is that ESLint keeps telling me "this.refs" is depreciated.
What do I do instead? How can I fix it so it's not using depreciated code?
The Lint rule you are referring to is called no-string-refs and warns you with:
"Using string literals in ref attributes is deprecated (react/no-string-refs)"
You are getting this warning because have implemented the deprecated way of using refs (by using strings). Depending on your React version, you can do:
React 16.3 and later
constructor() {
super();
this.btnRef= React.createRef();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.addVote}><span ref={this.btnRef} className="glyphicon"> </span></div>
</div>
);
}
React 16.2 and older
constructor() {
super();
this.btnRef; //not necessary to declare the variable here, but I like to make it more visible.
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.addVote}><span ref={(el) => this.btnRef = el} className="glyphicon"> </span></div>
</div>
);
}
For even better readability, you could also do:
render() {
let myRef = (el) => this.btnRef = el;
return (
<div>
<div onClick={this.addVote}><span ref={myRef} className="glyphicon"> </span></div>
</div>
);
}
Have a look at what the official documentation says on Refs and the DOM, and this section in particular:
Legacy API: String Refs
If you worked with React before, you might be
familiar with an older API where the ref attribute is a string, like
"textInput", and the DOM node is accessed as this.refs.textInput. We
advise against it because string refs have some issues, are considered
legacy, and are likely to be removed in one of the future releases. If
you're currently using this.refs.textInput to access refs, we
recommend the callback pattern instead.
The reason this ESLint rule exists is that string Refs are on their way out. However, for the code above I would recommend to not use a Ref in the first place.
Don't Overuse Refs
React's advantage is that it is declarative. Meaning, we have state and an expression (returned JSX) of how the UI (more precisely the DOM) should look given a certain state.
Whatever can be done using just state and UI expression, should be done this way. The problem with the use of a Ref in the code above is that it makes the code imperative. We can't understand how the DOM will look just from the JSX. Here is how you could achieve the same result in a declarative way:
export class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
handleClick = () => { // with arrow function there is no need for binding.
this.setState(
prevState => {
return {
active: !prevState.active
}
}
)
}
render() {
return (
<div>
<span
onClick={this.handleClick}
className={`glyphicon ${this.state.active && "active"}`}
>
Hello World
</span>
</div>
);
}
}
Refs should be used when state and UI expression aren't enough, and you need access to the actual DOM. For example, focusing on an input field, scrolling to an element, or getting the exact width and height of an element.
If you do use Refs, avoid string refs
String refs harm performance, aren't composable, and are on there way out.
string refs have some issues, are considered legacy, and are likely to
be removed in one of the future releases. [Official React documentation]
[resource1][1], [resource2][1]
Option #1: Use React.createRef
class MyComponent extends Component {
constructor(props) {
super(props)
this.myRef = React.createRef() // create a ref object
}
render() {
return <div ref={this.myRef}></div> // Attach the ref property to a dom element
}
}
Option #2: Use a ref callback
class MyComponent extends Component {
constructor(props){ // Optional, declare a class field
super(props)
this.myRef=null
}
render() {
return <div ref={ (ref) => this.myRef=ref }></div>
} // Attach the dom element to a class field
}
you can try a more declarative way. I changed your code to reflect this. You just need to remind that a component will refresh and call render in every state/props change. So, we can create the class of your element inside render method.
import React from 'react'
export default class myComponent extends React.Component {
constructor() {
super();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
let btnClass = 'glyphicon'
if(this.state.clicked){
btnClass+=' active'
}
return (
<div>
<div onClick={this.handleClick}><span ref="btn" className={btnClass}> </span></div>
</div>
);
}
handleClick() {
this.setState({
clicked: !this.state.clicked
})
}
}
I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?
Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.
Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}
Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}
Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />
You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}