How does firebase's onAuthStateChanged work in ComponentDidMount lifecycle in reactJS - reactjs

Can anyone explain we how this onAuthStateChanged function is working inside componentDidMount. I know this lifecycle hook get executed only once when the component is mounted. So how does the function inside gets executed?
What I assume is it's like callback function which keeps on running in event loop as gets fired when state changed like addEventlistner in JS.
componentDidMount() {
console.log("Context Mounted");
firebaseapp.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ currentUser: user });
console.log("User Signed IN ");
} else {
console.log("THERE IS NO USER");
}
});
}

You pretty much got the gist of it: after your register your onAuthStateChanged callback with Firebase it will be called:
"immediately" with the current authentication state
whenever that authentication state changes
Since you call setState to put the user into the state, this then triggers a rerender of the UI whenever one of the above events occurs.
This continues until the app exits or until your unregister the listener, which you should typically do in the componentWillUnmount method of the same component.

Related

How do I use async await when a component leaves view

How do I use Async Await ?
When this.props.onChangeStep1() is called, the component where the view is rendered is replaced by another component.
If the component has been swapped would Async Await still work ?
s3.upload(params, function(err, data) {
this.props.onChangeStep1(); //<----- After s3.upload I want to call this first
this.setState( //<----But I want this to run in the background
// even though the component is not in view
{
myState: "data-from-s3Upload"
});
}.bind(this)
);
If a callback is set to be executed later and the component that initiated the asynchronous call is unmounted, the callback will be executed regardless. If the callback attempts to act on the unmounted component (changing its state for instance) it will be considered as a memory leak and React will notify you with an error message in the console.
this.props.onChangeStep1(e) // <-- Forward data and Process logic on App.js / Parent Component

Why useEffect doesn't fire on every render?

My component has two Rect.useEffect hook
const Example = ({ user }) => {
React.useEffect(() => {
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};
I update this component using mobx. It is re-rendered correctly. But "Why not me?" is printed only once.
As per official docs
By default, effects run after every completed render
This means console.log("Why not me?"); too should run every time prop user is updated. But it doesn't. The console output is this
What's the reason behind this apparent inconsistency?
My complete code can be viewed here
In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react lifecycle.
This behaviour happens because you have user count as a observable variable.
According to the mobx-react docs
Observer is a React component, which applies observer to an anonymous
region in your component. It takes as children a single, argumentless
function which should return exactly one React component. The
rendering in the function will be tracked and automatically
re-rendered when needed.
and mobx docs
When autorun is used, the provided function will always be triggered
once immediately and then again each time one of its dependencies
changes.
You can confirm this behvaior by logging directly inside the functional component and you will observer that the component is only rendered once
EDIT:
To answer your question
If I change useEffect to this
React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));
basically remove anonymous function from useEffect and just pass
autorun directly, then it is run only once. Why is it so? What's the
difference?
The difference is that autorun returns a disposer function which when run will dispose of the autorun and would not longer execute it.
From the docs:
The return value from autorun is a disposer function, which can be
used to dispose of the autorun when you no longer need it.
Now what happens is that since useEffect executes the callback provided to it when it runs, the callback executed is the disposer function returned by autorun which essentially cancels your autorun.
It looks like your component doesn't rerender. autorun receives a callback and might call it independently from render.
Example component rerenders only when its parent rerenders or when its props change.
Use this code to observe what's really happening:
const Example = ({ user }) => {
console.log('render');
React.useEffect(() => {
console.log('useEffect autorun');
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};

Redirecting with React-router if user is not authenticated

I'm building a React application with React-router and redux, and i have a profile page I want to show at /profile only if the user is authenticated. In my App component's componentWillMount method I am fetching the current user with fetchUser() which uses axios to send a GET request to my Express API. My fetchUser() returns null by default, and returns false or the user object when the request finishes. In my Profile component, I am trying to switch over the user (null, false or user which should be truthy). The problem I am facing is that my switch case gets called before the fetchUser() request comes through, and so the user is null. The following code gets the job done, but chrome throws an error in the console:
render() {
if (this.props.auth === false) this.props.history.push('/');
return (
<div className="profile-page">
//Profile page goes here
</div>
)
}
Error thrown by chrome:
Warning: 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`.
Just to clarify: The code works perfectly, but chrome still complains.
I realise this means I need to move my logic from from the render method into componentWillMount or componentDidMount. The problem is that when those lifecycle methods are called, the user object is null, and so that won't work (I've tried). Does anyone have any ideas? Thanks in advance!
Adding the logic into the componentDidUpdate() lifecycle method solved the issue:
componentDidUpdate() {
if (this.props.auth === false) return this.props.history.push('/');
}
render() {
return (
<div className="profile-page">
<h4>Profile page</h4>
<ProfileForm />
</div>
)
}
}
As you have observed correctly that render method should not contain any logic that is switching the state/rerouting.
If your fetchUser function is asyc then you should only render the component based on the response of fetchUser.
componentWillMount(){
this.setState({loading:true})
fetchUser().then( () =>{this.setState({auht:true,loading:false)})
.catch( ()=>{this.setState({auth:false,loading:false})})
}
now based on this.state.loading you may take some decisions in the render.
Hope this works.

Listening when state change via redux - ReactJS

I'm new to reactjs framework and I have a bit of confusion if what is the right way in listening if state change after an API call.
use then and catch after calling an action via componentDidUpdate:
componentDidMount(){
this.props.getHero(this.props.params.id).then((result) => {
this.props.initialize({
"name":result.name,
"description": result.description
});
})
.catch(error => {
});
}
or via componentWillUpdate
// Call the getHero action (API)
componentDidMount(){
this.props.getHero(this.props.params.id);
}
// Then listen if the state change via `mapToStateProps`
componentDidMount(){
this.props.getHero(this.props.params.id);
}
componentWillUpdate(){
this.props.initialize({
"name":this.props.heroes.name,
"description": this.props.heroes.description
});
}
Listen for changes in componentWillUpdate
componentWillUpdate() is invoked immediately before rendering when new props or state are being received. Use this as an opportunity to perform preparation before an update occurs. This method is not called for the initial render.
Load data in componentDidMount
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.

React componentDidMount() is fired on unMounted component

A simple react component that invokes a promise from the data store on componentDidMount is throwing a warning:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LocationNameView component.
I threw a few debug console.log to see if this.isMounted() is true/false, and inside componentDidMount this.isMounted() will return false the first time then again and it will be true. I'm not sure if the docs are clear or the name of componentDidMount is skewing my reasoning here but it seems like this method should only be invoked if the component is actually mounted.
enter link description here
componentDidMount: function() {
var self = this;
// make the request to the backend and replace the loading location text
Models.Location.find(this.props.location)
.then(function(location) {
console.log(self.isMounted()); // <--- shows false then true
self.setState({ location : location });
});
},
componentDidMount is invoked when the underlying DOM representation is built, but, not yet mounted to the actual DOM.
The reason the warning is being displayed about setting state on an unMounted component is because the aSync callback in the above example can return before the component is actually mounted to the DOM tree in the client/browser.
The lesson here is if you're using an aSync callback that sets state in your component on componentWillMount or componentDidMount, first use the safety catch isMounted() before proceeding to setting state, ie:
componentDidMount() {
let self = this;
PromiseMethod().then(function aSyncCallback(data) {
if ( self.isMounted() ) {
self.setState({...data});
}
});
}
React Component Lifecycle reference

Resources