How can I access the field values in a parent component of a redux-form component?
I'm not sure if it's caused by typescript, but before I started using typescript, I was able to access the form values through mapStateToProps just like how I have it currently. I've been trying to figure out what was different to my previous implementation but the only difference would be the versions of the npm dependencies and the addition of typescript.
LoginPage.tsx
import LoginForm from 'components/Forms/LoginForm'
import Layout from 'components/Layout'
import { StatusCodes } from 'lib/enums/statusCodes'
import { storeAuthToken } from 'lib/helpers/auth'
import { NextPage } from 'next'
import Router from 'next/router'
import React from 'react'
import { connect, DispatchProp } from 'react-redux'
import { FormInstance } from 'redux-form'
interface IProps {
login: FormInstance<IFormData, IFormProps>
}
interface IState {
errorMessage?: string,
processing: boolean
}
interface IRootState {
form: IProps
}
export interface IFormData {
username?: string,
password?: string
}
export interface IFormProps {
contactId?: string,
errorMessage?: string,
fieldValues: Partial<IFormData>,
processing: boolean
}
class LoginPage extends React.Component<NextPage & DispatchProp & IProps, IState> {
state = {
errorMessage: undefined,
processing: false
}
setErrorMessage = (message: string) => {
this.setState({
errorMessage: message,
processing: false
})
}
handleSubmit = async (values: IFormData) => {
if (values && values.username && values.password) {
this.setState({
errorMessage: undefined,
processing: true
})
try {
const { dispatch } = this.props
await storeAuthToken(dispatch, values.username, values.password)
Router.push('/')
} catch (error) {
if (error === StatusCodes.BAD_REQUEST) {
this.setErrorMessage("Sorry, you have entered incorrect details. Please try again.")
} else {
this.setErrorMessage("Sorry, there was an issue trying to log you in")
}
}
}
}
render() {
const { login } = this.props
const { processing } = this.state
return (
<Layout title="Login">
<div className="form-wrapper full">
<LoginForm processing={processing} onSubmit={this.handleSubmit} fieldValues={login.values} />
</div>
</Layout>
)
}
}
const mapStateToProps = ({ form: { login } }: IRootState) => ({ login })
export default connect(mapStateToProps)(LoginPage)
LoginForm.tsx
import Link from 'next/link'
import React from 'react'
import { Field, InjectedFormProps, reduxForm } from 'redux-form'
import FormButton from 'components/Forms/FormButton'
import Input from 'components/Fields/Input'
import { validateRequired } from 'lib/helpers/validators'
import { IFormProps, IFormData } from 'pages/login'
class LoginForm extends React.Component<IFormProps & InjectedFormProps<IFormData, IFormProps>> {
render() {
const { contactId, errorMessage, fieldValues, handleSubmit, processing } = this.props
return (
<form id="login" onSubmit={handleSubmit} >
<h1>Sign in</h1>
<fieldset>
<div className="fields">
{
!contactId
? <Field name="username" type="text" component={Input} label="Username" validate={validateRequired} />
: <Field name="username" type="email" component={Input} label="Email" validate={validateRequired} />
}
</div>
<div className="fields">
<Field name="password" type="password" component={Input} label="Password" validate={validateRequired} />
</div>
</fieldset>
{ errorMessage && <p className="error-message">{errorMessage}</p> }
<div className="form-bottom">
<Link href="/"/*{`/forgot-password${fields.email ? `?email=${encodeURIComponent(fields.email)}` : ''}`}*/>
<a className="inline">Forgotten your password?</a>
</Link>
<FormButton loading={processing}>
Login
</FormButton>
</div>
</form>
)
}
}
export default reduxForm<{}, IFormProps>({ form: 'login' })(LoginForm)
Here is my redux store file incase if that is coded incorrectly
import { createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper'
import { AnyAction, applyMiddleware, combineReducers, createStore, Reducer } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk'
import authReducer, { AuthState } from './auth/reducer'
import contactReducer, { ContactState } from './contact/reducer'
import initialState from './initialState'
export interface State {
auth: AuthState
contact: ContactState
}
const bindMiddleware = (middleware: [ThunkMiddleware]) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
auth: authReducer,
contact: contactReducer,
form: formReducer
})
const reducer: Reducer = (state: State, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: Reducer = {
...state,
...action.payload
}
return nextState
} else {
return combinedReducer
}
}
const makeStore: MakeStore<State> = () => createStore(reducer, initialState, bindMiddleware([thunkMiddleware]))
export const wrapper = createWrapper<State>(makeStore/*, { debug: true }*/)
TypeError: Cannot destructure property 'login' of 'undefined' or 'null'.The way I wanted it to work was so that it returns the form state in redux so I can fetch the form values in "real-time" so whenever a new character is typed, the props will be updated with the new values. - Hyokunereturn combinedReducershould probably bereturn combinedReducer(state, action)- cbrArgument of type 'State' is not assignable to parameter of type 'CombinedState<{ auth: AuthState; contact: ContactState; form: FormStateMap; }>'.when I pass the two parameters toreturn combinedReducer. - HyokuneStateinterface is missing the key and type forform. - cbr