Here is the code I used in the function component
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
setIsLoggedIn(!!user);
});
return () => {
unsubscribe();
};
});
Here is my try on the class component.
componentDidUpdate(
prevProps: LessonDrawerProps,
prevState: LessonDrawerState
) {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
isLoggedIn: true,
});
}
});
}
The class onAuthStateChanged detection is not working properly as in the function component.
How can this be done?
The componentDidUpdate fires when the component was updated, which in your case seems to never happen (or at least not in time). You actually want to start listening for auth state changes as soon as the component is added to the widget tree.
You'll want to put the onAutStateChanged in the componentDidMount instead of in componentDidUpdate.
Related
I want to ask if my approach to using callback functions below is correct. I feel like the callback function I switched from Class to Hooks is not working as expected.
in Class
componentWillReceiveProps(nextProps) {
if (typeof nextProps.checked != 'undefined' && nextProps.checked != this.state.checked) {
this.setState({checked: nextProps.checked});
}
}
onClick = () => {
var self=this;
this.setState({checked: !this.state.checked}, function () {
self.props.onChange(self.state.checked);
});
};
in Hooks
const { checked,onChange } = props;
const [state, setState] = useState({
checked: !!checked
});
useEffect(() => {
setState({ ...state, checked: checked });
}, [checked]);
useEffect(() => {
onChange(state.checked)
}, [state.checked]);
const onClick = () => {
setState({ checked: !state.checked });
}
if the upper props.onChange handle the checked status and pass it down to this component, then you dont need to handle the state internally ?
I checked your code and I feel this is working as expected.
You can check here:
https://codesandbox.io/s/misty-firefly-2l4tt?file=/src/App.js
But, seeing your code, I feel you want to pass the status of the checked/unchecked field in the child component to the parent component. In that case, you can directly call onChange props from the child component onClick/onChange event and manage the state independently in the parent component.
Probably, the code of the parent component will help me in better understanding if you could share.
My code adds a new item in the firebase databse when i click a button, then i want the list of objects in my page to automatically update, because i don't want to manualy reload the page. So i came up with this code
constructor(props){
super(props);
this.state = {
groups: [],
code:'',
name:'',
update:true
}
}
async fetchGroups (id){
fetchGroupsFirebase(id).then((res) => {this.setState({groups:res})})
};
async componentDidUpdate(prevProps,prevState){
if(this.state.update !== prevState.update){
await this.fetchGroups(this.props.user.id);
}
}
handleCreateSubmit = async event => {
event.preventDefault();
const{name} = this.state;
try{
firestore.collection("groups").add({
title:name,
owner:this.props.user.id
})
.then((ref) => {
firestore.collection("user-group").add({
idGroup:ref.id,
idUser:this.props.user.id
});
});
this.setState({update: !this.state.update});
}catch(error){
console.error(error);
}
What i was thinking, after i add the new item in firebase, i change the state.update variable, which triggers componentDidUpdate, which calls the new fetching.
I tried calling the fetchGroups function in the submit function, but that didn't work either.
What am i doing wrong and how could i fix it?
ComponentDidUpdate will not be called on initial render. You can either additionally use componentDidMount or replace the class component with a functional component and use the hook useEffect instead.
Regarding useEffect, this could be your effect:
useEffect(() => {
await this.fetchGroups(this.props.user.id);
}, [update]);
Since you can't use useEffect in class components so you would need to rewrite it as functional and replace your this.state with useState.
A callback function sets the component state. But sometimes subscription which supplies the data need to end. Because callback is executed asynchronously, it's not aware if the subscription ends just after making the service call (which executes the callback function).
Then I see following error in the console:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
Is there a way to access the component state, even if I am in the callback function?
This would be the steps:
subscribe with parameters
unsubscribe
component is unmounted
subscribed service executes the callback function
callback functio sets state in an unmounted component and it gives error above
You can use a ref like this:
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
Then in your callback you can check if mounted.current === false and avoid setting the state
Here is some pseudo code how you can use useEffect to see if a component is mounted.
It uses useEffect to listen to someService when it receives a message it checks if the component is mounted (cleanup function is also called when component unmounts) and if it is it uses setServiceMessage that was created by useState to set messages received by the service:
import { useState, useEffect } from 'react';
import someService from 'some-service';
export default props => {
const userId = props.userId;
const [serviceMessage, setServiceMessage] = useState([]);
useEffect(
() => {
const mounted = { current: true };
someService.listen(
//listen to messages for this user
userId,
//callback when message is received
message => {
//only set message when component is mounted
if (mounted.current) {
setServiceMessage(serviceMessage.concat(message));
}
});
//returning cleanup function
return () => {
//do not listen to the service anymore
someService.stopListen(userId);
//set mounted to false if userId changed then mounted
// will immediately be set to true again and someService
// will listen to another user's messages but if the
// component is unmounted then mounted.current will
// continue to be false
mounted.current = false;
};
},//<-- the function passed to useEffects
//the function passed to useEffect will be called
//every time props.userId changes, you can pass multiple
//values here like [userId,otherValue] and then the function
//will be called whenever one of the values changes
//note that when this is an object then {hi:1} is not {hi:1}
//referential equality is checked so create this with memoization
//if this is an object created by mapStateToProps or a function
[userId]
);
};
This hook (insired from Mohamed answer) solves the problem in a more elegant maner:
function useMounted() {
const mounted = useMemo(() => ({ current: true }), []);
useEffect(() => {
return () => { mounted.current = false}
}, [mounted]);
return mounted;
}
(Updated to use useMemo instead of useRef for readability).
You can return a function from useEffect, which will be fired when a functional component unmount.
Please read this
import React, { useEffect } from 'react';
const ComponentExample = () => {
useEffect(() => {
// Anything in here is fired on component mount.
return () => {
// Anything in here is fired on component unmount.
}
}, [])
}
I found the accepted answer to this question hard to read, and React provides their own documentation on just this question. Their example is:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
I've created a component I call <Fade> that will fade in/out any children its given. Note that it relies on bootstrap's .fade and .show classes, though these could easily be implemented without bootstrap.
const Fade: React.FC<{}> = ({ children }) => {
const [ className, setClassName ] = useState('fade')
const [ newChildren, setNewChildren ] = useState(children)
useEffect(() => {
setClassName('fade')
const timerId = setTimeout(() => {
setClassName('fade show')
setNewChildren(children)
}, TIMEOUT_DURATION)
return () => {
clearTimeout(timerId)
}
}, [children])
return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}
It all boils down to one rule: unsubscribe from your asynchronous tasks in the cleanup function returned from useEffect.
lately i'm facing a tough issue with React/Redux (Thunk): i've created my Store with Action and Reducer properly, in my Component i trigger the Async function in componentDidMount method in order to update the state, But the State doesn't seems to be changing, although it does changed in componentDidUpdate and mapStateToProps functions ! Why ? Here is my code :
export const getAllInterventions = () => {
return dispatch => {
dispatch(getAllDataStart());
axios.get('/interventions.json')
.then(res => {
dispatch(getAllDataSuccess(res.data));
})
.catch(err => {
dispatch(getAllDataFail(err));
});
};
My reducer :
case actionTypes.GET_ALL_INTERVENTIONS_SUCCESS:
return {
...state,
interventions: interventions: action.interventions
};
My Component:
componentDidMount() {
this.props.getAllInterventions();
console.log('DidMount: ', this.props.inter); /*Empty Array Or Undefined */
}
const mapStateToProps = state => {
console.log('mapStateToProps', state);
return {
inter: state.interventions,
error: state.error
};
Your action and state changes are both asynchronous, doing console.log right after will always return before those changes are actually completed.
Your state is being updated, which is why it works when you console.log in the mapStateToProps.
I suggest setting up redux-devtools, you'll be able to easily track your actions/state.
I hope the code can be usefully for you
componentWillReceiveProps(nextProps){
console.log(nextProps.inter)
}
I'm new to Mobx and reactjs in general, I have knowledge in Redux and react native, and in Redux when I used to call an action and the props get updated, the componentDidUpdate life cycle method is triggered.
The scenario I'm having now is login. so the user fills the form, clicks submit, and the submit calls a Mobx action (asynchronous), and when the server responds, an observable is updated, and then it navigates to a main page (navigation happens in the component).
Here is my store code.
import { autorun, observable, action, runInAction, computed, useStrict } from 'mobx';
useStrict(true);
class LoginStore {
#observable authenticated = false;
#observable token = '';
#computed get isAuthenticated() { return this.authenticated; }
#action login = async (credentials) => {
const res = await window.swaggerClient.Auth.login(credentials)l
// checking response for erros
runInAction(() => {
this.token = res.obj.token;
this.authenticated = true;
});
}
}
const store = new LoginStore();
export default store;
export { LoginStore };
and this handler is in my component.
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.store.login(values);
}
});
}
componentDidUpdate() {
if (this.props.store.isAuthenticated) {
const cookies = new Cookies();
cookies.set('_cookie_name', this.props.store.token);
this.props.history.push('main');
}
}
It's not the ideal code, I'm just experimenting, but I'm not quite getting it.
Also, if I use the computed value (isAuthenticated) in the render life cycle method, the componentDidUpdate is triggered, but if I didn't use it in the render method, the componentDidUpdate is not triggered.
For example, if I do this
render() {
if (this.props.store.isAuthenticated) return null
// .... rest of the code here
}
the above will trigger the componentDidUpdate.
Am I missing something? is there a better way to do it with Mobx?
Thanks
Observer component will only react to observables referred in its render method. MobX documentation covers this.
I would recommend you to use when to solve the problem.
componentDidMount() {
when(
() => this.props.store.isAuthenticated,
() => {
// put your navigation logic here
}
);
}
Mobx suggest the following solutions for such a case:
when
autorun
reaction
See the examples below, and don't forget to dispose:
componentDidMount() {
this.disposers.push(
// option with autorun:
autorun(() => {
this.runYourLogicHere();
})
// another option with reaction:
reaction(
() => this.yourModelOrProps.something,
() => {
this.runYourLogicHere();
}
)
)
}
...
componentWillUnmount() {
this.disposers.forEach(disposer => {
disposer();
});
}
And see the answer of #Dominik Serafin in parallel thread as a reference.