I have this react app using redux but I can't figure out why the action creator is not calling the reducer.
I cant see the console log inside the LOGGED_IN case in the authReducer file.
the action is called userLogedIn and can be found in the Login.js class
Any help would be great!
thnx
this is the code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import App from './App';
import reducers from './reducers';
const store = createStore(
reducers,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Login.js - the class
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Redirect, Link } from 'react-router-dom';
import {connect} from 'react-redux';
import { userLogedIn } from '../../actions';
import {
Button,
Form,
FormGroup,
FormControl,
Col,
Checkbox,
ControlLabel,
HelpBlock,
Container,
Row
} from 'react-bootstrap';
class LoginForm extends Component {
onSubmitLogin = (event) => {
// let auth = this.state.auth;
event.preventDefault();
fetch('http://127.0.0.1/react/calc/api/user_switch/' + this.state.username +
'/'+ this.state.password )
.then(response => response.json())
.then(json => {
console.log('json ',json)
if(json.count > 0)
{
this.props.userLogedIn(this.props)
}
})
.catch(error => console.log('parsing faild', error))
}
onChange(event){
this.setState({
[event.target.name]: event.target.value
})
}
redirectUser = () =>
{
if(this.props.user.auth)
{
return <Redirect to='/mechinasetup' data={this.state.data} />
}
}
render() {
console.log();
return (
<Container id="LoginForm" className="yellow-bg">
<Row className="show-grid">
<Col xs={8} md={4}>
<Form>
<FormGroup controlId="formHorizontalusername">
<Col xs={12} sm={3} componentclass={'aa'}>
דואר אלקטרוני:
</Col>
<Col xs={12} sm={9}>
<FormControl
ref="username"
name="username"
type="text"
onChange={this.onChange.bind(this)}
placeholder="הקלד דואר אלקטרוני"/>
</Col>
</FormGroup>
<FormGroup controlId="formHorizontalPassword">
<Col xs={12} sm={3} componentclass={'cc'}>
סיסמא:
</Col>
<Col xs={12} sm={9}>
<FormControl ref="password" name="password" type="password" onChange={this.onChange.bind(this)} placeholder="הקלד סיסמא"/>
</Col>
</FormGroup>
<FormGroup>
<Col >
{this.redirectUser(this)}
<Button onClick={this.onSubmitLogin} type="submit" className="full-width-btn" id="loginSubmit">התחבר</Button>
</Col>
</FormGroup>
</Form>
</Col>
</Row>
</Container>
);
}
}
const mapStateToProps = (state) =>{
return{
user: state.user
}
}
export default connect(mapStateToProps, {userLogedIn})(LoginForm);
action:
import { LOGGED_IN } from '../consts';
export const userLogedIn = (state) => {
const action = {
type: LOGGED_IN,
state
}
console.log(state, ' action is auth ');
return action
}
authReducer.js:
import { LOGGED_IN } from '../consts';
const initialState = {
username: '',
password: '',
data: [],
auth: false
}
export default (state = initialState, action) => {
switch(action.type){
case LOGGED_IN:
console.log('LOGGED_IN');
return{
...state,
auth: true
}
default:
return state;
}
}
reducers.js:
import { combineReducers } from 'redux';
import manageCoursesReducer from './manageCoursesReducer';
import authReducer from './manageCoursesReducer';
export default combineReducers({
user: authReducer
})
As you know by now, here is your problem:
import { combineReducers } from 'redux';
import manageCoursesReducer from './manageCoursesReducer';
import authReducer from './manageCoursesReducer';
export default combineReducers({
user: authReducer
})
You are calling the same file in the relative path for two different imports.
I think that the problem is you're not dispatching the action anywhere. try this on Login.js:
const mapStateToProps = (state) =>{
return{
user: state.user
};
};
const mapDispatchToProps = dispatch => {
return {
userLogedIn: (params) => dispatch(userLogedIn(params))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
Let me know if it helped.
The mistake was that I called manageCoursesReducer twice for two different reducers.
import manageCoursesReducer from './manageCoursesReducer';
import authReducer from './manageCoursesReducer';
instead of
import manageCoursesReducer from './manageCoursesReducer';
import authReducer from './authReducer';
now it works
Related
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
Here is my Component Class
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { SimpleGrid,Box,Select,Input,InputGroup,InputRightAddon,Text,Heading ,Button,NumberInput,NumberInputField,} from "#chakra-ui/core";
import { Footer } from '../../layout/Main/components';
import {submitUserInput} from '../../utils/actions/userInput'
import PropTypes from 'prop-types';
class UserInput extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
yieldStress:0,
thickness:0,
diameter:0,
pMAX:0
}
this.handleChange = this.handleChange.bind(this)
}
handleSubmit=(event)=>{
event.preventDefault();
console.log(this.state);
this.props.submitUserInput(this.state);
}
handleChange=(event)=>{
this.setState({[event.target.name]:event.target.value});
}
render(){
const materialsList = this.props.materials.map((material)=>(
<option value={material.yieldStress} key={material.id}>{material.name}</option>
));
return (
<Box>
<Heading as="h4" size="md">Cylinder Details</Heading>
<Text>{this.props.thickness}</Text>
<form onSubmit={this.handleSubmit} >
<Select placeholder="Select Material of Cylinder" isRequired name="yieldStress" onChange={this.handleChange}>
{materialsList}
</Select>
<SimpleGrid columns={2} spacing={8} padding="16px 0px">
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Thickness of Cylinder" roundedRight="0" isRequired name="thickness" onChange={this.handleChange}/>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Diameter of Cylinder" roundedRight="0" isRequired name="diameter"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={8} padding="0px 0px">
<Box>
<InputGroup>
<InputRightAddon children={<Text paddingTop="12px">(N/mm)<sup>2</sup></Text>} padding="0 4px"/>
<NumberInput precision={2} step={0.2} >
<NumberInputField placeholder="Maximum pressure " roundedRight="0" isRequired name="pMAX" onChange={this.handleChange}/>
</NumberInput>
</InputGroup>
</Box>
<Box>
<InputGroup>
<InputRightAddon children="(mm)"/>
<Input type="number" placeholder="Factor of Saftey" roundedRight="0" isRequired name="FOS"onChange={this.handleChange}/>
</InputGroup>
</Box>
</SimpleGrid>
<Box padding="8px 0" >
<Button variantColor="teal" float="right" border="0" type="submit">Submit</Button>
</Box>
</form>
<Footer />
</Box>
)
}
}
UserInput.protoTypes = {
submitUserInput: PropTypes.func.isRequired,
thickness: PropTypes.string.isRequired,
}
const mapStateToProps = (state) => ({
thickness: state.userInput.thickness
})
const mapDispatchToProps = dispatch => {
return {
submitUserInput: (userInput)=> dispatch(submitUserInput(userInput))
}}
export default connect(mapStateToProps, mapDispatchToProps)(UserInput)
Here is My Action File
import { SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE} from '../types'
export const submitUserInput = ({yieldStress, FOS, diameter,pMAX,thickness }) => async (dispatch) => {
dispatch(submitRequestSent());
try {
console.log("Printing object in action")
console.log({yieldStress, FOS, diameter,pMAX,thickness }) //This is Printed
dispatch({
type: SUBMIT_SUCCESS,
payload:{yieldStress, FOS, diameter,pMAX,thickness }
}
);
}
catch (error) {
dispatch({
type: SUBMIT_FAILURE,
payload: error.message
})
throw error;
}
}
export const submitRequestSent = () =>
({
type: SUBMIT_REQUEST_SENT,
})
And Here is the UserInput Reducer
import {
SUBMIT_REQUEST_SENT,SUBMIT_SUCCESS,SUBMIT_FAILURE
} from '../../actions/types'
const initialState = {
userInput:{
yieldStress:0,
FOS:0,
diameter:0,
pMAX:0,
thickness:0
}
}
export default function (state = initialState, action) {
switch (action.type) {
case SUBMIT_REQUEST_SENT:
return {
...state,
}
case SUBMIT_SUCCESS:
console.log("Submit has been sucessfull And below is the Payload"); //This is Printed
console.log(action.payload)//This is Printed
return {
...state,
userInput:action.payload
}
case SUBMIT_FAILURE:
return {
...state,
error: action.payload
}
default:
return state;
}
}
And my root Reducer
import {combineReducers} from 'redux';
import authReducer from '../authReducer'
import userInputReducer from '../userInputReducer';
export default combineReducers(
{
auth:authReducer,
userInput:userInputReducer
}
)
I will Appreciate any applicable solution/Explanation..thanks
Within your state you have a property called userInput, which is the state section controlled by userInputReducer. That section has its own property which is also called userInput that stores the actual data. It that what you intended?
If so, you need to map properties like this:
const mapStateToProps = (state) => ({
thickness: state.userInput.userInput.thickness
})
If not, you need to rework your userInputReducer and initialState to remove the double nesting.
Everything Seems Fine to me, the Console logs work in the Action and in the Reducer but the Chrome Redux tools show no action being trigged and no state updates.
This sounds to me like an issue with dispatching an asynchronous thunk action. Redux cannot handle these types of actions without the appropriate middleware. When you create the store, it should be something like this:
import reducer from "./reducer";
import {createStore, applyMiddleware} from "redux";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
I copy and posted your reducer code with a store set up as above. I used a dummy component ( a button that increments thickness when clicked ) and I was not able to reproduce your issue. That leads me to believe that the issue is in the configuration of the store itself.
I am going to post my question here and hopefully learn something! I've been following a tutorial and do not yet have the insight into React and Redux to effectively figure out what is going on. I want to create functionality that adds and removes businesses to global state in React and Redux. From what I've Googled I know the file structures vary depending on the project so I will post all of my files here. I have them divided into actions.js, reducers.js, state.js, and store.js. I have an add Listing view with React and would like to add remove functionality to my view listings view. Here are my files:
redux/actions.js:
export const addListing = (newBusiness) => {
return {
type: 'ADD_LISTING',
value: newBusiness
}
}
export const removeListing = (index) => {
return {
type: 'REMOVE_LISTING',
value: index
}
}
redux/reducers.js:
import { combineReducers } from 'redux';
import { addBusiness, removeBusiness } from './actions'
const user = (state = null) => state
// add switch statements in here
const businesses = (state = [], action) => {
switch(action.type) {
case 'ADD_BUSINESS':
return [ ...state, action.value ]
case 'REMOVE_BUSINESS':
const newState = [ ...state ]
newState.splice(action.value, 1);
return newState;
default: // need this for default case
return state
}
}
export default combineReducers({ user, businesses })
redux/state.js
export default {
user: {
username: 'test-user-1',
email: 'test-user#example.com'
},
businesses: [
{
"id": 15,
"name": "My pizza",
"description":"Homemade pizza shop",
"address": "123 Taco Street",
"hours": "9-5"
}
]
};
redux/store.js
import { createStore } from 'redux'
import reducers from './reducers'
import state from './state'
export default createStore(reducers, state)
containers/addListing.js
import { connect } from "react-redux";
import { addListing } from '../redux/actions';
import AddListing from '../components/addListing'
const mapDispatchToProps = (dispatch) => {
return {
addListing: (business) => dispatch(addListing(business)),
}
}
export default connect(null, mapDispatchToProps)(AddListing)
containers/removeListing.js
import { connect } from "react-redux";
import { removeListing } from '../redux/actions';
const mapDispatchToProps = (dispatch) => {
return {
removeCar: (business) => dispatch(removeListing(business)),
}
}
export default connect(null, mapDispatchToProps)(removeListing)
containers/Listing.js:
import { connect } from 'react-redux'
import Listing from '../components/Listing'
const mapStateToProps = (state) => {
return {
businesses: state.businesses,
user: state.user.username
}
}
export default connect(mapStateToProps)(Listing)
components/addListing.js
import React from 'react';
import { InputLabel } from '#material-ui/core'
import { Input } from '#material-ui/core'
import { FormControl } from '#material-ui/core';
import { Button } from '#material-ui/core';
import { TextField } from '#material-ui/core';
import '../redux/state';
class addListing extends React.Component {
state = {
name: '',
description: '',
address: '',
hours: ''
}
handleTextChange = (e) => {
const newState = { ...this.state }
newState[e.target.id] = e.target.value
this.setState(newState)
console.log(this.state)
}
handleSubmit = (e) => {
e.preventDefault()
const payload = { ...this.state }
console.log("THE BUSINESS", payload)
this.props.addListing(payload)
console.log(this.props)
}
componentDidUpdate = (prevProps, prevState) => {
if (prevState.open !== this.state.open) {
this.setState({
name: '',
description: '',
address: '',
hours: ''
})
}
}
render(){
return (
<div className="App">
<form
onSubmit={this.handleSubmit}
style={{ display: 'flex', flexDirection: 'column', width: '350px' }}>
<TextField
id="name"
name="name"
placeholder="Name"
value={this.state.name}
onChange={this.handleTextChange}
required />
<TextField
id="description"
name="description"
placeholder="Description"
value={this.state.description}
onChange={this.handleTextChange}
required />
<TextField
id="address"
name="address"
placeholder="Address"
value={this.state.address}
onChange={this.handleTextChange}
required />
<TextField
id="hours"
name="hours"
placeholder="Hours"
value={this.state.hours}
onChange={this.handleTextChange}
required />
<br />
<Button variant="contained" color="primary" type="submit">Submit</Button>
</form>
</div>
);
}
}
export default addListing;
components/Listing.js:
import React from 'react'
import {
Container,
Table,
TableBody,
TableCell,
TableHead,
TableRow
} from '#material-ui/core'
import DeleteIcon from '#material-ui/icons/Delete'
import addListing from '../containers/addListing'
import removeListing from '../containers/removeListing'
import businesses from '../redux/state'
import user from '../redux/state';
const Listing = (props) => {
console.log(props.businesses)
return (
<Container maxWidth="lg" className="car-container">
<h4>Welcome, {props.user.username}</h4>
<div className="flex-container">
</div>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Name</TableCell>
<TableCell>Description</TableCell>
<TableCell>Address</TableCell>
<TableCell>Hours</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.businesses.map((businesses, idx) => (
<TableRow key={businesses.id}>
<TableCell component="th" scope="row">
</TableCell>
<TableCell>{businesses["name"]}</TableCell>
<TableCell>{businesses["description"]}</TableCell>
<TableCell>{businesses["address"]}</TableCell>
<TableCell>{businesses["hours"]}</TableCell>
<TableCell>
<DeleteIcon
// add onClick method here
// onClick={props.removeCar(idx)}
className="icon text-red"
onClick={ () => this.props.removeListing(idx)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Container>
)
}
export default Listing
App.js:
import React from 'react';
import Navigation from './components/Navigation'
import './App.css'
import Router from './Router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<Navigation />
<Router />
</BrowserRouter>
</Provider>
);
}
export default App;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Login from './components/Login'
import App from './App';
import * as serviceWorker from './serviceWorker';
import Listing from '../src/components/Listing';
import { Provider } from 'react-redux';
ReactDOM.render(
<App />,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
So far I know it's something simple. I am getting no errors but it just doesn't seem to add the new listings to global state. When I get to the listing view it only displays the Business I included as a default in state.js. I will try to reply in a quick manner and please let me know if more info is needed. Thanks!
I see that the names of your actions are different ADD_LISTING vs ADD_BUSINESS, REMOVE_LISTING vs. REMOVE_BUSINESS.
In the addListing you have {type: 'ADD_LISTING', ...}, in your reducer you have case 'ADD_BUSINESS': The strings are different. They need to match. Try renaming ADD_BUSINESS => ADD_LISTING, and REMOVE_BUSINESS=>REMOVE_LISTING.
Regarding the crash on line 50. You don't need this. because your component is not a class type. Change it to onClick={ () => props.removeListing(idx)}.
You're missing removeListing in the mapDispatchToProps.
Also, Redux DevTools plugin for Chrome can help you a lot in debugging issues with redux..
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
}
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';
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
}
}