Skip to main content
Code Review

Return to Answer

added 5 characters in body
Source Link
Andrew Li
  • 271
  • 1
  • 7
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'));
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos }); //provides this.props.todos
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete(index) {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos); //connects component to Redux store
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO: //import from ../actions/actions.js
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'));
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos }); //provides this.props.todos
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete() {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos); //connects component to Redux store
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO: //import from ../actions/actions.js
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'));
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos }); //provides this.props.todos
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete(index) {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos); //connects component to Redux store
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO: //import from ../actions/actions.js
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
added 100 characters in body
Source Link
Andrew Li
  • 271
  • 1
  • 7
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'));
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     ...
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos }); //provides this.props.todos
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete() {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos); //connects component to Redux store
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO: //import from ../actions/actions.js
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'))
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     ...
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos });
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete() {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos);
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO:
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'));
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos }); //provides this.props.todos
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete() {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos); //connects component to Redux store
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO: //import from ../actions/actions.js
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
Source Link
Andrew Li
  • 271
  • 1
  • 7

I'm not exactly sure of your environment (I'm going to assume you're using Node), and with this assumption, I want to address a few things starting with specific things, then more general:

Specific

  1. You shouldn't be keeping inputText in the global store. This creates lots of store dispatches and updates which are completely unnecessary and costly. Instead, pass the input value once you want to add a todo. This would entail the use of state in TodoApp to control the input so you'd have to change to an ES6 class, but that's favorable to the unnecessary updating of the Redux store.

  2. There's no need to do the two lines:

     const numbers = range(todos.length);
     const zipped = zip(numbers, todos);
    

    You can shorten your Array#map callback to this without those two lines:

     {
     todos.map(({ text }, index) =>
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
    

General

  1. Organization - Separate your Redux reducers, actions, etc. into different files for organization. I'm not sure if your project is currently in one file or not, but this is general advice. This app may be a minor one, but it's good practice for larger apps to keep things organized. For example, a directory structure I would use in this case looks like this:

     src/
     ├── actions/
     │ ├── actions.js
     │ └── actionCreators.js
     ├── reducers/
     │ └── todos.js
     ├── components/
     │ └── Todos.js
     └── index.js
    

    Where action.js contains all your actions, exported using ES6 modules (which I name accordingly for uniformity):

     export const DELETE_TODO = 'DELETE_TODO';
     export const ADD_TODO = 'ADD_TODO';
    

    And actionCreators.js contains all the action creators, in other words, returns an action to be dispatched. Right now, you're using object literals left and right, which isn't very consistent. It also doesn't convey a message at a glance. I would use action creators instead, for example, if you're adding a todo:

     export const addTodo = text => ({
     type: ADD_TODO,
     text
     });
    

    Then you can dispatch the action like so:

     dispatch(addTodo('foo'))
    

    Instead of like this everytime:

     dispatch({ type: ADD_TODO, text: 'foo' });
    

    This can be applied also to another action creator named deleteTodo. And in this new structure, todos.js would be a separate file for your reducer. This is good practice in preparation for larger apps, where you may have more than one reducer. Finally Todos.js is your actual React component and index.js is where you create the store and call ReactDOM.render.

  2. react-redux - Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where the react-redux comes in. Redux doesn't have anything to with React itself, but integrates well with React with the react-redux package. This is because Redux emits updates (via store.subscribe) when you dispatch and action and generate new state, which react-redux can take advantage to readably connect your components to state without having to recall ReactDOM.render every time the store changes. This allows for scaling to larger applications with more components.

    So in react-redux there's the connect higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments: mapStateToProps and mapDispatchToProps. What each does can be found in more detail at the GitHub page, but they essentially take values and the dispatch function from the Redux store and pass them as props to a certain component, the same as what you're doing now, except all in a neat package. But connect has to get the store from somewhere, so react-redux also provides a <Provider> component to pass down the Redux store to ancestors while not having to physically do store.getState(). This will get you this in index.js:

     import { Provider } from 'react-redux';
     import Todos from './components/Todos.js';
     import todos from './reducers/todos.js';
     ...
     const store = createStore(todos);
     ReactDOM.render(
     <Provider store={store}>
     <Todos />
     </Provider>,
     ...
     );
    

    Then in Todos.js:

     class Todos extends React.Component {
     constructor() {
     this.state = {
     inputText: ''
     };
     }
     handleChange(e) {
     this.setState({
     inputText: e.target.value
     });
     }
     render() {
     const { todos, onAdd, onDelete } = this.props;
     return (
     <div>
     <h1>TODO!</h1>
     <ul>
     {
     todos.map(({ text }, index) => (
     <li key={index}>
     {text}
     <button onClick={() => onDelete(index)}>delete</button>
     </li>
     )
     }
     <li>
     <input type="text" value={this.state.inputText} onChange={e => this.handleChange(e)}/>
     <button onClick={() => onAdd(this.state.inputText)}>add</button>
     </li>
     </ul>
     </div>
     );
     }
     }
     const mapStateToProps = ({ todos }) => ({ todos });
     const mapDispatchToProps = dispatch => ({
     onAdd(text) {
     dispatch(addTodo(text)); //import this from ../actions/actionCreators.js
     },
     onDelete() {
     dispatch(deleteTodo(index));
     }
     });
     export default connect(mapStateToProps, mapDispatchToProps)(Todos);
    

    Finally, this will clean up the reducer in todos.js for you:

     export default function todos(state = { todos: [] }, action) {
     switch(action.type) {
     case ADD_TODO:
     return {
     todos: state.todos.concat({
     text: action.text
     })
     };
     case DELETE_TODO:
     const filtered = state.todos.filter((e, index) => index !== action.index);
     return {
     todos: filtered
     };
     default:
     return state;
     }
     }
    
default

AltStyle によって変換されたページ (->オリジナル) /