ReduxForm and ReactRouter - Form Not Rendering - reactjs

I'm refactoring some code on my first react app, which is a simple login form that redirects and renders a list of items on successful login. I have a form container, connected to the redux store, that should render a login form - decorated with reduxForm. I don't understand why the form is not rendering.
index.jsx
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('app')
);
App.jsx
import {LoginFormContainer} from './components/Login/LoginFormContainer'
class App extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={LoginFormContainer}/>
</Switch>
)
}
}
export default App
LoginFormContainer.jsx
import LoginFormComponent from './LoginFormComponent';
import {reduxForm} from 'redux-form/immutable';
import {withRouter} from 'react-router-dom';
let LoginFormContainer = reduxForm({
form: 'login',
})(LoginFormComponent)
const mapStateToProps = null
const mapDispatchToProps = dispatch => {
return {
onSubmit: loginFormValues => {
dispatch(loginUser(loginFormValues))
}
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) =>
Object.assign({}, stateProps, dispatchProps, ownProps)
LoginFormContainer = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(LoginFormContainer))
export default LoginFormContainer
LoginFormComponent.jsx
class LoginForm extends Component {
render() {
return (
<Row>
<Col xs={12} md={12}>
<form>
<FormGroup>
<Field
name="username"
type="email"
component={renderField}
label="Username"
validate={[required]}
/>
</FormGroup>
<FormGroup>
<Field
name="password"
type="password"
component={renderField}
label="Password"
validate={[required]} />
</FormGroup>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</Col>
</Row>
)
}
}
export default LoginForm

Simply removing the curly braces from the import of LoginFormContainer in App.jsx fixed the issue. import LoginFormContainer from './components/Login/LoginFormContainer'

Just a few minor exports/imports missing.
LoginFormContainer.jsx
import LoginFormComponent from './login_form_component';
import {reduxForm} from 'redux-form/immutable';
import {withRouter} from 'react-router-dom';
import { connect } from 'react-redux'; // missing import
// need to export these variables to be used below
export const LoginFormContainer = reduxForm({
form: 'login',
})(LoginFormComponent)
export const mapStateToProps = null
export const mapDispatchToProps = dispatch => {
return {
onSubmit: loginFormValues => {
dispatch(loginUser(loginFormValues))
}
}
}
// this is not needed
// export const mergeProps = (stateProps, dispatchProps, ownProps) =>
// Object.assign({}, stateProps, dispatchProps, ownProps)
// withRouter is not needed unless you need any of the
// functionality here https://reacttraining.com/react-router/web/api/withRouter
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer)
LoginFormComponent.jsx
Not sure if you just did not include the imports for this file. Make sure to have this in there
import React, { Component } from 'react';

Related

connected-react-router push is called nothing happens

There is a Login component
// #flow
import type {
TState as TAuth,
} from '../redux';
import * as React from 'react';
import Button from '#material-ui/core/Button';
import FormControl from '#material-ui/core/FormControl';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import Paper from '#material-ui/core/Paper';
import { withNamespaces } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
connect,
} from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import useStyles from './styles';
import { login } from '../../redux';
import { push } from 'connected-react-router';
const logo = './assets/images/logo.png';
const {
useEffect,
} = React;
type TInputProps = {
input: Object,
meta: {
submitting: boolean,
}
}
const UserNameInput = (props: TInputProps) => (
<Input
id="userName"
name="userName"
autoComplete="userName"
autoFocus
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
const PasswordInput = (props: TInputProps) => (
<Input
name="password"
type="password"
id="password"
autoComplete="current-password"
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
type TProps = {
t: Function,
login: Function,
handleSubmit: Function,
error: string,
submitting: boolean,
auth: TAuth,
}
// TODO: fix flow error inside
const Login = ({
t,
login,
handleSubmit,
error,
submitting,
auth,
}: TProps) => {
const classes = useStyles();
useEffect(() => {
if (auth) {
console.log('push', push);
push('/dashboard');
}
}, [auth]);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<img src={logo} alt="logo" className={classes.logo} />
<form
className={classes.form}
onSubmit={handleSubmit((values) => {
// return here is very important
// login returns a promise
// so redux-form knows if it is in submission or finished
// also important to return because
// when throwing submissionErrors
// redux-form can handle it correctly
return login(values);
})}
>
<FormControl margin="normal" required fullWidth>
<Field
name="userName"
type="text"
component={UserNameInput}
label={
<InputLabel htmlFor="userName">{t('Username')}</InputLabel>
}
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
name="password"
type="password"
component={PasswordInput}
label={
<InputLabel htmlFor="password">{t('Password')}</InputLabel>
}
/>
</FormControl>
<div className={classes.error}>{error}</div>
<Button
disabled={submitting}
type="submit"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
>
{t('Sign in')}
</Button>
<Link className={classes.forgot} to="/forgot">
{t('Forgot Password?')}
</Link>
</form>
</Paper>
</main>
);
};
const mapStateToProps = ({ auth }) => ({ auth });
const mapDispatchToProps = {
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm({ form: 'login' })(withNamespaces()(Login))
);
In the useEffect hook the push from connected-react-router is used. The hook fires ok but nothing happens after it.
The same way, push is used in login action.
// #flow
import type {
TReducer,
THandlers,
TAction,
TThunkAction,
} from 'shared/utils/reduxHelpers';
import type {
TUser,
} from 'shared/models/User';
import createReducer from 'shared/utils/reduxHelpers';
import urls from 'constants/urls';
import axios, { type $AxiosXHR } from 'axios';
import { SubmissionError } from 'redux-form';
import { push } from 'connected-react-router';
export type TState = ?{
token: string,
result: $ReadOnly<TUser>,
};
export const ON_LOGIN = 'ON_LOGIN';
export const login: ({ userName: string, password: string }) => TThunkAction =
({ userName, password }) => async (dispatch, getState) => {
const res: $AxiosXHR<{username: string, password: string}, TState> =
await axios.post(`${urls.url}/signin`, { username: userName, password })
.catch((err) => {
throw new SubmissionError({ _error: err.response.data.message });
});
const data: TState = res.data;
dispatch({
type: ON_LOGIN,
payload: data,
});
push('/dashboard');
};
const handlers: THandlers<TState, TAction<TState>> = {
[ON_LOGIN]: (state, action) => action.payload,
};
const initialState = null;
const reducer: TReducer<TState> = createReducer(initialState, handlers);
export default reducer;
Here everything goes successful and dispatch happens and there is no push happening again.
Whats the problem?
Shouldn't there be dispatch(push('/dashboard')); ?
You just have to make sure that you do not create your middleware and pass in the history api before calling createRootReducer function.
If you try to create your middleware with routerMiddleware(history) too early , history will be passed in as undefined. Follow the README.md as it explains the exact execution order.
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // <-- Initiates the History API
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // <--- Now history can be passed to middleware
// ... other middlewares ...
),
),
)
return store
}

React/Redux Testing - Could not find "store" in either the context or props

I'm new to react/redux and just getting into testing my first app with chai. I'm using redux-form/immutable and react-router, and I'm not sure how to resolve this issue when testing:
1) Login
renders a login form:
Invariant Violation: Could not find "store" in either the context or props of "Connect(Form(LoginForm))". Either wrap the root component in a <Provider>, or explicitly pass "st
ore" as a prop to "Connect(Form(LoginForm))".
I found this issue: https://github.com/reactjs/react-redux/issues/57 which seems like the same problem but the solution of adding a function that returns the children to the Router element doesn't fix the problem.
index.jsx
import {configRoutes} from './config'
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>{configRoutes()}</Router>
</Provider>,
document.getElementById('app')
);
config.js
import App from './App';
import {PackageStoreContainer} from './components/Package/PackageContainer';
import {LoginStoreContainer} from './components/Login/LoginContainer';
export function configRoutes() {
const routes = <Route component={App}>
<Route path="/packages" component={PackageStoreContainer} />
<Route path="/" component={LoginStoreContainer} />
</Route>;
return routes;
}
LoginContainer.jsx
export class LoginContainer extends React.Component {
constructor(props, context) {
super(props, context);
sessionStorage.removeItem('credentials');
}
submit = values => {
this.props.dispatch(loginUser(values));
}
render() {
return (
<div className="row">
<LoginForm onSubmit={this.submit} />
</div>
);
}
};
const mapStateToProps = (state, ownProps) => {
return {
creds: {}
};
}
export const LoginStoreContainer = connect(mapStateToProps)(LoginContainer);
LoginForm.jsx
const required = value => (value ? undefined : 'Required');
const renderField = ({input, label, type, meta: {touched, error, warning}}) => (
<div>
<ControlLabel htmlFor="email">{label}</ControlLabel>
<div>
<input {...input} placeholder={label} type={type} className="form-control" />
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
</div>
);
const LoginForm = (props) => {
const { handleSubmit } = props
return (
<Col xs={12} md={12}>
<form onSubmit={handleSubmit}>
<FormGroup>
<Field
name="username"
type="email"
component={renderField}
label="Username"
validate={[required]}
/>
</FormGroup>
<FormGroup>
<Field
name="password"
type="password"
component={renderField}
label="Password"
validate={[required]} />
</FormGroup>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</Col>
);
};
export default reduxForm({
// a unique name for the form
form: 'login'
})(LoginForm)
Login_spec.js
describe('Login', () => {
it('renders a login form', () => {
const component = renderIntoDocument(
<LoginContainer />
);
const fields = scryRenderedDOMComponentsWithTag(component, 'input');
const submit = scryRenderedDOMComponentsWithTag(component, 'button');
// expect(fields.length).to.equal(3);
// expect(submit.length).to.equal(1);
});
});
Fix
import {Provider} from 'react-redux';
const createMockStore = (getState) => {
const middlewares = [];
return configureStore(middlewares)(getState);
};
const store = createMockStore();
const component = renderIntoDocument(
<Provider store={store}>
<LoginContainer />
</Provider>
);
LoginContainer is a connected component connect(mapStateToProps)(LoginContainer). That means it depends on the Redux store to generate the subtree, and when testing you don't have the wrapping <Provider store={store} /> around it.
The solution is to use redux-mock-store:
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
const component = renderer.create(
<Provider store={mockStore()}>
<LoginContainer />
</Provider>
);
when you connect a component, the connect function tries to find Provider with store in the parent hierarchy. If it does not find it it throws that error.
Easy solution is to wrap each testing component with Provider.
import reducers from "reducerPath"
import {Provider} from "react-redux"
import {createStore} from "redux"
<Provider store={createStore(reducers,{})} >
<LoginComponent/>
</Provider>
this will solve the issue but it is expensive because you have to set this up for each component
Easy way is to change the way how you render your app. Instead of this in index.jsx:
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>{configRoutes()}</Router>
</Provider>,
document.getElementById('app')
);
First create a wrapper component:
import reducers from "reducerPath"
import {Provider} from "react-redux"
import {createStore} from "redux"
export default (props)=>{
<Provider store={createStore(reducers,{})}>
{props.children}
</Provider>
}
then index.jsx
import Wrapper from "./wrapper.jsx"
ReactDOM.render(
<Wrapper>
<Router history={hashHistory}>{configRoutes()}</Router>
</Wrapper>,
document.getElementById('app')
);
Now in testing, you can use this Wrapper component to wrap the testing component instead of depending anoter third party library:
const component = renderIntoDocument(
<Wrapper store={store}>
<LoginContainer />
</Wrapper>
);

Function is not a function inside React-Router

i create simple signin form for my website.
But i have a little problem.
When i type login and password and try send it to server i got error
Uncaught TypeError: this.props.signinUser is not a function
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
class Signin extends Component{
constructor(props){
super(props);
this.state= {};
this.onSubmit = this.onSubmit.bind(this);
}
renderField(field){
return(
<div className="form-group">
<label>{field.label}</label>
<input
className="form-control"
type="text"
{...field.input}
/>
</div>
);
}
onSubmit(e){
e.preventDefault();
let {login,password} = this.state;
//this.props.login(login,password);
console.log({login,password});
this.props.signinUser({login,password});
this.setState({
login: '',
password: ''
})
}
render(){
let {login,password} = this.state;
return(
<form onSubmit={this.onSubmit}>
<Field
label="Login:"
name="login"
component={this.renderField}
onChange={e => this.setState({login: e.target.value})}
/>
<Field
label="Password:"
name="password"
component={this.renderField}
onChange={e => this.setState({password: e.target.value})}
/>
<button action="submit" className="btn btn-primary"> Sign In </button>
</form>
);
}
}
function mapStateToProps(state) {
return { form: state.form };
}
export default reduxForm({
form: 'signin'
}, mapStateToProps, actions)(Signin);
And Actions.
import axios from 'axios';
export function signinUser({login,password}){
return function(dispatch){
axios.post('http://localhost:8080/auth', { login, password});
};
}
and finally reducer.
import { combineReducers } from 'redux';
import { reducer as fromReducer } from 'redux-form';
const rootReducer = combineReducers({
form: fromReducer
});
export default rootReducer;
I use react 16, react redux v5 and react-router v3, react form v7!
I think its problem with connect function from ReactForm!
The problem lies here
export default reduxForm({
form: 'signin'
}, mapStateToProps, actions)(Signin);
This should be
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
....
//remainder of the code
....
mapDispatchToProps =(dispatch)=>{
return {
actions : bindActionCreators(actions , dispatch)
};
}
Signin= connect(
mapStateToProps,
mapDispatchToProps
)(Signin);
export default reduxForm({
form: 'signin'
})(Signin);
You would need to change the call this.props.signinUser to this.props.actions.signinUser
More info at : https://redux-form.com/7.0.4/docs/faq/howtoconnect.md/

Field error while using redux form in React js

I was trying to do some form validations in react js , while Using redux form am facing one error Field must be inside a component decorated with reduxForm() .
i just searched for this error on web but didn't get any solution for that .
Reference Link : http://redux-form.com/6.8.0/examples/simple/ .
Here is my code ,
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
export class EditProfile extends Component {
render() {
console.log("hello welcome");
const { handleSubmit, pristine, reset, submitting } = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" component="input" type="text"/>
</div>
</form>
);
}
}
export default reduxForm({
form: 'editForm'
})(EditProfile)
What i did wrong in my code , can someone clarify me .
You have both default export (which is decorated with reduxForm) and named export (not decorated).
I'm assuming you're importing your form like this:
import { EditProfile } from 'EditForm'; // Using named export
Instead you need to import the default one:
import EditProfile from 'EditForm'; // Default export
Technically there's no error, and babel doesn't complain as you're exporting both from the same file. And in some cases it makes sense to export both (e.g. export undecorated one for testing purposes). But in my work I prefer to have only one default export to prevent shooting myself in the foot.
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
export class EditProfile extends Component {
render() {
console.log("hello welcome");
const { handleSubmit, pristine, reset, submitting } = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" component="input" type="text"/>
</div>
</form>
);
}
}
EditProfile = reduxForm({
form: 'editForm'
})(EditProfile)
export default EditProfile;
I also faced the same issue. The solution is to edit /index.js
and add the following lines of code:
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
form: formReducer,
});
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();

redux-form unable to type values in fields

I'm not able to type values in input fields using redux-form. I have the following reducer
import {combineReducers} from 'redux';
import session from './sessionReducer';
import profile from './profileReducer';
import map from './mapReducer';
import { reducer as formReducer } from 'redux-form'
const rootReducer = combineReducers({
// short hand property names
session,
profile,
map,
form: formReducer
})
export default rootReducer;
and here is the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
import thunk from 'redux-thunk'
import { routerReducer, routerMiddleware, push } from 'react-router-redux'
import reducers from '../reducers'
import { browserHistory } from 'react-router';
const middleware = [ thunk ];
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
middleware.push(routerMiddleware(browserHistory));
// Add the reducer to your store on the `routing` key
const store = createStore(
combineReducers({
reducers,
routing: routerReducer
}),
applyMiddleware(...middleware),
)
export default store;
component
import React, {PropTypes, Component} from 'react';
import Upload from './Upload';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as profileActions from '../../../actions/profileActions';
import EventsCalendar from '../../common/EventsCalendar';
import { Field, reduxForm } from 'redux-form'
import ProfileForm from './ProfileForm';
import {
Form,
FormGroup,
FormControl,
ControlLabel,
Tabs,
Tab,
InputGroup,
Label,
HelpBlock,
Grid,
Row,
Button,
Col
} from 'react-bootstrap';
class Profile extends Component {
static propTypes = {
profile: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
profile: {
username: '',
password: '',
email: ''
}
}
//this.onUpdate = this.onUpdate.bind(this)
}
handleSubmit = (values) => {
// Do something with the form values
console.log(values);
}
componentDidMount() {
this.props.actions.getProfile()
}
componentWillReceiveProps(nextProps) {
if (nextProps.profile !== this.props.profile) {
}
}
render() {
console.log(this.props.profile);
const {profile} = this.props.profile;
const { handleSubmit } = this.props;
return (
<div className="row">
<Col lg={10}>
<Tabs defaultActiveKey={1} id="uncontrolled-tab-example">
<Tab eventKey={1} title="Vendor Data">
<ProfileForm onSubmit={this.handleSubmit} data = {this.props.profile}/>
</Tab>
<Tab eventKey={3} title="Events Calendar">
<EventsCalendar/>
</Tab>
</Tabs>
</Col>
<Col lg={2}>
<Upload/>
</Col>
</div>
);
}
}
function mapStateToProps(state) {
return {
profile: state.default.profile,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(profileActions, dispatch)
};
}
Profile = reduxForm({
form: 'profileForm' // a unique name for this form
})(Profile);
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
when I'm typing I see in console that the state is changing
the attached form component
import React, {Component} from 'react';
import {Field, reduxForm} from 'redux-form';
import FieldFormControl from '../../common/FieldFormControl';
import {
FormGroup,
FormControl,
ControlLabel,
Button
} from 'react-bootstrap';
class ProfileForm extends Component {
render() {
const {handleSubmit, profile, pristine, reset, submitting} = this.props;
return (
<form onSubmit={handleSubmit}>
<FormGroup controlId="signup-name">
<Field type="text" name="firsname" placeholder="test" value component={FieldFormControl}>Vorname</Field>
</FormGroup>
<FormGroup controlId="signup-username">
<Field type="text" name="lastname" placeholder={profile.username} value={profile.username} component={FieldFormControl}>Name</Field>
</FormGroup>
<FormGroup controlId="signup-email">
<Field type="text" name="email" placeholder={profile.username} value={profile.username} component={FieldFormControl}>Vorname</Field>
</FormGroup>
<Button
bsStyle="primary"
type="submit"
//disabled={pristine || submitting}
block
>Speichern</Button>
</form>
);
}
}
// Decorate the form component
ProfileForm = reduxForm({
form: 'profileForm' // a unique name for this form
})(ProfileForm);
export default ProfileForm;
the bootstrap override to be compatible with redux-form
import React, { Component } from 'react';
import {FormGroup, FormControl, ControlLabel} from 'react-bootstrap';
export default class FieldFormControl extends Component {
render () {
const { placeholder, type, input, meta} = this.props;
return (
<FormGroup controlId={input.name} validationState={meta.error ? 'error' : 'success'}>
<ControlLabel>{this.props.children}</ControlLabel>
<FormControl type={type} placeholder={placeholder} value={input.value} onChange={input.onChange} />
<FormControl.Feedback />
</FormGroup>
);
}
}
Remove the value prop from your Field components, redux-form handles updating the value and passing it to the component that you pass it. I'm assuming the idea here is to provide initial value, but this is not the place to do that.
<Field type="text" name="email" placeholder={profile.username} component={FieldFormControl}>Vorname</Field>
You can also pass all the input props to your FormControl in your FieldFormControl so you get onFocus, onBlur, etc., all provided by redux-form.
<FormControl placeholder={placeholder} {...input} />
If you want to initialize the fields with values, either use initialValues when you connect using reduxForm, or initialize if it needs to happen after the form mounts.
And finally, you're using combineReducers twice in such a way that most of your reducers are nested in a way that you didn't intend. To simplify this, I would import the routerReducer in your reducers/index.js file, and add it to your combineReducers there.
const rootReducer = combineReducers({
// short hand property names
session,
profile,
map,
form: formReducer,
routing: routerReducer,
});
Then, in your store, you'll just have
const store = createStore(
reducers,
applyMiddleware(...middleware),
);
You should then see that you'll have all your keys in your state (session, profile, form, routing, etc.) instead of just default and routing.
I do not think it is the redux-form's issue.
Instead, I think your application listens onChange of your input, and dispatch action to redux. So this is the root cause: you dispatch onChange action, causing the redux state to update, (I think your code has not change it in reducer) and after that, redux flushes the render UI.
To fix it, you:
typically, when you dispatch the onChange action, in your reducer, update yuor redux state explicitly. then new state will flush your UI automatically.
e.g. your reducer should have similar like this:
function myReducer(state = initialState, action) {
switch (action.type) {
case MY_INPUT_VALUE_CHANGE:
return Object.assign({}, state, {
vornname: action.data
})
default:
return state
}
}

Resources