React collects operations(like DOM operations such 'ADD','REPLACE','REMOVE' and more) so they could execute them in batch in one shot at the end of each render.
for example, setState call inside React component is scheduled to the end of the construction of this React tree by adding this operation to the operation queue. then react will go over this queue and will decide what changes need to be made to the DOM.
React will decide whether or not to call another render based on whether or not the operation queue is empty or not.
more info on this awesome tutorial which summarizes the basics of how React works internally.
I need access to this queue to decide whether or not the current render was the last for a very custom React component. (YES maybe I can avoid it but currently, this is my requirement)
the access must be from inside of this component.
my check will be called from the lastest useEffect which is after the render ends and the DOM was updated and is the latest lifecycle event, so if the operation queue is empty there will be no more renders for sure. (here nice article explains and demonstrates the order of hook calls)
couldn't find any public API, but a workaround would also be acceptable. (forking and editing React is not a workaround)
this src file is probably the main logic to this queue. and this is the type of the actual queue. however this is the source code and this queue is not exported in the built versions of react(neither development or production build)
so, is there a way to access the internal operation queue of React?
EDIT - Warning
this is for educational purposes only - do not use it on production!
this approach is not safe based on React core team member - I've already asked. it could be safe if you plan to use a fixed version of React without upgrading later.
####### END OF EDIT #######
SOLVED!
so after many many hours digging into React codebase I finally wrote a hook that tells if any update is currently scheduled.
Note: would work for function components only, and this hook is not well tested.
you can see some internal state of React by the undocumented __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED property. this prop holds ReactCurrentOwner which is basically a reference to the current component that is being constructed.
const currentOwner = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current;
currentOwner is the current component that is being constructed. this prop is available during renders only(because in effects is after render no component is being currently constructed).
but because another render can be triggered from state set from effects we should always call ower check from the latest effect
inside it .current.memoizedProps you will find a linked list of all hooks that were declared to this point on this component.
each hook holds a queue for holding scheduled updates, and inside it, there is a pending prop that tells if any update is currently scheduled for the next render.
we could run over this linked list to find out if an update is scheduled by any hook:
const wouldUpdate = (currentOwner) => {
let newObj = currentOwner?.memoizedState;
// go over the linked list of hooks and find out if there is any pending update
while (newObj && 'next' in newObj) {
newObj = newObj['next'];
if (newObj?.queue?.pending) return true;
}
return false;
};
so to summer up we could build a custom hook to check if the current render is the latest scheduled render:
const wouldUpdate = (currentOwner) => {
let newObj = currentOwner?.memoizedState;
// go over the linked list of hooks and find out if there is any pending update
while (newObj && 'next' in newObj) {
newObj = newObj['next'];
if (newObj?.queue?.pending) return true;
}
return false;
};
export const useDoesUpdateIsScheduled = () => {
// #ts-ignore
// hold the current owner ref so we could call it from effects
const currentOwner = useRef(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current);
return () => wouldUpdate(currentOwner.current);
};
so many hours for so little code... ;<
Usage:
const YourComponent = (props) => {
//..
// code, hooks ,logic, effects would be here
//..
// should be could from the last useEffect
const wouldUpdate = useDoesUpdateIsScheduled();
useEffect(() => {
console.log(wouldUpdate());
});
return <div>... your jsx here ...</div>;
};
screenshot of test component on mount:
you can see that on the latest render our hook tells us there are no pending updates.
you can also call wouldUpdate from function body but take into account that updates can be scheduled from effects (means that calling it while rendering would not catch these updates)
the popular why-did-you-render also uses this undocumented __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED property to achieve it's goal.
THATS IT(actually it wasn't worth the hours, and it will probably break on various cases as this is not public API).
Related
I am creating a calendar date selection function component for assigning days to schedules in my React app and I wanted to be able to pre-populate calendar with the existing data so that it could be modified by the user.
This is what I have so far:
const initialOptions: { [key: string]: number[] } = {};
for (const option of Object.keys(props.options)) {
const dates = props.options[option].dates;
initialOptions[option] = dates ? dates : [];
}
const [selectedDates, setSelectedDates] = useState(initialOptions);
However, when I try and render the page, I get this:
React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
After reading through the react rules of hooks, I didn't see anything that indicated that react was depending on the value of the parameter to "associate local state with [my useState() call]". All it really said was...
As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them.
So why is react complaining at me when I am calling useState() in top-level react code that is outside of any conditional statements or functions as per their own rules?
The comments on this question that basically said calls to react hooks need to be before any control structures, even if they are unrelated were what pointed me in the right direction.
The answer provided in the comments was not quite satisfactory though since I needed to process the inital value of selectedDates if an initial value was provided and have that available before I called useState() in order to pass it as a parameter.
Despite being perplexed by this and the somewhat nonsensical nature of this solution (order shouldn't matter with two barely-related pieces of code, right?), I managed to refactor my logic such that it both stopped react from complaining AND allowed me to still conditionally set the selectedDates in my react calendar component.
Here's what I ended up with:
const initialOptions: { [key: string]: number[] } = {};
Object.entries(props.options).forEach(value => {
const [id, options] = value;
if (options.dates) {
initialOptions[id] = options.dates;
}
});
const [selectedDates, setSelectedDates] = useState(initialOptions);
As someone who isn't that familiar with the internals of react, it seems that either:
the react team got something wrong when writing the ESLint plugin for the react hook rules, or
there was a functional limitation in how ESLint works that doesn't allow for a more specific/accurate check, causing the developers to go with a "better safe than sorry" approach by using a less specific check that still caught rule violations as well as edge cases like this one
So overall, my conclusion is, by replacing my for loop with a call to .forEach(), the ESLint
plugin saw my loop as a function rather than a control structure/conditional and allowed my code to pass the test and run without issue.
Now, as a self-described "junior" react developer, i'm pretty sure that tricking ESLint into not giving an error like this is not a good long-term solution. Ideally ESLint rules probably need updating to better check for improper use of conditionals, and/or the react docs should be updated with an example for how to conditionally set a hook's default value in a way that doesn't violate the rules of react hooks.
EDIT: I have created an issue for the react documentation in order to find out what a good long-term solution to this would be and get the documentation and/or ESLint plugins updated if necessary
If you ignore the warning that means that you are setting your expectations wrong on how your Component's code will be executed during renderings.
Just by looking at initialOptions, you can see that the initial value is based on incoming props. In React when the props change your Component gets re-rendered, the initialOptions is re-evaluated BUT it's NOT updated again by useState(initialOptions).
Sure you can say: "but my useState(initialOptions) is not wrapped around any condition!". While that is absolutely true, you didn't inform React that selectedDates needs to be updated between renders. It's value is still the first initial value when the Component was rendered first time.
You need to move the foreach logic into a useEffect with dependency to props.options.
Example based on your code:
const initialOptions: { [key: string]: number[] } = {};
const [selectedDates, setSelectedDates] = useState(initialOptions);
useEffect(() => {
// Here it's okay to have an optional condition!
if (!props.options.length) { return false; }
const newOptions = [];
Object.entries(props.options).forEach(value => {
const [id, options] = value;
if (options.dates) {
newOptions[id] = options.dates;
}
});
setSelectedDates(newOptions);
}, [props.options]);
I've prepared a sandbox example which demonstrates why the rule "Only Call Hooks at the Top Level - Don’t call Hooks inside loops, conditions, or nested functions." must be respected https://codesandbox.io/s/immutable-meadow-xvy50t?file=/src/Component.js <-- click the body of the page repeatedly and notice that the component doesn't update.
So I am trying to migrate an existing Portal implementation from the old unstable_renderSubtreeIntoContainer to the new portal implementation.
I have an issue though, the relevant code has the following functionality:
unstable_renderSubtreeIntoContainer(
this,
this.props.children,
this.portalElement,
() => {
if (this.props.isOpen) {
this.props.onRender(this.portalElement,
this.getTargetElement());
}
callback(); //runs this.props.open() if the update ran open
},
);
Some of the open/close logic could be simplified by wrapping the component to be rendered inside an object and the appropriate callbacks could be called from there. But it seems createPortal has no callback to allow you specify when a render has or hasn't taken place. Is there anyway to synchronously or asynchronously on a createPortal call has finished rendering?
I was creating a very simple first Native app in React but my app is responding very slow to events such as touch
As suggested I have implemented several things like implementing FlatList instead of mapping and so on..
The two things which i think i might be doing wrong is updating state in
componentDidUpdate() {
var updateCoinData = [...this.props.cryptoLoaded];
socket.on('trades', (tradeMsg) => {
for (let i=0; i<updateCoinData.length; i++) {
if (updateCoinData[i]["short"] == tradeMsg.coin ) {
//Search for changed Crypto Value
updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
updateCoinData[i]['mktcap'] = tradeMsg['message']['msg']["mktcap"]
updateCoinData[i]['price'] = tradeMsg['message']['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(updateCoinData);
}
}
})
or maybe calling setState too often in my child component.
If anyone wants reference this is the link to my repository
https://github.com/irohitb/Crypto
These are the two files where most of the operations are happening
Child Repository
Parent Repository
I know it is probably too much to ask but can someone help me/suggest me how I can improve its performance.
There is a 'structural error', redux abusing. Not directly related to question (peformance) however it can have some indirect relation.
You're processing state/data locally while it's a reducers responsibility.
You should prepare payload from socket data and dispatch redux action/message. Reducer should create copy of prev state (use slice() to copy array, spread operator '...' is slow), search for matching id, update it and return as new state.
Main idea of reducer is extracting logic of state transitions to be simply testable. What can you test when only storing/receiving ready data set?
I am trying to use redux state store in a redux app using a rxjs obserable wrapper. Source tutorial
I first tried this approach when I switched from ngrx to redux in angular. Now I’m using this pattern in a react app. However, I have a bit of an issue. When I subscribe to some state store stream I use setState(foo) to store the value in the component. This in turn triggers a new render cycle. I’d like to have one render cycle per component instead of 2 or more.
I’ve been trying to inhibit the initial rendering of the component in order to have it triggered first and only once by the state store subscription. When you have multiple nested components and multiple subscriptions they tend to create wasteful renderings just to do the app init. I know that React does a great job of optimising for multiple renderings but still I find that keeping an eye on the rendering cycles is healthy for avoiding subtle bugs.
Any recommendation on how to trigger the first rendering from the state store subscription?
app.module.tsx
private subscribeToAppSettings() {
DEBUG.cmp && debug('Subscribe appSettings$');
appSettings$().pipe(
skip(1), // For REST api calls I skip the initial state
takeUntil(this.destroyed$),
)
.subscribe(settings => {
DEBUG.subscribe && debug('==> Observe appSettings$', [settings]);
this.setState({ settings });
});
}
As you can see AppModule and everything else is rendered twice because of this subscription. This is a filtered set of logs, showcasing when the app is running the render() methods. Just the init stage, no user interactions.
After reviewing the entire architecture again I figured that I need to manually set the initial state in the components. Now, the initial rendering is doing the useful work, and the second rendering will be ignored by the react change detection.
I still have the extra rendering cycles. However, I see that this is the state of affairs with change detection. A lot of things trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on performance. As they say: I'm barking at the wrong tree.
state.service.tsx
/** Access state changes as an observable stream */
export const store$ = new Observable<AppState>(observer => {
// All state store observable use `distinctUntilChanged()` operator.
// Without this initial state, `distinctUntilChanged()` will be unable to compare previous and current state.
// As a result, the webapi observable will miss the first response fron the server.
observer.next(appInitialState);
let appState: AppState;
store.subscribe( () => {
appState = store.getState();
observer.next(appState);
});
})
app.module.tsx
constructor(props: any) {
super(props);
DEBUG.construct && debug('Construct AppModule');
this.state = {
navigatorIsVisible: appInitialState.navigator.isVisible,
searchOverlayIsVisible: appInitialState.search.isVisible
} as State;
getAppSettings();
}
search.overlay.smart.tsx
searchOverlayIsVisible$().pipe(
takeUntil(this.destroyed$),
skip(1), // Ignore init state
)
.subscribe(searchOverlayIsVisible => {
DEBUG.subscribe && debug('Observe searchOverlayVisiblity$', searchOverlayIsVisible);
this.setState({ searchOverlayIsVisible });
this.state.searchOverlayIsVisible
});
search.overlay.service.tsx
export function toggleSearchOverlay(isVisible?: boolean) {
if (DEBUG.service && DEBUG.verbose) debug('Toggle search overlay', isVisible);
store.dispatch(
searchActions.toggleSearch(isVisible)
);
return searchOverlayIsVisible$();
}
export const searchOverlayIsVisible$ = () => store$.pipe(
map( state => SEARCH_VISIBILITY(state) ),
distinctUntilChanged()
);
Conclusions
Pushing the initial state in the store$ observable is necessary because we need all the state store observables to recieve their first state. Without this initial state distinctUntilChanged() will not be able to run the comparison between previous and current state. If distictUntilChanged is blocking the obsevables then we end up blocking responses from the webapi. This means we see empty pages even if the state store received the first set of data.
Notice that we are using the component constructor to setup the initial state. Thus, we use the first rendering cycle for useful work. The second rendering will be inhibited by using skip(1) in all state store observables.
Even if we setup init state in constructor we still keep the initial state in reducers as well. All the TOGGLE actions need an initial state to start from.
Be aware that, a lot of processes trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on DOM rendering performance.
This means it is close to impossible to have just one componentDidUpdate call per route change in LessonsPage. This means we still need to filter out duplicate calls to handlRouteParams().
I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).