I'm trying to find a way to measure the time certain components take to mount, taking into consideration than the library for measuring is asynchronous (firebase-performance) to be specific.
I need to find out how long a page consisting of multiple asynchronously mounted components takes to render. I am not sure, how I should go about tracking the time between the time component is invoked (function) and the time it takes to mount (render).
I'm slightly confused by the life-cycle methods, as componentWillMount is considered deprecated. Basically, my understanding would be, that in order to track the time component takes to render would be the following.
class Example extends Component<Props> {
async componentDidMount() {
// let's just say this takes 3 seconds as an external API call
await exampleCall()
}
render() {
return <h1>Test</h1>
}
}
And in order to measure the performance of this component in a easily reproducable way, I wanted to construct a wrapper for the component
import perf from '#react-native-firebase/perf'
const withPerformance = (PassedComponent) => {
class Tracker extends Component<Props> {
constructor(props) {
super(props)
console.log('constructor fired')
this.trace = null
}
async componentWillMount() {
const trace = await perf.stratTrace('perf_test')
console.log('Started perf tracking')
this.trace = trace
}
async componentDidMount() {
if (this.trace) {
console.log('Stopped tracking perf')
await this.trace.stop()
} else {
console.log('Mounted, but tracking did not instantiate')
}
}
render() {
return <PassedComponent {...this.props} />
}
}
return Tracker
}
So in order for my perf tracking needs, I could just wrap my existing components with the following wrapper
// last line of Example.js:
export default withPerformance(Example)
This however results, when opening the remote debugger in my simulator with very confusingly:
constructor fired [constructor]
Mounted, but tracking did not instantiate [componentDidMount]
Started perf tracking [componentWillMount]
And my firebase console is empty with no custom traces.
This is confusing and wrong for multiple reasons, but the worst culprit is obviously the breaking of the flow of the lifecycle methods, which should be as follows:
But in my case, it seems to be constructor -> CDM -> CWM
This seems to seemingly break the component lifecycle as componentWillMount somehow fires after componentDidMount (albeit this might be due to the asynchronous nature), which is not ideal to begin with.
componentWillMount is a deprecated lifecycle method to begin with, but there seems to be no replacement for it introduced where I could begin any asynchronous subscriptions before the actual DOM nodes are loaded.
I am forced to use asynchronity in the lifecycle methods, which is not ideal to begin with.
As of note, I am aware of the Profiler introduced in react, but I need this to be able to work in production as well, which right now is not recommended with the Profiler.
Any idea how I should go about doing this?
I did experiment with manual tracking with performance.now() which does work, but it's a client requirement (not in an architectural, but actual client sense) does want the integration to work with the firebase performance tracking.
Related
The standard way to make an API call in functional React is with useEffect:
function Pizzeria() {
const [pizzas, setPizzas] = useState([])
useEffect(
() => fetchPizzas().then(setPizzas),
[]
)
return (
<div>
{pizzas.map((p, i) => <Pizza pizza={p} key={i} />)}
</div>
)
}
But, as this article points out, useEffect will not fire until after the component has rendered (the first time). Obviously in this trivial case it makes no difference, but in general, it would be better to kick off my async network call as soon as possible.
In a class component, I could theoretically use componentWillMount for this. In functional React, it seems like a useRef-based solution could work. (Allegedly, tanstack's useQuery hook, and probably other libraries, also do this.)
But componentWillMount is deprecated. Is there a reason why I should not do this? If not, what is the best way in functional React to achieve the effect of starting an async call early as possible (which subsequently sets state on the mounted component)? What are the pitfalls?
You're splitting milliseconds here, componentWillMount/render/useEffect all happen at essentially the same time, and the time spent fetching occurs after that. The difference in time from before to after rendering is tiny compared to the time waiting for the network when the request is sent. If you can do the fetch before the component renders, react-query's usePrefetch is nice for that.
Considering the scope of a single component, the earliest possible would be to just make the call in the component's function. The issue here is just that such statement would be executed during every render.
To avoid those new executions, you must keep some kind of "state" (or variable, if you will). You'll need that to mark that the call has been made and shouldn't be made again.
To keep such "state" you can use a useState or, yes, a useRef:
function Pizzeria() {
const pizzasFetchedRef = useRef(false)
const [pizzas, setPizzas] = useState([])
if (!pizzasFetchedRef.current) {
fetchPizzas().then(setPizzas);
pizzasFetchedRef.current = true;
}
Refs are preferred over state for this since you are not rendering the value of pizzasFetched.
The long story...
Yet, even if you use a ref (or state) as above, you'll probably want to use an effect anyway, just to avoid leaks during the unmounting of the component. Something like this:
function Pizzeria() {
const pizzasFetchStatusRef = useRef('pending'); // pending | requested | unmounted
const [pizzas, setPizzas] = useState([])
if (pizzasFetchStatusRef.current === 'pending') {
pizzasFetchStatusRef.current = 'requested';
fetchPizzas().then((data) => {
if (pizzasFetchStatusRef.current !== 'unmounted') {
setPizzas(data);
}
});
}
useEffect(() => {
return () => {
pizzasFetchStatusRef.current = 'unmounted';
};
}, []);
That's a lot of obvious boilerplate. If you do use such pattern, then creating a custom hook with it is the better way. But, yeah, this is natural in the current state of React hooks. See the new docs on fetching data for more info.
One final note: we don't see this issue you pose around much because that's nearly a micro-optimization. In reality, in scenarios where this kind of squeezing is needed, other techniques are used, such as SSR. And in SSR the initial list of pizzas will be sent as prop to the component anyway (and then an effect -- or other query library -- will be used to hydrate post-mount), so there will be no such hurry for that first call.
I went through below React official site to understand about new life cycle method getSnapshotBeforeUpdate
But I couldn’t understand the advantage of this method and when exactly we should use.
Below is the example from docs and it differentiates two methods.
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
The two paragraphs above the example you quoted explain the need for that:
In the above example, componentWillUpdate is used to read the DOM
property. However with async rendering, there may be delays between
“render” phase lifecycles (like componentWillUpdate and render) and
“commit” phase lifecycles (like componentDidUpdate). If the user does
something like resize the window during this time, the scrollHeight
value read from componentWillUpdate will be stale.
The solution to this problem is to use the new “commit” phase
lifecycle, getSnapshotBeforeUpdate. This method gets called
immediately before mutations are made (e.g. before the DOM is
updated). It can return a value for React to pass as a parameter to
componentDidUpdate, which gets called immediately after mutations.
In other words: React 16.6 introduced a new feature called "Suspense". This feature enables async rendering - the rendering of a subtree of react components can be delayed (for example to wait for a network resource to load). It is also used internally by React to favor important DOM updates over others to increase the perceived rendering performance. This can - as one would expect - cause substantial delays between the react-side virtual DOM rendering (which triggers componentWillUpdate and render(), but may contain some async component subtree which has to be awaited) and the actual reflection to the DOM (which triggers componentDidUpdate). In older react versions before Suspense these lifecycle hooks were always called with very little delay because the rendering was completely synchronous, which justified the pattern to gather DOM information in componentWillUpdate and use it in componentDidUpdate, which is no longer the case.
The main difference is getSnapshotBeforeUpdate runs before the update, componentDidUpdate runs after.
So if there is anything you need to save before it gets overwritten, that's what getSnapshotBeforeUpdate is for. These are usually externally managed things (uncontrolled in React terms), such as the scrollPosition in your example, or when interoping with other libraries outside React (e.g. a jQuery plugin).
The main guideline is that if you are unsure, you probably don't need it. If you do, you will know it.
I made a very simple project for you to understand when we should use getSnapshotBeforeUpdate life cycle method and I used getSnapshotBeforeUpdate to store user scroll position and used that in componentDidUpdate
github repository
https://github.com/mahdimehrabi/getSnapShot-sample
demo
https://mahdimehrabi.github.io/js/snapshot/
Well, actually getSnapshotBeforeUpdate() runs after the update.
Consider this which kinda explains everything:
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {x:1};
console.log(1);
}
componentDidMount() {
this.setState({x:2});
console.log(3);
}
shouldComponentUpdate() {
console.log(4);
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState){
console.log(5,prevState.x,this.state.x);
return 999;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(6,snapshot);
}
componentWillUnmount() {
console.log(7);
}
render() {
console.log(2);
return null;
}
}
ReactDOM.render(
<Demo />,
document.querySelector('div')
);
ReactDOM.unmountComponentAtNode(document.querySelector('div'));
The output is:
1
2
3
4
2
5 1 2
6 999
7
I'm using redux and redux-saga in an application to manage state and asynchronous actions. In order to make my life easier, I wrote a class that acts essentially as a saga manager, with a method that "registers" a saga. This register method forks the new saga and combines it with all other registered sagas using redux-saga/effects/all:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
}
}
This class is then used by my store to get the _combined saga, supposedly after all sagas are registered:
const store = Redux.createStore(
reducer,
initialState,
compose(Redux.applyMiddleware(sagaMiddleware, otherMiddleware)),
);
sagaMiddleware.run(sagasManager.getSaga());
However, I ran into the problem that depending on circumstances (like import order), this doesn't always work as intended. What was happening was that some of the sagas weren't getting registered before the call to sagaMiddleware.run.
I worked around this by providing a callback on SagasManager:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
this.onSagaRegister();
}
}
And then the store code can use this as
sagasManager.onSagaRegister = () => sagaMiddleware.run(sagasManager.getSaga());
This seems to work, but I can't find in the docs whether this is safe. I did see that .run returns a Task, which has methods for canceling and the like, but since my problem is only in that awkward time between when the store is constructed and the application is rendered I don't that would be an issue.
Can anyone explain whether this is safe, and if not what a better solution would be?
It may depend on what you mean by "safe". What exactly do you mean by that in this case?
First, here's the source of runSaga itself, and where it gets used by the saga middleware.
Looking inside runSaga, I see:
export function runSaga(options, saga, ...args) {
const iterator = saga(...args)
// skip a bunch of code
const env = {
stdChannel: channel,
dispatch: wrapSagaDispatch(dispatch),
getState,
sagaMonitor,
logError,
onError,
finalizeRunEffect,
}
const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)
if (sagaMonitor) {
sagaMonitor.effectResolved(effectId, task)
}
return task
}
What I'm getting out of that is that nothing "destructive" will happen when you call runSaga(mySagaFunction). However, if you call runSaga() with the same saga function multiple times, it seems like you'll probably have multiple copies of that saga running, which could result in behavior your app doesn't want.
You may want to try experimenting with this. For example, what happens if you have a counter app, and do this?
function* doIncrement() {
yield take("DO_INCREMENT");
put({type : "INCREMENT"});
}
sagaMiddleware.runSaga(doIncrement);
sagaMiddleware.runSaga(doIncrement);
store.dispatch({type : "DO_INCREMENT"});
console.log(store.getState().counter);
// what's the value?
My guess is that the counter would be 2, because both copies of doIncrement would have responded.
If that sort of behavior is a concern, then you probably want to make sure that prior sagas are canceled.
I actually ran across a recipe for canceling sagas during hot-reloading a while back, and included a version of that in a gist for my own usage. You might want to refer to that for ideas.
I'm using React.js, and as you know, componentWillMount() is going to be deprecated.
I wanna replace my componentWillMounts.
I'm going to move its logics into constructor. Is there any difference between executing some logic in componentWillMount and in constructor?
For example,
before
class Hello extends React.Component {
componentWillMount() {
doSomething();
}
render() {
return <div>{this.state.name} </div>
}
}
after
class Hello extends React.Component {
constructor(props) {
super(props);
doSomething();
}
render() {
return <div>{this.state.name} </div>
}
}
Also, when doSomething is setState, is there any difference setting state in constructor and in public prop?
in constructor
constructor(props) {
super(props);
this.state = { foo: 1 };
}
in public prop
state = { foo: 1 };
constructor is not the right place for performing some actions. Because it will hold other operations until it's finished.
componentDidMount is the right choice because it's an asynchronous function so that actions are run in the background and there'll be no hamper in the UI rendering.
Here's a list that you can choose when to use between constructor and componentDidMount:
constructor
Do:
initialize state
bind event handlers
If you don't initialize a state and you don't bind methods, you don't need to implement the constructor.
Don't:
Avoid introducing any side-effects or subscriptions. Do not set state by using setState() in the constructor.
componentDidMount
Do:
initialization that requires DOM nodes
load data from a remote endpoint (where to instantiate the network request)
set up any subscriptions (don’t forget to unsubscribe in componentWillUnmount())
You may also be interested to read the comment from the creator of react, Dan Abramov:
I won't like to wait for the component to be mounted to dispatch an ajax call to fulfill the component data dependencies. I would like to do it as soon as possible, like in the constructor, not even in componentWillMount.
If it's an async request, it won't be fulfilled by the time the component mounts anyway, regardless of where you fire it. This is because JS is single threaded, and the network request can't "come back" and be handled while we are still rendering. So the difference between firing it earlier and later is often negligible.
You're right that it matters in some rare cases though and for those cases it might make sense to break the recommendation. But you should be extra cautious as state can update before mounting, and if your data depends on state, you might have to refetch in that case. In other words: when in doubt, do it in componentDidMount.
The specific recommendation to avoid side effects in the constructor and Will* lifecycles is related to the changes we are making to allow rendering to be asynchronous and interruptible (in part to support use cases like this better). We are still figuring out the exact semantics of how it should work, so at the moment our recommendations are more conservative. As we use async rendering more in production we will provide a more specific guidance as to where to fire the requests without sacrificing either efficiency or correctness. But for now providing a clear migration path to async rendering (and thus being more conservative in our recommendations) is more important.
For further interest, you may also visit this post.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
So If you want to perform some action just once, on before mount of component, then getDerivedStateFromProps is not appropriate option.
Use componentDidMount method.
Have more details in https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Use getDerivedStateFromProps() it's next method called after constructor.
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
setState in a constructor will perform unnecessary computation, you can either use the state property as you suggest or this.state = { ... } in your constructor if you need to perform more computation or access props.
The React documentation recommends you use constructor (or a class property) over componentWillMount for initialising state. For side-effects (e.g. http request) that could update state you should consider componentDidMount or componentDidUpdate. With asynchronous state updates, you should always make sure that your component handles the state without that data.
import React from 'react';
class AjaxInConstructor extends React.Component {
constructor() {
super();
this.state = { name: '', age: '' };
this.loadData().then((data) => {
this.setState(data);
});
}
// simulate the AJAX (network I/O)
public loadData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'slideshowp2',
age: 123,
});
}, 2000);
});
}
public render() {
const { name, age } = this.state;
return (
<div>
<p>Can I init component state async?</p>
<p>name: {name}</p>
<p>age: {age}</p>
</div>
);
}
}
ReactDOM.render(<AjaxInConstructor />, document.body);
Above is my demo code. I know people always put ajax in componentDidMount or componentWillMount lifecycle.
But this case also works.
In chrome console, React throw no error and waring. So, My Question is usage like this is completely correct ? Is there have some error?
You can make an AJAX call wherever you want. There is nothing "wrong" in making an AJAX call in the constructor, but there is a catch. You'll want to make the AJAX call only after the component has been mounted or just before it is about to be mounted.
So before component is rendered, making an AJAX call in componentDidMount() or componentWillMount() is recommended. Just because React allows to do "things" does not mean you should! :)
UPDATE
I also realize that initially my answer wasn't rigorous. I have always followed what fellow programmer have followed, blindly.
After searching a bit I found these to be one step closer to the complete answer-
Why ajax request should be done in componentDidMount in React components?
Essence of those answer says that when you call setState() in componentWillMount(), the component will not re-render. Therefore one must use componentDidMount(). After further reading I learned that it was fixed in subsequent release by React team. You can now call setState() in componentWillMount(). I think that is the reason why everyone recommends making AJAX calls in didMount.
One of the comments also puts forth my thoughts very articulately-
well, you are not calling setState from componentWillMount nor
componentDidMount directly, but from a new async stack. I have no idea
how exactly react is implemented to keep reference to this with live
event listeners from various methods. if using undocumented features
is not scary enough for you and want a bit of excitement that it might
work and maybe even in future versions, then feel free, I don't know
whether it will break or not