I'm trying to load a modal 2 seconds after the page has been loaded. I tried setting the state on componentDidUpdate but I keep on getting active: undefined The active props determines the visibility of the modal on the page. I tried toggling it to true on browser on the react tool and my modal shows up. I'm just not sure how to load to it 2 seconds after the page loads up.
state = { show: true };
showModal = () => {
this.setState({ show: true });
};
closeModal = () => {
this.setState({ show: false });
};
render() {
const { showModal } = this.state;
return (
<React.Fragment>
....
<Modal.ModalAnimator active={showModal} onClose={this.closeModal}>
<Modal.ModalWithCross
onClose={this.closeModal}
>
<h3>Are you interested in any other Additions?</h3>
<Section>
<p>Hit “notify concierge” and we’ll be in touch shortly.</p>
</Section>
</Modal.ModalWithCross>
</Modal.ModalAnimator>
</React.Fragment>
)
}
When destructuring the state, you write showModal instead of the actual state field name show. So your first lines in the render function should read:
render() {
const { show } = this.state;
return (
<React.Fragment>
...
<Modal.ModalAnimator active={show} onClose={this.closeModal}>
...
Please try this.
state = { show: true };
closeModal = () => {
this.setState({ show: false });
};
componentDidMount() {
setTimeout(() => {
this.setState({ show: true });
}, 2000);
}
render() {
const { showModal } = this.state;
return (
let model = null;
if (this.state.show) {
let model = (
<Modal.ModalAnimator active={showModal} onClose={this.closeModal}>
<Modal.ModalWithCross
onClose={this.closeModal}
>
<h3>Are you interested in any other Additions?</h3>
<Section>
<p>Hit “notify concierge” and we’ll be in touch shortly.</p>
</Section>
</Modal.ModalWithCross>
</Modal.ModalAnimator>
)
}
<React.Fragment>
....
{model}
</React.Fragment>
)
}
I have a simple modal where I writte some data and submit to database. I'm trying to implement a simple loading on the button, so the user receives feedback. Unfortunately, this isn't working.
constructor(props) {
super(props);
this.state = {
isLoading: false,
show: false,
list: []
}
}
onSubmit = async event => {
ref.onSnapshot(async doc => {
if (doc.data().status === 'accepted') {
const list = await this.get(name, age, id);
this.setState(prevState => ({
isLoading: !prevState.isLoading, // this doesn't work
list: list,
show: false // state for a modal
}, () => console.log('loading on submit', this.state.isLoading)))
}
}
}
<button
onClick={this.onSubmit}
disabled={this.state.isLoading ? true : false}>
{this.state.isLoading ? 'Loading...' : 'OK'}
</button>
Thank you! :)
You want to set isLoading to true before you get your data, and then set isLoading to false when the asynchronous request is complete.
Example
function getList() {
return new Promise(function(resolve) {
setTimeout(() => resolve([1, 2, 3]), 1000);
});
}
class App extends React.Component {
state = {
isLoading: false,
show: false,
list: []
};
onSubmit = event => {
this.setState({ isLoading: true });
getList().then(list => {
this.setState({
isLoading: false,
list,
show: false
});
});
};
render() {
return (
<button
onClick={this.onSubmit}
disabled={this.state.isLoading}
>
{this.state.isLoading ? "Loading..." : "OK"}
</button>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
trigger loading true before await call like below code.
onSubmit = async event => {
ref.onSnapshot(async doc => {
if (doc.data().status === 'accepted') {
this.setState({
isLoading : true
})
const list = await this.get(name, age, id);
this.setState(prevState => ({
isLoading: false,
list: list,
show: false // state for a modal
}, () => console.log('loading on submit', this.state.isLoading)))
}
}
}
What I want is that if the user is already signed in, the app should skip the log in page for him and display the main page.
I tried the below code but it doesn't work. How to overcome this issue ?
componentWillMount() {
let user = firebase.auth().currentUser;
if (user != null) {
this.props.navigation.navigate('Drawer');
}
this.unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.navigation.navigate('Drawer');
}
});
}
Take a look at this hint, it should solve your problem
class App extends React.Component {
constructor() {
super();
this.state = {
loading: true,
authenticated: false,
};
}
componentWillMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ loading: false, authenticated: true });
} else {
this.setState({ loading: false, authenticated: false });
}
});
}
render() {
if (this.state.loading) return null; // Render loading/splash screen etc
if (!this.state.authenticated) {
return <Login />;
}
return <Home />;
}
}
I am trying to close a modal when a user presses outside of the Modal element. Somehow when Dismiss() is called, the state is still the same in the callback.
Why is this happening?
export default class Message extends React.Component {
constructor(props) {
super(props);
this.state = {
id: "",
show: false
};
}
componentDidMount() {
this.props.onRef(this);
}
Show(id) {
this.setState({
id: id,
show: true
});
}
Dismiss() {
this.setState({
id: '',
show: false
}, function (state) {
console.log(state) // undefined
});
}
render() {
if (this.state.show) {
return (
<Modal close={() => this.Dismiss()}>
<h1>{this.state.id}</h1>
</Modal>
);
} else {
return null
}
}
}
Not sure why there's a state argument in your callback, should just be
Dismiss() {
this.setState({
id: '',
show: false
}, function () {
console.log(this.state)
});
}
Yes, that is because this.setState function in React is async. And the new state is only available in the event queue
Here is what I mean:
this.setState({newAdded: "test"});
let youCannotGetIt = this.state.newAdded; // here is undefined, because this.setSate is async
I ma trying to call the backend api to get the user's profile on page load:
Given the following action:
export const GET_MY_PROFILE_START = 'GET_MY_PROFILE_START';
export const GET_MY_PROFILE_ERROR = 'GET_MY_PROFILE_ERROR';
export const GET_MY_PROFILE_SUCCESS = 'GET_MY_PROFILE_SUCCESS';
let a = 0;
export function getMyProfile() {
a = a+1;
window.console.log("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
window.console.log(a);
return dispatch => {
window.console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
dispatch(getMyProfileStart());
$.ajax({
type: 'GET',
url: getMyProfileURL,
contentType: "application/json",
dataType: 'json',
}).done(function(res){
if (!res.values || res.values.count > 0) {
dispatch(getMyProfileSuccess(res.data))
} else {
dispatch(getMyProfileError())
}
}).fail(function(error) {
dispatch(getMyProfileError())
})
}
}
function getMyProfileStart() {
return {
type: GET_MY_PROFILE_START,
}
}
function getMyProfileSuccess(profile) {
return {
type: GET_MY_PROFILE_SUCCESS,
profile: profile,
}
}
function getMyProfileError() {
return {
type: GET_MY_PROFILE_ERROR,
}
}
and following reducer:
import { SET_USER_PROFILE, CLEAR_USER_PROFILE, GET_MY_PROFILE_START, GET_MY_PROFILE_ERROR, GET_MY_PROFILE_SUCCESS } from '../serActions'
export default (state = {
loggedIn: false,
profiledLoading: false,
profile: {},
}, action) => {
switch (action.type) {
case SET_USER_PROFILE:
return {
loggedIn: true,
profiledLoading: false,
profile: {},
}
case CLEAR_USER_PROFILE:
return {
loggedIn: false,
profiledLoading: false,
profile: {},
}
case GET_MY_PROFILE_START:
return {
loggedIn: false,
profiledLoading: true,
profile: {},
}
case GET_MY_PROFILE_ERROR:
return {
loggedIn: true,
profiledLoaded: false,
profile: {},
}
case GET_MY_PROFILE_SUCCESS:
return {
loggedIn: true,
profiledLoading: false,
profile: action.profile,
}
default:
return state
}
}
and the following component:
class AvatarButton extends Component {
componentWillMount() {
window.console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
this.props.dispatch(getMyProfile())
}
render() {
//const { profile, toggleMenu, showHideMenu } = this.props
const { user } = this.props
window.console.log(user);
return (
<a className="user-button nav-button avatar-button"
onClick={user.toggleMenu}
onBlur={this.hideMenu.bind(this)}
>
<i className="glyphicon glyphicon-user"/>
</a>
)
}
hideMenu() {
this.props.user.showHideMenu(false)
}
}
AvatarButton.propTypes = {
toggleMenu: PropTypes.func.isRequired,
showHideMenu: PropTypes.func.isRequired,
}
function select(state) {
return {
user: state.user,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(AvatarButton)
This Component is used in:
class UserNavContainer extends Component {
constructor(props) {
super(props)
this.state = {
displayMenu: false,
}
}
render() {
const { dispatch, user, pathname } = this.props
if (!user.loggedIn)
return (
<LoginButton pathname={pathname}/>
)
return (
<div className="user-nav">
<AvatarButton
toggleMenu={this.toggleMenu.bind(this)}
showHideMenu={this.showHideMenu.bind(this)}
/>
<UserMenu
visible={this.state.displayMenu}
logoutHandler={this.logout.bind(this)}
hideMenu={this.showHideMenu.bind(this, false)}
/>
</div>
)
}
logout() {
window.location = "some url"
}
toggleMenu() {
this.showHideMenu(!this.state.displayMenu)
}
showHideMenu(show) {
this.setState({
displayMenu: show,
})
}
}
function select(state) {
return {
user: state.user,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(UserNavContainer)
and finally the top level component that is using UserNavContainer:
class AppHandler extends Component {
componentWillMount() {
authUser(this.authSuccess.bind(this), this.authFail.bind(this))
}
authSuccess() {
this.props.dispatch(login())
}
authFail() {
this.props.dispatch(logout())
}
render() {
window.console.log("renderrenderrenderrenderrenderrenderrenderrenderrenderrender");
return (
<div className="app-container">
<div className="top-nav row">
<div className="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
<BackButton
pathname={this.props.location.pathname}
/>
<UserNavContainer
pathname={this.props.location.pathname}
/>
<div className="header">
<img src="https://www.wewherego.com/img/logo/logo_wherego.png"/>
<h1>{this.props.pageHeader}</h1>
</div>
</div>
</div>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={1000}
transitionLeaveTimeout={1000}
>
{React.cloneElement(this.props.children, {
key: this.props.location.pathname,
})}
</ReactCSSTransitionGroup>
</div>
)
}
}
function select(state) {
return {
selectedCity: state.selectedCity,
pageHeader: state.pageHeader,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(AppHandler)
In the AppHandler, it is calling the following methods in the ComponentWillMount:
export function authUser(loginCb, logoutCb) {
const data = readCookie('something')
if (!data) {
logoutCb()
} else {
loginCb()
}
return
}
export function signoutUser() {
//clean up some stuff
}
When I am opening the page, I can only see 1 line of
renderrenderrenderrenderrenderrenderrenderrenderrenderrenderrender
but I see the log keeps printing:
UserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1057
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1058
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1059
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
So apparently the componentWillMount method, for some reason, is kept being called, and the a in getMyProfile() now goes to 1059(and still counting).
I have no idea why this would happen?
If you're dispatching actions in component lifecycle methods, you should do it in componentDidMount not willMount. It appears you're triggering an infinite loop blocking the component from mounting.
Try by wrapping your onClick function call as-
onClick={() => user.toggleMenu}
The real problem is that in the components, they are dispatching SET_USER_PROFILE event, which changes the loggedIn variable to true, but then the app dispatches GET_MY_PROFILE_START which changes the loggedIn variable to false. This in return triggers the new cycle and moves loggedIn to true, so the loop keeps going.
The right way to do it is that in the reducer, instead of setting variables, use object.assign to change the values of the variable. So it really should be:
export default (state = {
loggedIn: false,
profiledLoading: false,
profile: {},
}, action) => {
switch (action.type) {
case GET_MY_PROFILE_START:
return Object.assign({}, state, {
loggedIn: true,
profiledLoading: true,
})
case CLEAR_USER_PROFILE:
return Object.assign({}, state, {
loggedIn: false,
})
case GET_MY_PROFILE_ERROR:
return Object.assign({}, state, {
profiledLoaded: false,
profile: {},
})
case GET_MY_PROFILE_SUCCESS:
return Object.assign({}, state, {
profiledLoading: false,
profile: action.profile,
})
default:
return state
}
}