Reputation: 346
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
Upvotes: 0
Views: 104
Reputation: 19772
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:
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();
).dispatch
an action
if you're only returning the type
and payload
..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"));
Upvotes: 2
Reputation: 826
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?
Upvotes: 0