Kamil Staszewski
Kamil Staszewski

Reputation: 346

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

Upvotes: 0

Views: 104

Answers (2)

Matt Carlotta
Matt Carlotta

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:

  • 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"));

Upvotes: 2

Demon
Demon

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

Related Questions