I'm trying to wrap my head around redux, react-redux and redux-form.
I have setup a store and added the reducer from redux-form. My form component looks like this:
LoginForm
import React, {Component, PropTypes} from 'react'
import { reduxForm } from 'redux-form'
import { login } from '../../actions/authActions'
const fields = ['username', 'password'];
class LoginForm extends Component {
onSubmit (formData, dispatch) {
dispatch(login(formData))
}
render() {
const {
fields: { username, password },
handleSubmit,
submitting
} = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<input type="username" placeholder="Username / Email address" {...username} />
<input type="password" placeholder="Password" {...password} />
<input type="submit" disabled={submitting} value="Login" />
</form>
)
}
}
LoginForm.propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
}
export default reduxForm({
form: 'login',
fields
})(LoginForm)
This works as expected, in redux DevTools I can see how the store is updated on form input and on submitting the form the login
action creator dispatches the login actions.
I added the redux-thunk middleware to the store and setup the action creator(s) for logging in as described in the redux docs for Async Actions:
authActions.js
import ApiClient from '../apiClient'
const apiClient = new ApiClient()
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
function requestLogin(credentials) {
return {
type: LOGIN_REQUEST,
credentials
}
}
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
function loginSuccess(authToken) {
return {
type: LOGIN_SUCCESS,
authToken
}
}
export const LOGIN_FAILURE = 'LOGIN_FAILURE'
function loginFailure(error) {
return {
type: LOGIN_FAILURE,
error
}
}
// thunk action creator returns a function
export function login(credentials) {
return dispatch => {
// update app state: requesting login
dispatch(requestLogin(credentials))
// try to log in
apiClient.login(credentials)
.then(authToken => dispatch(loginSuccess(authToken)))
.catch(error => dispatch(loginFailure(error)))
}
}
Again, in redux DevTools I can see that this works as expected. When dispatch(login(formData))
is called in onSubmit
in the LoginForm, first the LOGIN_REQUEST
action is dispatched, followed by LOGIN_SUCCESS
or LOGIN_FAILURE
. LOGIN_REQUEST
will add a property state.auth.pending = true
to the store, LOGIN_SUCCESS
and LOGIN_FAILURE
will remove this property. (I know this might me something to use reselect for, but for now I want to keep it simple.
Now, in the redux-form docs I read that I can return a promise from onSubmit
to update the form state (submitting
, error
). But I'm not sure what's the correct way to do this. dispatch(login(formData))
returns undefined
.
I could exchange the state.auth.pending
flag in the store with a variable like state.auth.status
with the values requested, success and failure (and again, I could probably use reselect or something alike for this).
I could then subscribe to the store in onSubmit
and handle changes to state.auth.status
like this:
// ...
class LoginForm extends Component {
constructor (props) {
super(props)
this.onSubmit = this.onSubmit.bind(this)
}
onSubmit (formData, dispatch) {
const { store } = this.context
return new Promise((resolve, reject) => {
const unsubscribe = store.subscribe(() => {
const state = store.getState()
const status = state.auth.status
if (status === 'success' || status === 'failure') {
unsubscribe()
status === 'success' ? resolve() : reject(state.auth.error)
}
})
dispatch(login(formData))
}).bind(this)
}
// ...
}
// ...
LoginForm.contextTypes = {
store: PropTypes.object.isRequired
}
// ...
However, this solution doesn't feel good and I'm not sure if it will always work as expected when the app grows and more actions might be dispatched from other sources.
Another solution I have seen is moving the api call (which returns a promise) to onSubmit
, but I would like to keep it seperated from the React component.
Any advice on this?
dispatch(login(formData))
returnsundefined
Based on the docs for redux-thunk:
Any return value from the inner function will be available as the return value of
dispatch
itself.
So, you'd want something like
// thunk action creator returns a function
export function login(credentials) {
return dispatch => {
// update app state: requesting login
dispatch(requestLogin(credentials))
// try to log in
apiClient.login(credentials)
.then(authToken => dispatch(loginSuccess(authToken)))
.catch(error => dispatch(loginFailure(error)))
return promiseOfSomeSort;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With