Why doesn't the redux state update? - reactjs

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.

Related

Value from Redux store always returns undefined

Trying to store user input from form into redux, however value accessed through useSelector is always undefined.
Slice component:
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: "userSettings",
initialState: {
interval: 15000,
},
reducers: {
updateInterval: (state, action) => {
state.interval = action.payload
}
},
});
export const { updateInterval } = userSlice.actions;
export const userRefreshInterval = (state) => state.userSettings;
export default userSlice.reducer;
Input form component:
import React, { useState } from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
import { useSelector, useDispatch } from 'react-redux'
import {userRefreshInterval, updateInterval} from "./store/userSlice.js";
export default function UserSettingsForm() {
const [refreshInterval, setInterval] = useState("");
const dispatch = useDispatch();
const interval = useSelector(userRefreshInterval);
console.log(interval); // <--- always undefined
const handleSubmit = (e) => {
e.preventDefault();
dispatch(updateInterval(refreshInterval));
};
const handleChangeInterval = (text) => {
setInterval(text);
};
return (
<Form onSubmit={handleSubmit} style={{margin: 10}} inline>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Label for="refreshInterval" className="mr-sm-2">Refresh Interval:</Label>
<Input type="text" name="refreshInterval" id="refreshInterval" placeholder="interval" onChange={(e) => handleChangeInterval(e.target.value)} />
</FormGroup>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Label for="roundDecimal" className="mr-sm-2">Round results to decimal:
</Label>
<Input type="text" name="roundDecimal" id="roundDecimal" placeholder="roundDecimal" />
</FormGroup>
<Button>Submit</Button>
</Form>
);
}
Update with store.js as asked:
import { configureStore } from '#reduxjs/toolkit'
import userReducer from "./userSlice";
export default configureStore({
reducer: {
user: userReducer
}
})
Feeling like missing something simple, pls help to identify the cause of the issue.
I haven't seen your store first.
try this
export const userRefreshInterval = (state) => state.user;
Because you have named your slicer as user. If you want to get state using
export const userRefreshInterval = (state) => state.userSettings;
Configure your store like the bellow
export default configureStore({
reducer: {
userSettings: userReducer
}
})
// your component
import React, { useState } from "react";
import { Button, Form, FormGroup, Label, Input } from "reactstrap";
import { useSelector, useDispatch } from "react-redux";
import { updateInterval } from "./store/userSlice.js";
export default function UserSettingsForm() {
const [refreshInterval, setInterval] = useState("");
const dispatch = useDispatch();
// need to change here, no need of userRefreshInterval, no need to even export or create this function in reducer file
const interval = useSelector((state) => state.user.interval);
console.log(interval); // <--- always undefined
const handleSubmit = (e) => {
e.preventDefault();
dispatch(updateInterval(refreshInterval));
};
const handleChangeInterval = (text) => {
setInterval(text);
};
return (
<Form onSubmit={handleSubmit} style={{ margin: 10 }} inline>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Label for="refreshInterval" className="mr-sm-2">
Refresh Interval:
</Label>
<Input
type="text"
name="refreshInterval"
id="refreshInterval"
placeholder="interval"
onChange={(e) => handleChangeInterval(e.target.value)}
/>
</FormGroup>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Label for="roundDecimal" className="mr-sm-2">
Round results to decimal:
</Label>
<Input
type="text"
name="roundDecimal"
id="roundDecimal"
placeholder="roundDecimal"
/>
</FormGroup>
<Button>Submit</Button>
</Form>
);
}
// your reducer file
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: "userSettings",
initialState: {
interval: 15000,
},
reducers: {
updateInterval: (state, action) => {
state.interval = action.payload;
},
},
});
export const { updateInterval } = userSlice.actions;
// removed userRefreshInterval function
export default userSlice.reducer;
Let me know if it works!

Unable to access First component state in second component using Redux,Sagas

First Compoent
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from 'prop-types'
import { withRouter } from "react-router-dom";
import { gateway as MoltinGateway } from "#moltin/sdk";
import {getList,updateList} from "./../Action/Action";
import { connect } from "react-redux";
import Icon from '#material-ui/core/Icon';
import Payment from "./../Payment/Payment";
import Tick from './done.png'
export class Item extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pickItem = this.pickItem.bind(this);
}
UpdateList ={}
pickItem(pickedItem, id) {
//console.log(pickedItem,id)
document.getElementById(id).classList.toggle("active")
this.UpdateList = pickedItem.map(function(data,i){
if(data.id == id && i<=5 && data.pickedItem!=='Yes'){
data.pickedItem = 'Yes'
return data
}else{
data.pickedItem = 'No'
return data
}
});
}
componentWillMount() {
this.props.getList();
}
updateList(){
//console.log(this.UpdateList)
this.props.updateList(this.UpdateList)
this.props.history.push({
pathname: '/Payment',
});
}
render() {
//const { pickedItem } = this.state;
const {list} = this.props
let filtereDate;
if(list!==undefined && list.length>0){
filtereDate = list.map(function(data,i){
if(i<=5){
return(
<div key={data.id} ref={data.id} id={data.id} onClick={this.pickItem.bind(this, list, data.id )} className='item-list'>
<span className="tickMark"><img src={Tick} /></span>
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>{data.name}</h3>
<p>
<span>₹</span>
<span>{data.id}</span>
</p>
</div>
</div>
)
}
}.bind(this));
}
return (
<div className="ItemPage">
<header>
<h1>Online shopping</h1>
<h2>Visit | Pick | Pay</h2>
</header>
{filtereDate}
<div className="btnWrp">
<button onClick={this.updateList.bind(this)} className="button">Make Payment</button>
</div>
</div>
);
}
}
Item.propTypes = {
list: PropTypes.object,
getList: PropTypes.func,
updateList:PropTypes.func
}
function mapStateToProps(state){
const Items= state
return {
list : Items.list
}
}
const mapDispatchToProps = dispatch => ({
getList: () => dispatch(getList()),
updateList: (list) =>
dispatch(updateList(list))
})
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(Item));
Sages file
import { put, takeLatest, all, call,select } from "redux-saga/effects";
function* fetchNews() {
const json = yield fetch(
"http://petstore.swagger.io/v2/pet/findByStatus?status=available"
).then(response => response.json());
yield put({ type: "GET_LIST_SUCCESS", json: json });
}
function * updateNewList(data){
///console.log(data.payload)
yield put({ type: "GET_LIST_SUCCESS", list: data.payload });
}
function * fetchupateList(){
const signals = yield select(store => store)
console.log(signals)
}
function* actionWatcher() {
yield takeLatest("GET_LIST_REQUEST", fetchNews);
yield takeLatest("GET_UPDATED_LIST_REQUEST", fetchupateList);
yield takeLatest("UPDATE_LIST_REQUEST", updateNewList);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
**Second Component **
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from 'prop-types'
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import {getList,updateList,getUpdatedList} from "./../Action/Action";
export class Payment extends React.Component {
constructor(props) {
super(props);
this.state = {
pickedItem: [1, 2]
};
}
componentWillMount() {
this.props.getUpdatedList();
}
render() {
console.log(this.props)
const { pickedItem } = this.state;
//console.log(pickedItem);
return (
<div className="PaymentPage">
<div className="pageWrapper">
<form noValidate autoComplete="off">
<h1>Payment Details</h1>
<TextField
id="outlined-name"
label="Card Type"
margin="normal"
variant="outlined"
/>
<TextField
id="outlined-name"
label="Card Name"
margin="normal"
variant="outlined"
/>
<TextField
id="outlined-name"
label="Card Number"
margin="normal"
variant="outlined"
/>
<div className="clm-2-inp">
<TextField
id="outlined-name"
label="Expiry Date (MM/YYYY)"
margin="normal"
variant="outlined"
/>
<TextField
id="outlined-name"
label="CVV"
margin="normal"
variant="outlined"
/>
</div>
</form>
<div className="itemsection">
<h2>Summery</h2>
<div className="item-list">
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>Item Name</h3>
<p>
<span>₹</span>
<span>3000</span>
</p>
</div>
</div>
<Button variant="contained" color="primary">
Submit Purchase
</Button>
</div>
</div>
</div>
);
}
}
Payment.propTypes = {
list: PropTypes.object,
getList: PropTypes.func,
updateList:PropTypes.func,
getUpdatedList:PropTypes.func
}
function mapStateToProps(state,ownProps){
console.log(state,ownProps)
const Items= state
return {
list : Items.list
}
}
const mapDispatchToProps = {
getList,
updateList,
getUpdatedList
};
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(Payment));
Reducer
const initialState = {
list: {}
}
const Reducer = (state = initialState, action) => {
switch (action.type) {
case "GET_LIST_SUCCESS":
return {
...state,
list: action.json,
}
case "GET_LIST_SUCCESS":
return {
...state,
list: action.list,
}
default:
return state;
}
};
export default Reducer;
Once i click the "Make payment" button in the first component, i will updated the list with some modification those modified changes i want to get in the second component.
I unable to get first redux store value in the second component.
Help me to ix this issue please.

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
}

How can I dispatch a state change to redux when calling it from a function inside the component?

I'm still fairly new to Javascript and React, and have been trying to introduce Redux into my admin-site project.
Right now the only thing I want to use it for is to change the ip-address of all axios calls to my backend. So on my admin site, when the user goes to log in, they will be presented with a drop-down of different servers that the site can make a request to, e.g. development, staging, production...
So when the user does log in, any component that needs to make a request, which is actually every single component, can use the IP-address that is stored in the redux state.
A kind of mini-question is I see a lot of people recommend not to connect all components to the redux store, but never sure why, for this, should I just put the IP-address in local-storage and clear() it everytime the user tries to log in? Anyway, I want to at least successfully implement Redux so I can use it in the future if need be.
The problem is that I have some buttons, which DO successfully change the state in the redux-store, and then other components once logged in make the requests to that particular server. But in the dropdown I just put in <Select />, upon any change, it then calls a function inside my component, the function runs and everything gets logged to console as it should, EXCEPT that the state in the redux-store doesnt change anymore, but as I can see it, its still using the same call to react-redux connect as the buttons were doing
So I have my actions.js:
export const DEV_ADDRESS = 'dev.example.com';
export const STAGE_ADDRESS = 'stage.exmaple.com';
export const PROD_ADDRESS = 'prod.example.com';
export function devServer() {
return {
type: DEV_ADDRESS,
};
}
export function stageServer() {
return {
type: STAGE_ADDRESS,
};
}
export function prodServer() {
return {
type: PROD_ADDRESS,
};
}
And my reducer.js:
import {
DEV_ADDRESS,
STAGE_ADDRESS,
PROD_ADDRESS,
} from '../actions/serverActions';
const initialState = {
serverAddress: PROD_ADDRESS,
};
export default function (state = initialState, action) {
switch (action.type) {
case DEV_ADDRESS:
console.log("REDUCER DEV");
return {
...state,
serverAddress: DEV_ADDRESS
};
case STAGE_ADDRESS:
console.log("REDUCER STAGE");
return {
...state,
serverAddress: STAGE_ADDRESS
};
case PROD_ADDRESS:
console.log("REDUCER PROD");
return {
...state,
serverAddress: PROD_ADDRESS
};
default:
return state;
}
}
Now in my LogIn.jsx:
import React, { PureComponent } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Button } from 'reactstrap';
import axios from 'axios';
import Select from 'react-select';
import { Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import EyeIcon from 'mdi-react/EyeIcon';
import Logo from '../../shared/img/logo/logo_light_2.svg';
import * as serverActionTypes from '../../redux/actions/serverActions';
class LogIn extends PureComponent {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
showPassword: false,
in: false,
};
}
componentDidMount() {
localStorage.clear();
}
.
.
.
.
.
handleEmailChange(event) {
this.setState({ email: event.target.value })
}
handlePasswordChange(event) {
this.setState({password: event.target.value})
handleChange = (selectedOption) => {
console.log("$$$$$$$$$$$$");
console.log(selectedOption.value);
console.log(this.props)
if (selectedOption.value === serverActionTypes.devServer().type) {
console.log("handle dev change");
{this.props.onSelectDevServerAddress}
}
else if (selectedOption.value === serverActionTypes.stageServer().type) {
console.log("handle stage change");
{this.props.onSelectStageServerAddress}
}
else if (selectedOption.value === serverActionTypes.prodServer().type) {
console.log("handle prod change");
{this.props.onSelectProdServerAddress}
}
};
render() {
const { handleSubmit } = this.props;
if (this.state.in === true) {
return <Redirect to={{pathname: "/dashboard"}} />;
}
return (
<div className="account">
<div className="account__wrapper">
<div className="account__card" align="center">
<img
src={Logo}
alt="example-logo"
height="35"
style={{marginBottom: '50px'}}
/>
</div>
<div className="account__card">
<form className="form form--horizontal" onSubmit={handleSubmit}>
<div className="form__form-group">
<span className="form__form-group-label">E-mail</span>
<div className="form__form-group-field">
<Field
name="email"
component="input"
type="email"
placeholder="example#mail.com"
value={this.state.email}
onChange={this.handleEmailChange.bind(this)}
/>
</div>
</div>
<div className="form__form-group">
<span className="form__form-group-label">Password</span>
<div className="form__form-group-field">
<Field
name="password"
component="input"
type={this.state.showPassword ? 'text' : 'password'}
placeholder="Password"
value={this.state.password}
onChange={this.handlePasswordChange.bind(this)}
/>
<button
className={`form__form-group-button${this.state.showPassword ? ' active' : ''}`}
onClick={e => this.showPassword(e)}
><EyeIcon />
</button>
</div>
</div>
<div className="form__form-group">
<span className="form__form-group-label">Server</span>
<div className="form__form-group-field">
<div className="form__form-group-input-wrap">
<Select
name='server_address_selector'
value='prod.example.com'
onChange={this.handleChange}
options={[
{ value: 'dev.example.com', label: 'Dev' },
{ value: 'stage.example.com', label: 'Stage' },
{ value: 'prod.example.com', label: 'Prod' },
]}
clearable={false}
className="form__form-group-select"
/>
</div>
</div>
</div>
<Button
color="success"
onClick={this.onLogin.bind(this)}
outline>
Sign In
</Button>
<Button
color="success"
onClick={this.props.onSelectDevServerAddress}
outline>
DEV
</Button>
<Button
color="success"
onClick={this.props.onSelectStageServerAddress}
outline>
STAGE
</Button>
<Button
color="success"
onClick={this.props.onSelectProdServerAddress}
outline>
PROD
</Button>
</form>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
ipAddress: state.server
}
}
const mapDispatchToProps = dispatch => ({
onSelectDevServerAddress: () => dispatch(serverActionTypes.devServer()),
onSelectStageServerAddress: () => dispatch(serverActionTypes.stageServer()),
onSelectProdServerAddress: () => dispatch(serverActionTypes.prodServer()),
});
LogIn = connect(
mapStateToProps,
mapDispatchToProps
)(LogIn);
export default reduxForm({
form: 'log_in_form'
})(LogIn);
The problem is that you didn't dispatch any action to the reducer. You have to actually invoke a functions from mapStateToDispatch in order to perform some update on the redux store state.
In the code below:
handleChange = (selectedOption) => {
console.log("$$$$$$$$$$$$");
console.log(selectedOption.value);
console.log(this.props)
if (selectedOption.value === serverActionTypes.devServer().type) {
console.log("handle dev change");
{this.props.onSelectDevServerAddress}
}
else if (selectedOption.value === serverActionTypes.stageServer().type) {
console.log("handle stage change");
{this.props.onSelectStageServerAddress}
}
else if (selectedOption.value === serverActionTypes.prodServer().type) {
console.log("handle prod change");
{this.props.onSelectProdServerAddress}
}
};
Instead of doing something like this:
{this.props.onSelectProdServerAddress}
You have to call that function:
this.props.onSelectProdServerAddress();
Now, your action will be dispatched to the reducer.

Redux can't handle data from 2 inputs

I'm trying to take control of state data of my component with one function. Im also using redux. But theres something wrong with redux or i don't see my mistake. Heres my component:
this.state = {
data: this.props.ui.users,
name: '',
email: ''
}
}
handleChange = (evt) => {
this.setState({ [evt.target.name]: evt.target.value });
}
componentWillReceiveProps = () => {
this.setState({
name: ''
})
}
render() {
return (
<div>
<form onSubmit={(e) => this.props.uiActions.addUser(e, this.state.name, this.state.email)}>
<input type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}/>
</form>
</div>
)
}
}
Everything works. But when i want to add another input that handles email input, action doesnt triggers. I can pass without any problem this.state.email with my data, for example "something" but redux doesnt see another input. With one input everything is fine.
The difference is that below first input i add
<input type="text"
name="email"
value={this.state.email}
onChange={this.handleChange}/>
and redux doesnt triggers action. Heres action that handles passing data:
export function addUser(e, name, email) {
return (dispatch, getState) => {
e.preventDefault()
console.log(email)
const { users } = getState().ui
dispatch({ type: UI_ACTIONS.ADD_USER, users:[...users, {id: users.length+1, name: name, email: email}] });
}
}
reducer:
export default (state = initialState, action) => {
switch (action.type) {
case UI_ACTIONS.SET_REPOS:
return { ...state, users: action.users };
case UI_ACTIONS.ADD_USER:
return {...state, users: action.users};
default:
return state;
}
};
What am i doing wrong? Here you can find my repo: https://github.com/KamilStaszewski/crudapp/tree/develop/src
There's a lot going on here that really needs to be revised. The way you've structured the app is anti-pattern (non-stardard/bad practice) and will cause you more headaches as the application becomes more dynamic.
Several things to consider:
You don't need Redux unless you're using heavily nested components (for this example, React state would be sufficient)
You should separate your containers (Redux connected functions/AJAX requests) from your components (any function that cares about how things look): https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
Keep form actions within the form's component (like e.preventDefault();) and utilize Redux state from within the reducer (you have access to Redux state within the reducer, so for the addUser action, there's no need to call Redux's getState();).
You don't need to dispatch an action if you're only returning the type and payload.
Always .catch() your promises. For some reason, there's a trend that up-and-coming developers assume that every promise will resolve and never throw an error. Not catching the error will break your app!
I've gone ahead and restructured the entire app. I encourage you to deconstruct it and follow the application flow, and then take your project and fix it accordingly.
Working example: https://codesandbox.io/s/zn4ryqp5y4
actions/index.js
import { UI_ACTIONS } from "../types";
export const fetchUsers = () => dispatch => {
fetch(`https://jsonplaceholder.typicode.com/users`)
.then(resp => resp.json())
.then(data => dispatch({ type: UI_ACTIONS.SET_REPOS, payload: data }))
.catch(err => console.error(err.toString()));
};
/*
export const handleNameChange = value => ({
type: UI_ACTIONS.UPDATE_NAME,
val: value
})
*/
/*
export const handleEmailChange = value => ({
type: UI_ACTIONS.UPDATE_EMAIL,
val: value
})
*/
export const addUser = (name, email) => ({
type: UI_ACTIONS.ADD_USER,
payload: { name: name, email: email }
});
components/App.js
import React from "react";
import UserListForm from "../containers/UserListForm";
export default ({ children }) => <div className="wrapper">{children}</div>;
components/displayUserList.js
import map from "lodash/map";
import React from "react";
export default ({ users }) => (
<table>
<thead>
<tr>
<th>ID</th>
<th>USER</th>
<th>E-MAIL</th>
</tr>
</thead>
<tbody>
{map(users, ({ id, name, email }) => (
<tr key={email}>
<td>{id}</td>
<td>{name}</td>
<td>{email}</td>
</tr>
))}
</tbody>
<tfoot />
</table>
);
containers/UserListForm.js
import map from "lodash/map";
import React, { Component } from "react";
import { connect } from "react-redux";
import { addUser, fetchUsers } from "../actions/uiActions";
import DisplayUserList from "../components/displayUserList";
class Userlist extends Component {
state = {
name: "",
email: ""
};
componentDidMount = () => {
this.props.fetchUsers();
};
handleChange = evt => {
this.setState({ [evt.target.name]: evt.target.value });
};
handleSubmit = e => {
e.preventDefault();
const { email, name } = this.state;
if (!email || !name) return;
this.props.addUser(name, email);
this.setState({ email: "", name: "" });
};
render = () => (
<div style={{ padding: 20 }}>
<h1 style={{ textAlign: "center" }}>Utilizing Redux For Lists</h1>
<form style={{ marginBottom: 20 }} onSubmit={this.handleSubmit}>
<input
className="uk-input"
style={{ width: 300, marginBottom: 10 }}
type="text"
name="name"
placeholder="Add user's name..."
value={this.state.name}
onChange={this.handleChange}
/>
<br />
<input
className="uk-input"
style={{ width: 300, marginBottom: 10 }}
type="text"
name="email"
placeholder="Add user's email..."
value={this.state.email}
onChange={this.handleChange}
/>
<br />
<button className="uk-button uk-button-primary" type="submit">
Submit
</button>
</form>
<DisplayUserList users={this.props.users} />
</div>
);
}
export default connect(
state => ({ users: state.ui.users }),
{ addUser, fetchUsers }
)(Userlist);
reducers/index.js
import { routerReducer as routing } from "react-router-redux";
import { combineReducers } from "redux";
import { UI_ACTIONS } from "../types";
const initialState = {
users: [],
name: "",
email: ""
};
const uiReducer = (state = initialState, { payload, type }) => {
switch (type) {
case UI_ACTIONS.SET_REPOS:
return { ...state, users: payload };
case UI_ACTIONS.ADD_USER:
return {
...state,
users: [...state.users, { id: state.users.length + 1, ...payload }]
};
default:
return state;
}
};
const rootReducer = combineReducers({
ui: uiReducer,
routing
});
export default rootReducer;
root/index.js
import React from "react";
import { browserHistory, Router } from "react-router";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { syncHistoryWithStore } from "react-router-redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
import routes from "../routes";
// CONFIG REDUX STORE WITH REDUCERS, MIDDLEWARES, AND BROWSERHISTORY
const store = createStore(rootReducer, applyMiddleware(thunk));
const history = syncHistoryWithStore(browserHistory, store);
// APP CONFIG'D WITH REDUX STORE, BROWSERHISTORY AND ROUTES
export default () => (
<Provider store={store}>
<Router
onUpdate={() => window.scrollTo(0, 0)}
history={history}
routes={routes}
/>
</Provider>
);
routes/index.js
import React from "react";
import { IndexRoute, Route } from "react-router";
import App from "../components/App";
import UserListForm from "../containers/UserListForm";
export default (
<Route path="/" component={App}>
<IndexRoute component={UserListForm} />
</Route>
);
types/index.js
export const UI_ACTIONS = {
UPDATE_NAME: "UPDATE_NAME",
INCREMENT_COUNT: "INCREMENT_COUNT",
SET_REPOS: "SET_REPOS",
ADD_USER: "ADD_USER",
UPDATE_NAME: "UPDATE_NAME",
UPDATE_EMAIL: "UPDATE_EMAIL"
};
export const TEST_ACTION = {
ACTION_1: "ACTION_1"
};
index.js
import React from "react";
import { render } from "react-dom";
import App from "./root";
import "uikit/dist/css/uikit.min.css";
render(<App />, document.getElementById("root"));
One thing I see that seems wrong to me is: input element isn't bind to this
<input type="text" name="name" value={this.state.name}
onChange={this.handleChange.bind(this)}
/>
Have you also trace what your event handler is doing in console?

Resources