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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }
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
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 inTodoApp
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.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
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. FinallyTodos.js
is your actual React component andindex.js
is where you create the store and callReactDOM.render
.react-redux
- Don't subscribe to state like that -- sure, it works but there's a more canonical pattern to use. That's where thereact-redux
comes in. Redux doesn't have anything to with React itself, but integrates well with React with thereact-redux
package. This is because Redux emits updates (viastore.subscribe
) when you dispatch and action and generate new state, whichreact-redux
can take advantage to readably connect your components to state without having to recallReactDOM.render
every time the store changes. This allows for scaling to larger applications with more components.So in
react-redux
there's theconnect
higher-order component that provides state to a certain child, connecting it to the Redux store. You provide it two arguments:mapStateToProps
andmapDispatchToProps
. What each does can be found in more detail at the GitHub page, but they essentially take values and thedispatch
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. Butconnect
has to get the store from somewhere, soreact-redux
also provides a<Provider>
component to pass down the Redux store to ancestors while not having to physically dostore.getState()
. This will get you this inindex.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; } }