I have a react native component which contains both WillFocus and componentDidMount functions.
My Questions is, if I navigate to this component which function is getting triggered first? 'WillFocus' or 'componentDidMount'
Sample code is showing below
class Notifications extends Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
const{navigation}=this.props
this.state = {
highlightHome : true,
highlightNotifications: true,
}
}
willFocus = [this.props.navigation.addListener(
'willFocus',
payload => {
console.log('willFocus')
}
)]
componentDidMount() {
console.log('componentDidMount')
}
}
React Navigation emits events to screen components that subscribe to them.
componentDidMount:
This method is called once all our children Elements and our Component instances are mounted onto the Native UI. When this method is called we now have access to the Native UI (DOM, UIView, etc.), access to our children refs and the ability to potentially trigger a new render pass.
willFocus:
the screen will focus.
By definition, willFocus will be called after ComponentDidMount because it mounted all the UI components.
componentDidMount call first, but in the next time (back or something...) just willFocus call
Related
I have a React class component that renders null by default, and some children after an activate() function is called. Roughly like this:
class MyComponent extends React.Component {
...
activate() {
this.setState({showComponent: true})
}
...
render() {
if (this.state.showComponent) {
return <Child />
} else {
return null
}
}
}
I have an external JavaScript script in which I interact with the MyComponent (call activate function).
To keep it short, my problem is that after calling activate() in this external JS script and trying to access HTMLElements in the <Child /> component right after (with document.getElementById) I get null pointers as the <Child /> component is rendered asynchronously a bit later.
Is there a way to adapt the activate() function in MyComponent to 'wait' for all children in <Child /> to be mounted?
I already tried to exploit the async nature of setState and tried async activate() with await this.setState({showComponent: true}) but this did not change the rendering order.
So in short, is there a way to wait for children to be rendered after you perform a setState update. I guess this is a bit of a special case as it involves conditional rendering and MyComponent initially renders nothing.
Happy about any ideas!:)
How about emitting a custom event on window object inside componentDidMount of Child component and listening for this event in the external javascript and accessing the HTML element in the event handler.
we can pass a function to child component which we can call in child's componentDidMount hooks , so when the child gets mounted , it will trigger that function ... but keep in mind don't update any state variable of parent component in that passed function otherwise it will stuck in a loop (coz as the state variable changes , it will re- render all the child component and eventually component did mount will also be called ...)
class MyComponent extends React.Component {
...
activate() {
this.setState({showComponent: true})
}
callback = ()=>{
// function to be passed in child component
// don't update any state variable here ..
}
...
render() {
if (this.state.showComponent) {
return <Child callback={this.callback} />
} else {
return null
}
}
}
class child extends React.Component {
constructor (props){
super(props)
}
componentDidMount(){
this.props.callback() // this will be called when this child will be
mounted
}
}
In react native after componentDidMount() its not call again when you navigate to other screens and return to your component (screen). so react native navigation suggest do it by bellow JS codes:
componentDidMount() {
this._Reload = this.props.navigation.addListener('focus', () => {
//do something to reset component
});
}
componentWillUnmount() {
this._Reload();
}
but now I get this error: undefined is not an object (evaluating 'this.props.navigation.addListener') because I use my component in a tab component(nested). how can I add a focus to my component to refresh?
I think I must do it by prop drilling.
I would suggest passing parent screen navigation object (so that you will have focus listener) via context and then in your tab component set the listener to that parent navigation.
Check this for ref on how to pass data to your tab component : Passing params to tab navigator React Navigation 5
Alternatively for a functional component you can use this hook: useFocusEffect from #react-navigation/native
I have a modal in my React one page app. I made a class component of modal and render it in some place on the page. I need to call it from many other pages in app this modal.
My modal class:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
modalShow: false,
}
}
setModalShow(state){
this.setState({modalShow: state});
}
render(){...}
}
State of modal I keep in modal class. I want to show modal by changing state from outside Modal class. For examle something like
import Modal from './Modal'
...
Modal.setModalShow(true)
But Modal its a class, but I need to call setModalShow in instance of a class. And I don't understand how to do such a thing in React true way.
That kind of behavior requires passing down that function as a child property, Like this:
class Modal extends React.Component {
constructor(props) {
super(props);
}
setModalShow(state){
this.props.showModal(true);
}
render(){
...
}
}
And wherever you use the modal, there should be the showing state like:
class ModalWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
}
}
showModal(state){
this.setState({modalShow: state});
}
render(){
return (<Modal showModal={showModal} />);
}
}
While you can, as other suggests, pass a function that will allow you to register the method to another component, this is probably not the "React" way to open a modal outside of the component (meaning that there are ways that will, I believe, be clearer to write in React than this). I would suggest either making the open state a prop or maintain the modal state in a react context (or even pass the opening function as a context, if more things happen in it than simply opening the modal), which will allow you to avoid prop drilling.
You can read about the context API in the React docs: https://reactjs.org/docs/context.html. Note that it is far simpler to use as a hook in a functional component, but it works fine with classes as well.
you should pass the method setModalShow from Modal to one of its children, and then the child component would call this.props.setModalShow(true).
I desided to use Redux. In Motivation is described my problem.
Background:
I'm trying to figure out the best way to implement a Portal component that wraps React's native portal utility. The component would simply handle creating the portal's root element, safely inserting it into the DOM, rendering any of the component's children into it, and then safely removing it again from the DOM as the component is unmounting.
The Problem:
React strongly advises against side effects (like manipulating the DOM) outside of React's safe life cycle methods (componentDidMount, componentDidUpdate, etc...) since that has the potential to cause problems (memory leaks, stale nodes, etc...). In React's examples of how to use Portals, they mount the portal's root element into the DOM tree on componentDidMount, but that seems to be causing other problems.
Issue number 1:
If the Portal component 'portals' it's children into the created root element during it's render method, but waits until it's componentDidMount method fires before appending that root element into the DOM tree, then any of the portal's children which need access to the DOM during their own componentDidMount life cycle methods will have issues, since at that point in time they will be mounted to a detached node.
This issue was later addressed in React's docs which recommend setting a 'mounted' property to true on the Portal component's state once the Portal component had finished mounting and successfully appended the portals root element to the DOM tree. Then in the render, you could hold off on rendering any of the Portal's children until that mounted property was set to true, as this would guarantee that all of those children would be rendered into the actual DOM tree before their own respective componentDidMount life cycle methods would fire off. Great. But this leads us to...
Issue number 2:
If your Portal component holds off on rendering any of it's children until after it itself has mounted, then any of the componentDidMount life cycle methods of it's ancestors will also fire off prior to any of those children being mounted. So any of the Portal component's ancestors that need access to refs on any of those children during their own componentDidMount life cycle methods will have issues. I haven't figured out a good way to get around this one yet.
Question:
Is there a clean way to safely implement a portal component so that it's children will have access to the DOM during their componentDidMount life cycle methods, while also allowing the portal component's ancestors to have access to refs on those children during their own respective componentDidMount life cycle methods?
Reference Code:
import { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
export default class Portal extends Component {
static propTypes = {
/** This component uses Portals to dynamically render it's contents into
* whatever DOM Node contains the **id** supplied by this prop
* ('portal-root' by default). If a DOM Node cannot be found with the
* specified **id** then this component will create one and append it
* to the 'Document.Body'. */
rootId: PropTypes.string
};
static defaultProps = {
rootId: 'portal-root'
};
constructor(props) {
super(props);
this.state = { mounted: false };
this.portal = document.createElement('div');
}
componentDidMount() {
this.setRoot();
this.setState({ mounted: true });
}
componentDidUpdate( prevProps, prevState ) {
if( this.props.rootId !== prevProps.rootId ) this.setRoot();
}
componentWillUnmount() {
if( this.root ) {
this.root.removeChild(this.portal);
if( !this.root.hasChildNodes() ) this.root.parentNode.removeChild(this.root);
}
}
render() {
this.portal.className = this.props.className ? `${this.props.className} Portal` : 'Portal';
return this.state.mounted && ReactDOM.createPortal(
this.props.children,
this.portal,
);
}
setRoot = () => {
this.prevRoot = this.root;
this.root = document.getElementById(this.props.rootId);
if(!this.root) {
this.root = document.createElement('main');
this.root.id = this.props.rootId;
document.body.appendChild(this.root);
}
this.root.appendChild(this.portal);
if( this.prevRoot && !this.prevRoot.hasChildNodes() ) {
this.prevRoot.parentNode.removeChild(this.prevRoot);
}
}
}
The constructor is a valid lifecycle method in which you can perform side effects. There's no reason you can't create/attach the root element in the constructor:
class Portal extends Component {
constructor(props) {
super();
const root = document.findElementById(props.rootId);
this.portal = document.createElement('div');
root.appendChild(portal);
}
componentWillUnmount() {
this.portal.parent.removeChild(this.portal);
}
render() {
ReactDOM.createPortal(this.props.children, this.portal);
}
// TODO: add your logic to support changing rootId if you *really* need it
}
Edit: Check out the git repository for a minmal example: https://github.com/maximilianschmitt/blind-lifecycle
I have a component RequireUser that tries to ensure that the user is logged in and will otherwise not render its children. Its parent component, App, should know if a user is required and render a login form if needed.
The problem is, that the App component mounts AFTER the RequireUser component in a tree like this:
App
RequireUser
SomeOtherComponent
In RequireUser's componentDidMount I am triggering an action requireLogin that sets the UserStore's loginRequired variable to true.
This does not update the parent component (App) because it has not yet been mounted and can therefor not register changes to the store.
class RequireUser extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
if (!this.state.requireUser) {
UserActions.requireUser();
// using setTimeout will work:
// setTimeout(() => UserActions.requireUser());
}
}
componentWillUnmount() {
this.unlisten();
}
render() {
if (this.state.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
class App extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser />
</div>
);
}
}
Output:
User required? false
I have required your user
If I use setTimeout in RequireUser, App receives the state changes and renders, but only after a flicker:
User required? true
I have required your user
I have the feeling what I am doing is an anti-pattern and I would be grateful for suggestions of a more elegant solution than flickering with setTimeout. Thanks!
My suggested answer is to add this to the App component:
componentDidMount() {
// setup listener for subsequent changes
alt.stores.UserStore.listen(this.onChange);
// grab the current state now that we're mounted
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
There is no way to avoid the double render. Your RequireUser component already performs two renders.
Initial render of RequireUser
componentDidMount() callback
an action is dispatched
UserStore receives the dispatched action and updates its state
change notification is emitted
RequireUser sets state based on the state change
Second render of RequireUser
But your codebase is still considered Flux, and indeed follows the pattern intended for React apps. Essentially, you have a loading state... a state where we don't actually know if we need to require a user or not. Depending on what UserActions.requireUser() does, this may or may not be desired.
You might consider a refactor
You can fix the double-render if you rewrite RequireUser as a view-only component. This means no listeners nor setting state internally. This component simply renders elements based on the props passed in. This is literally all your RequireUser component would be:
class RequireUser extends React.Component {
render() {
if (this.props.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
You will then make your App component a controller-view. The listener is added here, and any changes to state are propagated downward by props. Now we can setup in the componentWillMount callback. This gives us the single render behavior.
class App extends React.Component {
(other lifecycle methods)
componentWillMount() {
if (!this.state.requireUser) {
UserActions.requireUser();
}
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
componentDidMount() {
(same as above)
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser requireUser={this.state.requireUser} />
</div>
);
}
}
Flux architecture and controller-views/views: https://facebook.github.io/flux/docs/overview.html#views-and-controller-views
Your components each only gets the states from your Store once - only during the construction of each components. This means that the states in your components will NOT be in sync with the states in the store
You need to set up a store listeners on your components upon mounting in order to retrieve a trigger from the store and the most up-to-date states. Use setState() to update the states inside the component so render() will be called again to render the up-to-date states
What about putting the store listener in the constructor? That worked for me.