-
-
Save laere/9485b4d02ad88692b8d4 to your computer and use it in GitHub Desktop.
export const AddTodo = (text) => { | |
const post = axios.post(API, { text }); | |
//logging purposes | |
post.then(function(response) { | |
return console.log(response); | |
}, function(err) { | |
console.log('Error') | |
return { | |
type: REMOVE_TODO, | |
id | |
}; | |
}); | |
return { | |
type: ADD_TODO, | |
id: nextId++, | |
payload: text | |
}; | |
}; |
import React, { Component } from 'react'; | |
import ListItem from '../components/ListItem'; | |
export default class List extends Component { | |
constructor(props) { | |
super(props); | |
//reference these functions | |
this.handleOnClick = this.handleOnClick.bind(this); | |
this.clearInput = this.clearInput.bind(this); | |
} | |
handleOnClick(e) { | |
e.preventDefault(); | |
//save input value | |
let inputValue = this.refs.inputfield.value; | |
if(inputValue === '') return; | |
//pass input value to callback | |
this.props.addTodo(inputValue); | |
} | |
clearInput() { | |
//clears input on clear button | |
this.refs.inputfield.value = ''; | |
console.log('This clears the input value'); | |
} | |
renderTodos() { | |
//if items is passed as props | |
if(this.props.items) { | |
//map each item to have item id and item text | |
return this.props.items.map((item) => { | |
return ( | |
//pass removeTodo func to child pass text to child | |
<ListItem removeTodo={this.props.removeTodo} key={item.id} text={item.text} /> | |
); | |
}); | |
} | |
} | |
render() { | |
return ( | |
<div> | |
{/*initiate callback on submit*/} | |
<form onSubmit={this.handleOnClick}> | |
<input type="text" ref="inputfield"/> | |
<input type="submit" value="Add" className="btn btn-primary" /> | |
<input onClick={this.clearInput} type="submit" value="Clear" className="btn btn-primary" /> | |
</form> | |
<ul> | |
{/*render todos*/} | |
{this.renderTodos()} | |
</ul> | |
</div> | |
); | |
} | |
} |
import React, { Component } from 'react'; | |
import List from '../containers/List'; | |
//action creators | |
import { AddTodo, RemoveTodo, FetchTodos } from '../reducers/reducer_todos'; | |
//reference to store | |
import { connect } from 'react-redux'; | |
//allows use of action creators directly (without dispatch wrapper) | |
import { bindActionCreators } from 'redux'; | |
class App extends Component { | |
constructor(props) { | |
super(props); | |
//reference these functions | |
this.addTodo = this.addTodo.bind(this); | |
this.removeTodo = this.removeTodo.bind(this); | |
} | |
componentWillMount() { | |
// fetch todos | |
//then dispatch them to action creator | |
this.props.FetchTodos(); | |
} | |
addTodo(text) { | |
//add to do | |
this.props.AddTodo(text); | |
console.log('This is the text passed to the AddTodo AC: ' + text); | |
} | |
removeTodo(id, e) { | |
//remove todo | |
this.props.RemoveTodo(id); | |
console.log('This is the ID of the removed todo: ' + id); | |
console.log(e.type, e.which, e.timeStamp); | |
} | |
render() { | |
return ( | |
<div> | |
<h1>Todo List</h1> | |
{/*pass down action creators | |
pass down items state*/} | |
<List | |
items={this.props.items} | |
addTodo={this.addTodo} | |
removeTodo={this.removeTodo} | |
/> | |
</div> | |
); | |
} | |
} | |
let mapDispatchToProps = (dispatch) => { | |
return bindActionCreators( | |
{ | |
AddTodo: AddTodo, | |
RemoveTodo: RemoveTodo, | |
FetchTodos: FetchTodos | |
}, dispatch); | |
}; | |
let mapStateToProps = (state) => { | |
return { | |
items: state.items, | |
text: state.text | |
}; | |
}; | |
export default connect(mapStateToProps, mapDispatchToProps)(App); |
basically I imported the action creators into my main container that uses bind action creators let me add it here.
The actual action is dispatched in the addTodo(text) function in the main container.jsx file.
So the code you have will work, but it won't do what you think it's going to do.
This page in the redux docs describes the async stuff I talked about but in less detail. The gist of it is that the way you have you code setup now is fine for synchronous, but won't work for asynchronous (api calls).
bindActionCreators
is a great convenience function but it's robbing you of more granular use of dispatch()
. What is does is take all the functions you pass it and wrap them in dispatch()
-- so when you call this.props.AddTodo(text)
it's really doing dispatch(this.props.AddTodo(text))
. The return value of AddTodo
is what is dispatched.
In your case it's working becuause in action creator.jsx
you return
return {
type: ADD_TODO,
id: nextId++,
payload: text
};
immediately. If instead you tried to do the "from the api response, later" approach action creator.jsx
wouldn't return anything because it would be waiting for the promise to resolve.
So, in order to use api calls you need to pass dispatch to your function rather than wrapping your function in dispatch. Check out the Async Action Creators section in this redux doc page for an example and the Async Action Creators section in Reducing Boilerplate doc page.
From the page:
“Thunk” middleware lets you write action creators as “thunks”, that is, functions returning functions. This inverts the control: you will get dispatch as an argument, so you can write an action creator that dispatches many times.
You can use redux-thunk middleware and simply use it with applyMiddleware
.
Then, when you write an action creator it will look like this:
export function AddTodo(text) {
return (dispatch, getState) => {
const post = axios.post(API, { text });
//logging purposes
post.then(function(response) {
return console.log(response);
}, function(err) {
console.log('Error')
dispatch({
type: REMOVE_TODO,
id
});
});
dispatch({
type: ADD_TODO,
id: nextId++,
payload: text
});
};
};
So now you can control when you want to dispatch, and how many times you want to dispatch, from an action creator. In your original action creator.jsx
once it returned a value it was done. dispatch()
was called with the returned value and the promise was basically ignored. Using the above action creator it will dispatch immediately (ADD_TODO
) and then if the promise is rejected will dispatch again (REMOVE_TODO
)
OHHHH alright, so basically bindActionCreators is something I shouldn't use if I am messing with async calls? I see what you're saying is that being able to use dispatch sort of gives me more freedom, since dispatch wont be bind to those action creators because of bind ActionCreators? I am going to have to read this again, and check out the documentation some more. But I am sort of seeing it.
👍
Though IMO I would forego bindActionCreators
all together and thunk all the things. You can use thunk for synchronous calls too since all it's doing is giving you the ability to call dispatch whenever you want.
Wow awesome, alright, one more thing is would thunk replace the promise middleware I am using, or can I use both?
You can use both!
The best feature of middleware is that it’s composable in a chain. You can use multiple independent third-party middleware in a single project.
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.
Be careful though because the order you put them in will matter. Since they are chained each middleware will receive the result of the previous middleware.
In your scenario I would put thunk first, then promise middleware. Thunk will allow to dispatch at will, and then if the object you return is in the form accepted for the promise middleware, it will utilize that.
Take a look at the source for redux-promise (it's just 1 file!)
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => dispatch({ ...action, payload: error, error: true })
)
: next(action);
};
}
All it does:
- Check to see if the passed action is in FSA form
- If yes, checks if
payload
is a promise - If yes, executes the promise and when the promise is resolved, executes
dispatch
from thethen()
block.
If it's not a promise then it simply passes the action on to the next thing with next(action)
, but if it it waits to resolve and then dispatches a new action (which the next middleware/thing receives). If there is another middleware in order after it then that receives action
, if not then the reducer gets the action and does its thing.
If you're getting more familiar with promises and how redux flows you should notice that redux-promise is super similar to both what you're already doing with axios and most of the examples I gave on reddit. It's removing the boilerplate you would need to write to handle the promise in each action creator and instead allows you to write an action with a payload
that is a promise
. Then it takes care of executing the promise and dispatching actions depending on the result for you.
Also take a look at redux-promise-middleware as it accomplishes the same thing but with a slightly different approach to how it handles the result of a resolved promise. (I prefer redux-promise-middleware).
As an example of using multiple middlewares -- in the app I am building I have this middleware stack (in order):
- clientMiddleware -- My own custom middleware that combines redux-promise-middleware and thunk-middleware with the middleware example I cited in one of the reddit posts (the one from the boilerplate). It lets me thunk, resolve and dispatch a promise, but also return that promise from the action creator so I can do server-side rendering.
- storageMiddleware -- Let's me store some parts of my app state to local storage (in the browser) so I can persist some UI preferences.
- notificationMiddleware -- A small middleware I built that uses re-notif and listens for actions with a
_REJECTED
type suffix and then attempts to parse an error from the payload and show a toastr notification for a failed api response. (Remember, this happens afterclientMiddleware
so I've already got my api response!) - reduxRouterMiddleware -- Uses react-router-redux to handle route transitions.
Wow awesome, I will definitely use both thunk and redux-promise-middleware in the future. Thanks again for the help, I learned an absolute ton more about redux and other things, and really feel confident using it again in the future!
Where are you calling
dispatch()
?