I am trying to create a simple form app, where there will be a textarea input and a submit button. Where, if I type something in the textarea and then click submit, the text that I just typed will show under the button inside a tag. When im doing this without Redux, it works fine, even after when I use Redux partly meaning when I manage only one state (input field state) using Redux it works great. But when i make two reducers, and two dispatches then problem happens. Here are my codes.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Provider from 'react-redux/es/components/Provider';
import {
createStore,
applyMiddleware,
combineReducers,
} from 'redux';
import { getInput, getOutput } from './reducer';
import { createLogger } from 'redux-logger';
import App from './App';
import reportWebVitals from './reportWebVitals';
const rootReducer = combineReducers({
getInput,
getOutput,
});
const logger = createLogger();
const store = createStore(
rootReducer,
applyMiddleware(logger)
);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
app.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
changeInput,
postOutput,
} from './action';
import {
Form,
Button,
Container,
} from 'react-bootstrap';
const mapStateToProps = (state) => {
return {
input: state.getInput.input,
output: state.getOutput.output,
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleInput: (event) =>
dispatch(changeInput(event.target.value)),
handleClick: (props) =>
dispatch(postOutput(props.output)),
};
};
class App extends Component {
// constructor() {
// super();
// this.state = {
// output: '',
// };
// }
// handleInput = (event) => {
// this.setState({ input: event.target.value });
// };
// handleClick = () => {
// this.setState({
// output: this.props.input,
// });
// };
render() {
return (
<div>
<Container>
{' '}
<Form>
<Form.Group controlId='exampleForm.ControlTextarea1'>
<div>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
marginBottom: '10px',
}}>
<Form.Control
as='textarea'
rows={5}
placeholder='enter something here'
onChange={this.props.handleInput}
style={{ width: '500px' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
variant='primary'
onClick={this.props.handleClick}>
Submit
</Button>
</div>
</div>
</Form.Group>
</Form>
</Container>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<h1 value={this.props.input}>
{this.props.output}
</h1>
</div>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
action.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
export const changeInput = (text) => ({
type: CHANGE_INPUT_FIELD,
payload: text,
});
export const postOutput = (text) => ({
type: POST_OUTPUT,
payload: text,
});
reducer.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
const initialStateInput = {
input: '',
};
const initialStateOutput = {
output: '',
};
export const getInput = (
state = initialStateInput,
action = {}
) => {
switch (action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign({}, state, {
input: action.payload,
});
default:
return state;
}
};
export const getOutput = (
state = initialStateOutput,
action = {}
) => {
switch (action.type) {
case POST_OUTPUT:
return Object.assign({}, state, {
output: action.payload,
});
default:
return state;
}
};
constant.js
export const CHANGE_INPUT_FIELD =
'CHANGE_INPUT_FIELD';
export const POST_OUTPUT = 'POST_OUTPUT';
changeInput action must be handled inside the component there is no reason to dispatch an action and handle it with reducer because reducer is for managing shared states.
Can you specify what is the "problem"?
The problem is not with actions, you cannot see the value because the value is set to undefined
In App.js you have to pass the correct value
onClick={this.props.handleClick}> must change as onClick={this.props.handleClick(this.props)}> otherwise props will be equal to event object in the line handleClick: (props) => dispatch(postOutput(props.output))
Still you won't see the value in UI because the output value is set to '' because you are not setting the input value to the output value in reducer.
My suggestion there must be another action that fires when submit button is clicked and sets the current input value to the input, then fire getOutput
Related
I have a simple app that so far only has a main login component, and a home page with a navbar which the user is supposed to be routed to once they login to the app. The user experience should be as follows: user selects name from blue dropdown menu, once that name is selected, they click the green login button which dispatches setLoggedInUser from loggedInSlice.js and sets the selected user as the authorize object For some reason , my state from my redux store is lost when I wrap my login button in a router link tag which leads to the home page , but it is not lost when that login button doesnt link anywhere. I want to understand why I am not retaining my redux authUser state once I navigate from the userLogin.js component to the Main.js Component.
Components:
App.js
import logo from "./logo.svg";
import "./App.css";
import TestComponent from "./components/TestComponent";
import Main from "./components/Main";
import { Routes, Route, Link } from "react-router-dom";
import { useState } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import UserLogin from "./components/UserLogin";
import Nav from "./components/Nav";
function App() {
return (
<div className="App">
<Routes>
<Route path="home" element={<Main />} />
<Route path="/" element={<UserLogin />} />
<Route path="testcomponent" element={<TestComponent />} />
</Routes>
</div>
);
}
export default App;
UserLogin.js
import React from "react";
import { Routes, Route, Link, useNavigate } from "react-router-dom";
import { Redirect } from "react-router-dom";
import { useState, useEffect } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import { useSelector, useDispatch } from "react-redux";
import "bootstrap/dist/css/bootstrap.min.css";
import { setLoggedInUser } from "../features/loggedInSlice";
export default function UserLogin() {
let navigate = useNavigate();
const users = useSelector((state) => state.users);
const state = useSelector((state) => state);
const dispatch = useDispatch();
const [user, setUser] = useState("Select A User");
const [authorizedUser, setauthorizedUser] = useState({});
const handleSelect = (e) => {
setUser(e.target.text);
};
function authorizeUser() {
setauthorizedUser(
users.filter((userName) => userName.firstName === user)[0]
);
}
useEffect(() => {
dispatch(setLoggedInUser(authorizedUser));
}, [authorizedUser]);
function authorizeLogin() {
console.log(state);
}
console.log(state);
return (
<div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Card style={{ width: "50%" }}>
<Card.Header>Would you Rather?</Card.Header>
<Card.Body>
<Card.Title>Special title treatment</Card.Title>
<Card.Text>
With supporting text below as a natural lead-in to additional
content.
</Card.Text>
</Card.Body>
<Dropdown className="d-inline mx-2" onClick={handleSelect}>
<Dropdown.Toggle
id="dropdown-autoclose-true"
style={{ width: "60%" }}
>
{user}
</Dropdown.Toggle>
<Dropdown.Menu style={{ width: "60%" }}>
{users.map((user, index) => (
<Dropdown.Item href="#" key={index}>
{user.firstName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<div>
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
</div>
</Card>
<button onClick={authorizeLogin}>click</button>
</div>
</div>
);
}
Main.js
import React from "react";
import NavBar from "./Nav";
import { Routes, Route, Link } from "react-router-dom";
export default function Main() {
return (
<div>
<NavBar />
</div>
);
}
Nav.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { Card, Button, Accordion, Dropdown, Nav } from "react-bootstrap";
export default function NavBar() {
const state = useSelector((state) => state);
console.log(state);
const loggedInFirstName = state.loggedIn.authUser.firstName;
return (
<div style={{ display: "flex" }}>
<Nav justify variant="tabs" defaultActiveKey="/home">
<Nav.Item>
<Nav.Link>Home</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>New Poll </Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>Leaderboard</Nav.Link>
</Nav.Item>
</Nav>
<h5 style={{ paddingLeft: "15px", paddingTop: "15px" }}>Welcome</h5>
</div>
);
}
Redux Store and Slices:
store.js
import { configureStore } from "#reduxjs/toolkit";
import usersReducer from "../features/usersSlice";
import loggedIn from "../features/loggedInSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
loggedIn: loggedIn,
},
});
loggedInSlice.js
import React from "react";
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
authUser: [],
};
export const loggedIn = createSlice({
name: "loggedIn",
initialState,
reducers: {
setLoggedInUser: (state, action) => {
console.log(action.payload);
state.authUser = action.payload;
},
},
});
export default loggedIn.reducer;
export const { setLoggedInUser } = loggedIn.actions;
usersSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = [
{
firstName: "matt",
age: 30,
total: 0,
},
{
firstName: "mike",
age: 25,
total: 0,
},
{
firstName: "steve",
age: 22,
total: 0,
},
];
export const usersReducer = createSlice({
name: "UsersSlice",
initialState,
});
export default usersReducer.reducer;
To summarize I need to understand why my authUser is added to my redux store only when the login button does not route to another component
code that adds the authUser
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
code that does not add the authUser
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
Issue
The issue here is that enqueued React state updates are asynchronously processed. The navigation action to "/home" when wrapping the login button with a Link occurs well before the enqueued state update is processed. React sees that the UserLogin component is no longer mounted and ignores (silently) the state update.
Solution
I suggest moving the "login" logic into an asynchronous action. This will return a Promise to the UserLogin component that can be waited on and upon successful authentication issue the navigation action to the "/home" path.
Example:
loggedInSlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const initialState = {
authUser: []
};
const loginUser = createAsyncThunk(
"loggedIn/loginHandler",
async (user, { dispatch }) => {
... asynchronous login logic ...
// if successfully logged in
dispatch(loggedIn.actions.setLoggedInUser(user));
return ... response object/success message/etc...;
}
);
export const loggedIn = createSlice({
name: "loggedIn",
initialState,
reducers: {
setLoggedInUser: (state, action) => {
state.authUser = action.payload;
}
}
});
export default loggedIn.reducer;
export const { setLoggedInUser } = loggedIn.actions;
export { loginUser };
UserLogin
Dispatch the login action directly in the button's onClick handler, wait for resolved Promise, then navigate.
...
import { loginUser } from "../features/loggedInSlice";
export default function UserLogin() {
const navigate = useNavigate();
const [user, setUser] = useState("Select A User");
const users = useSelector((state) => state.users);
const state = useSelector((state) => state);
const dispatch = useDispatch();
...
function authorizeUser() {
const selectedUser = users.find((userName) => userName.firstName === user);
dispatch(loginUser(selectedUser))
.unwrap()
.then((response) => {
console.log({ response });
navigate("/home");
});
}
...
return (
<div>
<div ...>
<Card ...>
...
<div>
<Button
...
onClick={authorizeUser}
>
Login
</Button>
</div>
</Card>
...
</div>
</div>
);
}
How to update the state of the props when a user likes a post?
The props would need to automatically update when a user clicks like.
Currently, a user can like a post, and only on page refresh I am able to see the updated number of likes, which shows on
{this.props.likeCount}
What Component lifecycle would be best for seeing the updated props without refreshing the page? this application is using redux.
Like.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className="fa fa-heart-o">
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.props.like}>Like </a>
{this.getLikes(this.props.postId)}
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but I just call it credentials but it should be called something more
// specific.
getLikeCount: (id) => dispatch(getLikeCount(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
Actions.js
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data);
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
Reducer
import { GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:[],
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
default:
return state
}
}
edit(im getting a wierd infinite post loop)
wierd error
Update the code to the following code.
GET_LIKES_COUNT handles the api action, of getting the number of likes for a post.
Without it, it will be always set to 0 likes on render.
ADD_LIKE action gives it the functionality of updating the state without refreshing the page.(i know that their is more specific term they call this in react, maybe its re-rendering) Update the state without re-rendering the component as well as the most important part which is making the api call to the backend to allow the user to like a post. We set likes to 0 to make it possible to upvote the state and it to have it updated without refresh.
Thanks for the assistance #novonimo.
Reducer
import { GET_LIKES_COUNT, ADD_LIKE} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
// get number of likes from api
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
return({
...state,
likes: state.likes + 1
})
default:
return state
}
}
Actions
export const postLike = (id) => {
return (dispatch) => {
// console.log(userId);
return Axios.post('/api/posts/like', {
postId: id
}).then( (like) => {
dispatch({type: ADD_LIKE})
// console.log('you have liked this', like)
}).catch( (err)=> {
console.log('there seem to be an error', err);
})
}
}
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data);
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
PostItem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, getLikeCount, postLike} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
clickLike = (id) => () => {
this.props.postLike(id);
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, likes} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
<Like like={this.clickLike(id)} postId={id}/>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(PostItem);
Like.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className="fa fa-heart-o">
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.props.like}>Like </a>
{this.getLikes(this.props.postId)}
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
getLikeCount: (id) => dispatch(getLikeCount(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
React philosophy is based on remove Refresh pages on changes.
so forget refresh in all react app.
in the component you can change code like this:
handleAddUpVote = ()=> this.props.dispatch(addUpVote())
return(
<div onClick={this.handleAddUpVote}> sth </div>
)
and in action:
const ADD_UP_VOTE = "ADD_UP_VOTE";
const addUpVote = ({type: ADD_UP_VOTE});
export {ADD_UP_VOTE, addUpVote}
and finally, change your reducer:
initialState={
voteCounter: 0
}
const Reducer = (state=initialState, action) => {
switch(action.type){
case(ADD_UP_VOTE):
return{
...state,
voteCounter: state.voteCounter + 1
};
}
}
In my project, I am persisting state of option buttons in redux. There are different buttons group and I am handling their click action in single function handleClick. But it seems like it’s not working. Should I create a different handler for each button group? Can anyone suggest the best solution?
code :
import React, { Component } from "react";
import { Button } from "semantic-ui-react";
import { withRouter } from "react-router";
import Answers from "../Answers/Answers";
import { handleClick } from "../../actions/handleClickAction"
import { connect } from 'react-redux'
class Section extends Component {
handleClick = event => {
this.props.handleClick(event);
};
render() {
console.log(this.state);
let styles = {
width: '50%',
margin: '0 auto',
marginBottom: '15px'
}
const { history } = this.props;
const { que1, que2, que3 } = this.state;
return (
<>
<p>1. I was stressed with my nerves on edge.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que1} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>2. I lost hope and wanted to give up when something went wrong.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que2} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>3. I feel very satisfied with the way I look and act</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que3} style={{ backgroundColor: 'red' }} />
</Button.Group>
<p />
{` `}
<Button
disabled={!que1 || !que2 || !que3}
onClick={() => history.push("/section2", [this.state])}
>
NEXT
</Button>
</>
);
}
}
export default withRouter(connect(null, { handleClick })(Section));
main.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
import { Provider } from 'react-redux'
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById("root"));
index.js
import { combineReducers } from "redux";
import selectOptionReducer from "./selectOptionReducer";
export default combineReducers({
selectOption: selectOptionReducer
})
selectOptionReducer.js
import { SELECT_OPTION } from "../actions/types"
const initialState = {
que1: "",
que2: "",
que3: "",
que4: "",
que5: ""
}
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_OPTION:
return {
...state,
que1: action.payload,
que2: action.payload,
que3: action.payload,
que4: action.payload,
que5: action.payload
};
default:
return state;
}
}
store.js
import { createStore } from 'redux'
import selectOptionReducer from "./reducers/selectOptionReducer";
const store = createStore(selectOptionReducer);
export default store;
handleClickAction.js
import { SELECT_OPTION } from "./types"
export const handleClick = e => {
return {
type: SELECT_OPTION,
payload: e.target.attributes.getNamedItem("data-key").value
}
}
output :
From what I can see, that reducer would be setting the state for all questions to the same answer on every action.
You need a way to specify which question is being answered.
I would go with something like the following which creates a custom onClick handler for each question and passes the question id to the action creator to be included in the reducer payload. The reducer then uses that id to only update the question being answered.
(untested)
selectOptionReducer.js
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_OPTION:
const { questionId, value } = action.payload;
return { ...state, [questionId]: value };
default:
return state;
}
}
handleClickAction.js
export const handleClick = ({ questionId, e }) => {
return {
type: SELECT_OPTION,
payload: { questionId, value: e.target.attributes.getNamedItem("data-key").value }
}
}
component
class Section extends Component {
handleClick = questionId => e => {
this.props.handleClick({ questionId, e });
};
...
<Button.Group widths="5" onClick={this.handleClick("que1")} style={styles}>
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?
Redux: Undefined Prop
Undefined Prop
I want to pass down a prop from my container component, down to my presentational component via props, but I haven't been able to do so without the prop being undefined. Why isn't the number prop being passed down to the presentational component?
Creating the store with the initial stae and rootreducer:
import {createStore, applyMiddleware} from 'redux';
...
import rootReducer from './reducers/rootReducer';
const initialState = {
multiply: 2,
number: 1
}
export const store = createStore(
...initialState,
rootReducer,
applyMiddleware(logger(), thunk),
window.devToolsExtension && window.devToolsExtension()
);
Reducer for multiply actions and divide actions:
const multiplyReducer = (state = {}, action) => {
switch (action.type) {
case 'MULTIPLY':
return state = {
...state,
number: state.number * action.payload
}
case 'DIVIDE':
return state = {
...state,
number: state.number / action.payload
}
default:
return state;
}
}
export default multiplyReducer;
Root reducer:
import {combineReducers} from 'redux';
import multiplyReducer from './multiplyReducer';
const rootReducer = combineReducers({
multiply: multiplyReducer
});
export default rootReducer;
Wrapping the app in a Provider:
import Multiplier from './ContainerComponents/Multiplier';
import { BrowserRouter, Route } from 'react-router-dom';
const App = ({ store }) => (
<BrowserRouter>
<Route path="/" exact component={Multiplier}/>
</BrowserRouter>
);
export default App;
Actions:
export const multiplyAction = {
type: 'MULTIPLY',
payload: 2
}
export const divideAction = {
type: 'DIVIDE',
payload: 2
}
Container Component:
import MultiplierDisplay from '../StyleComponents/MultiplierDisplay';
import {connect} from 'react-redux';
import {multiplyAction, divideAction} from '../Redux/actions/multiplyActions';
class Multiplier extends React.Component {
render() {
return (<MultiplierDisplay {...this.props} />)
}
};
const mapStateToProps = (state) => {
return {multiply: state.multiply, number: state.number}
};
const mapDispatchToProps = (dispatch) => {
return {
handleClick: (event) => {
dispatch(multiplyAction)
},
handleClick2: (event) => {
dispatch(divideAction)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Multiplier);
Presentational Component:
const MultiplierDisplay = (props) => {
return (
<div
className="top"
style={{
alignContent: 'center',
justifyContent: 'center'
}}>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
<h1 style={{
marginLeft: 20
}}>
Multiply the count:
</h1>
<p style={{
fontSize: '3em'
}}>
Count: {props.number}
</p>
<button
style={{
marginLeft: 100,
width: '20%',
height: '20%'
}}
onClick={props.handleClick}
title="Multiply">
Multiply
</button>
<button
style={{
marginLeft: 50,
width: '20%',
height: '20%'
}}
onClick={props.handleClick2}
title="Divide">
Divide
</button>
</div>
)
}
export default MultiplierDisplay;
Your prop number isn't passed on to the presentation component being you haven't passed it down from the container,
class Multiplier extends React.Component {
render() {
return (<MultiplierDisplay/>) // this is where you are not passing it down
}
};
If you need to pass all the props down to the MultiplierDisplay component you can write
class Multiplier extends React.Component {
render() {
return (<MultiplierDisplay {...this.props}/>)
}
};
or if you want to pass selected props down, you can write them like
class Multiplier extends React.Component {
render() {
const { multiply, number, handleClick, handleClick2 } = this.props;
return (<MultiplierDisplay multiply={multiply} number={number} handleClick={handleClick} handleClick2={handleClick2}/>)
}
};
Also you need to get the state from state.multiple you are having number and multiply in multiplierReducer which you are using as multiply
const mapStateToProps = (state) => {
return {multiply: state.multiply.multiply, number: state.multiply.number}
};
const mapDispatchToProps = (dispatch) => {
return {
handleClick: (event) => {
dispatch(multiplyAction)
},
handleClick2: (event) => {
dispatch(divideAction)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Multiplier);
You can use this below to your code
const mapStateToProps = (state) => {
return {multiply: state.multiply.multiply, number: state.multiply.number}
};
There is a problem in how you are mapping you state to props of your Multiplier component as written in combineReducer you have added multiply as the state name for your multiplyReducer, hence in your mapStateToProps function you need to can access your multiplyReducer state by the following code:
const mapStateToProps = (state) => {
return {multiply: state.multiply.multiply, number: state.multiply.number}
};
Thanks I hope this helps.