Question
When I try to call props.history.push('/home') from React-Router 4 I receive the error below:
index.js:2177 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the Formik component.
__stack_frame_overlay_proxy_console__ @ index.js:2177
printWarning @ warning.js:33
warning @ warning.js:57
warnAboutUpdateOnUnmounted @ react-dom.development.js:9766
scheduleWorkImpl @ react-dom.development.js:10737
scheduleWork @ react-dom.development.js:10689
enqueueSetState @ react-dom.development.js:6212
./node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:237
Formik._this.setSubmitting @ formik.es6.js:2959
_callee$ @ LoginContainer.js:88
tryCatch @ runtime.js:62
invoke @ runtime.js:296
prototype.(anonymous function) @ runtime.js:114
step @ Register.js:101
(anonymous) @ Register.js:101
Promise resolved (async)
step @ Register.js:101
(anonymous) @ Register.js:101
(anonymous) @ Register.js:101
login @ LoginContainer.js:47
handleSubmit @ LoginContainer.js:30
C._this.handleSubmit @ formik.es6.js:2912
Formik._this.executeSubmit @ formik.es6.js:3079
(anonymous) @ formik.es6.js:2969
Promise resolved (async)
Formik._this.runValidationSchema @ formik.es6.js:2966
Formik._this.submitForm @ formik.es6.js:3072
Formik._this.handleSubmit @ formik.es6.js:3044
callCallback @ react-dom.development.js:542
invokeGuardedCallbackDev @ react-dom.development.js:581
invokeGuardedCallback @ react-dom.development.js:438
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:452
executeDispatch @ react-dom.development.js:836
executeDispatchesInOrder @ react-dom.development.js:858
executeDispatchesAndRelease @ react-dom.development.js:956
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:967
forEachAccumulated @ react-dom.development.js:935
processEventQueue @ react-dom.development.js:1112
runEventQueueInBatch @ react-dom.development.js:3607
handleTopLevel @ react-dom.development.js:3616
handleTopLevelImpl @ react-dom.development.js:3347
batchedUpdates @ react-dom.development.js:11082
batchedUpdates @ react-dom.development.js:2330
dispatchEvent @ react-dom.development.js:3421
Ideally I want to call react-router from within my login handler, but I've not been able to work out a way to do this.
N/A
Here is my App.js
import React, { Component } from 'react'
import {
Switch,
Route
} from 'react-router-dom'
import Authentication from './Authentication'
import Home from './Home'
class App extends Component {
render () {
return (
<div className="App h-100">
<Switch>
<Route path="/home" component={Home} />
<Route path="/" component={Authentication} />
</Switch>
</div>
)
}
}
export default App
The Authentication component is very simple, it just wraps around my LoginContainer:
import React from 'react'
import {
Switch,
Route,
Redirect
} from 'react-router-dom'
import {
Col,
Container,
Row,
Jumbotron
} from 'reactstrap'
import LoginContainer from '../containers/LoginContainer'
import RegisterContainer from '../containers/RegisterContainer'
const Authentication = () => (
<Jumbotron fluid className="h-100 mb-0 align-items-center d-flex ts-jumbotron">
<Container>
<Row>
<Col md={8} lg={6} className="mx-auto">
<h1 className="text-white text-center">TrackSuite</h1>
<Switch>
<Route path="/login" component={LoginContainer} />
<Route path="/register" component={RegisterContainer} />
<Redirect to="/login" />
</Switch>
</Col>
</Row>
</Container>
</Jumbotron>
)
export default Authentication
My LoginContainer is a Container component that I wrap around my Login component:
import { withFormik } from 'formik'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { withRouter } from 'react-router-dom'
import Yup from 'yup'
import AuthenticationService from '../services/AuthenticationService'
import Login from '../components/Login'
import { setToken } from '../actions'
const mapDispatchToProps = dispatch => {
return {
setToken: token => dispatch(setToken(token))
}
}
const LoginContainer = compose(
withRouter,
connect(null, mapDispatchToProps),
withFormik({
mapPropsToValues ({ email, password }) {
return {
email: '',
password: ''
}
},
/**
* @todo Make password requirements identical to API requirements.
*/
handleSubmit (values, props) {
login({
email: values.email,
password: values.password
}, { ...props })
},
validationSchema: Yup.object().shape({
email: Yup
.string()
.email('Email address is not valid.')
.required('Email address is required.'),
password: Yup
.string()
.required('Password is required.')
})
})
)(Login)
const login = async (credentials, { setSubmitting, setErrors, setStatus, props }) => {
// console.log('history', history)
console.log('props: ', props)
// const { history } = props
try {
setSubmitting(true)
const res = await AuthenticationService.login({
email: credentials.email,
password: credentials.password
})
if (res.status === 201) {
setToken(res.data.token)
setStatus({
message: 'Successfully logged in! Redirecting...'
})
props.history.push('/home')
}
} catch (error) {
console.log('error: ', error)
if (error.response.data.error) {
const errKeys = Object.keys(error.response.data.error)
switch (errKeys[0]) {
case 'email':
setErrors({
email: error.response.data.error[errKeys[0]]
})
break
case 'password':
setErrors({
password: error.response.data.error[errKeys[0]]
})
break
default:
setStatus({
error: error.response.data.error
})
}
}
} finally {
setSubmitting(false)
}
}
export default LoginContainer
And lastly my Login component is a simple presentational component:
import {
Alert,
Button,
Card,
CardBody,
CardText,
CardTitle,
Row,
Col,
Form,
FormGroup,
FormFeedback,
Input
} from 'reactstrap'
import React from 'react'
import { NavLink } from 'react-router-dom'
const Login = (props) => {
const {
values,
errors,
status,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting
} = props
return (
<Card className="Login">
<CardBody>
<CardTitle className="text-center">Login</CardTitle>
<Form onSubmit={handleSubmit}>
<Row>
<Col xs={10} className="mx-auto">
{status && status.error ? (
<Alert color="danger">{status.error}</Alert>
) : (
status && status.message && <Alert>{status.message}</Alert>
)}
<FormGroup>
<Input
autoFocus
type="email"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
id="email"
className={touched.email && ((status && status.email) || errors.email) ? 'is-invalid' : null}
placeholder="Email"
/>
{status && status.email ? (
<FormFeedback>{status.email}</FormFeedback>
) : (
errors.email && touched.email && <FormFeedback>{errors.email}</FormFeedback>
)}
</FormGroup>
<FormGroup>
<Input
type="password"
name="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
id="password"
className={touched.password && ((status && status.password) || errors.password) ? 'is-invalid' : null}
placeholder="Password"
/>
{status && status.password ? (
<FormFeedback>{status.password}</FormFeedback>
) : (
errors.password && touched.password && <FormFeedback>{errors.password}</FormFeedback>
)}
</FormGroup>
<Button
color="success"
className="d-block mx-auto"
type="submit"
disabled={isSubmitting}>
Sign In
</Button>
</Col>
</Row>
</Form>
<CardText className="text-right">Don't have an account? You'll need to <NavLink to="/register">register</NavLink>.</CardText>
</CardBody>
</Card>
)
}
export default Login
Is there a recommended way to do this? I tried to check https://github.com/jaredpalmer/formik/issues/299 but it does not recommend how to access the react-router props, only how to inject them. The props object seems to contain a history object, but when I try to use it, I receive the error above.
Any help would be appreciate tremendously.
The source is available at https://github.com/JheeBz/traqsuite/tree/master/client if that helps, but obviously the code above relating to this issue is uncommitted.
I came across the same problem. it has to do with setSubmitting function which force changes the state.
`
try {
setSubmitting(true) <===========================REMOVE THIS
const res = await AuthenticationService.login({
email: credentials.email,
password: credentials.password
})
`
I have the same problem. Am not using setSubmitting(true).
UPDATE:
For me the problem was using resetForm(); after the props.history.push.
This is what worked for me:
<WrappedFormComp {...props} />
Now that you passed history via prop drilling, you can write:
const formEnhancer = withFormik({
[...]
handleSubmit(values, { props }) {
props.history.push('/some/funky/path')
}
})
For those facing the same problem... Here is what's worked for me https://github.com/jaredpalmer/formik/issues/299#issuecomment-451758739
Most helpful comment
This is what worked for me:
Now that you passed history via prop drilling, you can write: