Skip to content

Instantly share code, notes, and snippets.

@laere

laere/List.jsx Secret

Last active February 22, 2016 15:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save laere/9485b4d02ad88692b8d4 to your computer and use it in GitHub Desktop.
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);
@FoxxMD
Copy link

FoxxMD commented Feb 18, 2016

Where are you calling dispatch()?

@laere
Copy link
Author

laere commented Feb 18, 2016

basically I imported the action creators into my main container that uses bind action creators let me add it here.

@laere
Copy link
Author

laere commented Feb 18, 2016

The actual action is dispatched in the addTodo(text) function in the main container.jsx file.

@FoxxMD
Copy link

FoxxMD commented Feb 18, 2016

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)

@laere
Copy link
Author

laere commented Feb 18, 2016

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.

@FoxxMD
Copy link

FoxxMD commented Feb 18, 2016

👍

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.

@laere
Copy link
Author

laere commented Feb 18, 2016

Wow awesome, alright, one more thing is would thunk replace the promise middleware I am using, or can I use both?

@FoxxMD
Copy link

FoxxMD commented Feb 18, 2016

You can use both!

From the middleware docs

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 the then() 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 after clientMiddleware so I've already got my api response!)
  • reduxRouterMiddleware -- Uses react-router-redux to handle route transitions.

@laere
Copy link
Author

laere commented Feb 18, 2016

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment