Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux screencast series on Egghead #1065

Closed
gaearon opened this issue Nov 23, 2015 · 69 comments
Closed

Redux screencast series on Egghead #1065

gaearon opened this issue Nov 23, 2015 · 69 comments

Comments

@gaearon
Copy link
Contributor

gaearon commented Nov 23, 2015

If you're following Redux repo but haven't dived into it yet, or are confused about some of its fundamental aspects, you'll be happy to learn that Egghead has just published my Getting Started with Redux series.

It covers the same topics as the “Basics” part of the docs, but hopefully dives a little bit deeper and ensures you really understand the Redux fundamentals.

I plan to create more content about Redux on Egghead—this time, for subscribers only. It will feature more advanced topics. If you have particular suggestions, please let me know in this thread!

@stormpat
Copy link

👍

@smashercosmo
Copy link

  1. Local component's state vs. global state
  2. Action handled by multiple reducers vs. 1-to-1 action-reducer relation
  3. Handling action chains (especially async ones), when second action should be triggered right after the first one is finished.
  4. Optimization technics to prevent unnecessary rerenering (batching actions, reselect etc.).

@iZaL
Copy link

iZaL commented Nov 24, 2015

👍

@dchambers
Copy link

I've just watched the first 16 episodes in the series and think they're an excellent resource that we'll likely be using for internal training at the company I work for. However, the fact you only ever used plain objects and deep-freeze for your state-tree, and never use or refer to immutable libraries like immutable-js makes me wonder if you are encouraging people down this path, or whether this just makes presenting the ideas easier?

Would be great to know your thoughts on this.

@gaearon
Copy link
Contributor Author

gaearon commented Nov 24, 2015

Throwing Immutable into the mix would confuse beginners who'd need to learn to differentiate between Redux and Immutable API, may assume Immutable is required, etc. It's a good idea for a couple of advanced lessons though!

@dchambers
Copy link

Throwing Immutable into the mix would confuse beginners who'd need to learn to differentiate between Redux and Immutable API, may assume Immutable is required, etc. It's a good idea for a couple of advanced lessons though!

Okay, makes sense. Just good to know your thinking behind this decision :-).

@cateland
Copy link

What @smashercosmo said 👍

@wpannell
Copy link

Unit testing. TDD.

@Restuta
Copy link
Contributor

Restuta commented Nov 24, 2015

What @cateland said 👍

@nikek
Copy link

nikek commented Nov 24, 2015

I must say I’m very impressed by you skill set, being able to create excellent js libraries with extensive documentation and concise tutorials in this manner.

After viewing all the videos I have a bunch of feedback on the specific videos that I’ll send by mail. The conclusion though is that this is so much more than an introduction to Redux. You cover code practices and component architecture and more. It is really really awesome and very pedagogical. I think the videos can be divided into three groups though. Redux basic, redux behind the scenes, and redux with react.

For upcoming videos I’d like to see less react specific concepts and more on async actions and tests.

Remarkably well done, keep it up! :)

@raquelxmoss
Copy link

In video 21 you note that the TodoApp component no longer needs to be a class, it can be a function. It would be great if you could explain how you came to this realisation -- why is this a suitable candidate to be a functional component, and what benefit does this give?

@harmony7
Copy link

This is so good.

Item 3 in @smashercosmo 's list is something I'd like to know as well.

@nathanielks
Copy link

what @wpannell said! Unit Testing/TDD!

Would also love to see videos on the topics covered in the Advanced docs.

@gaearon
Copy link
Contributor Author

gaearon commented Nov 26, 2015

What specifically are you interested in with regards to unit testing?
Lessons 5, 11, 12 give an idea of how to unit test the reducers.

@nathanielks
Copy link

...that's a good question. Would the process change very much when they're mocha tests?

@gaearon
Copy link
Contributor Author

gaearon commented Nov 26, 2015

Not really. But I guess that's a good topic for a series of advanced lessons. Unit testing reducers, action creators, components etc.

@farzd
Copy link

farzd commented Nov 26, 2015

Firstly, great videos. Already understand redux but it was refreshing.
If you're creating more advanced videos, may i suggest async actions / middleware.
Don't think unit testing really needs any coverage. You can just called your reducer functions and assert on them?

@nathanielks
Copy link

Yeah, I guess it really isn't much else other than wrapping the expects in mocha tests. :thumbs up:

Thankfully everything is so simple!

@mikebarnhardt
Copy link

Immutability with object ID hashes (e.g. [post._id]: {...post}).

I find myself relying too heavily on a reduce function to take an array of API entities and produce an ID hash with it. I know that normalizr will handle some of this, but I'd like videos similar to the EggheadIO videos where you take us from point A to B. It is not solely a Redux-based thing, but is heavily intertwined.

@gaearon
Copy link
Contributor Author

gaearon commented Nov 30, 2015

@raquelxmoss

In video 21 you note that the TodoApp component no longer needs to be a class, it can be a function. It would be great if you could explain how you came to this realisation -- why is this a suitable candidate to be a functional component, and what benefit does this give?

That was actually some sloppy lesson planning on my part. I didn't realize until too late that I could've made it a functional component from the very start. The benefit for using functions over classes is simplicity, so do this anytime you can. Basically if you don't need lifecycle hooks like componentDidMount or state, use functional components.

@harmony7
Copy link

harmony7 commented Dec 2, 2015

I would find a lesson on how we can split these pieces of code up into an actual directory structure to be helpful. I know that as long as everything is there it's probably going to work but maybe recommended conventions of naming folders (component / container / store / reducer etc), what kinds of files go into which folder, and so on would be nice.

@mikebarnhardt
Copy link

I'd also like advanced reducer composition.

@sompylasar
Copy link

Reusing complex components (that are comprised of a reducer or multiple reducers, a set of actions, action creators, maybe accessing some server-side API, several React components -- like redux-form, but more real-app-specific). This also includes organizing modular directory structure.

@cbenz
Copy link

cbenz commented Dec 2, 2015

Watched the whole thing, very good! I appreciated the use of ES6/7 syntax (Object.assign-like), React 0.14 function components and avoiding Immutable things.

Perhaps a video explaining the recommended code architecture.

And updating the doc to use ES6/7 syntaxes? (are PR welcome in that direction?)

@SpencerCDixon
Copy link
Contributor

Unit testing, creating API Middleware, doing OAuth/some type of user authentication with Redux, using Immutable with redux (how to get it set up, best practices etc.)

@tbloncar
Copy link

tbloncar commented Dec 5, 2015

This series serves as a great introduction to Redux, the single-atom state, and related philosophies. I thought you did a nice job of homing in on the core principles whilst avoiding inducing cognitive overload. The environment you worked in is also easily replicable, which makes the hands-on portion all the more approachable.

@sompylasar
Copy link

@gaearon What do you think of structuring actions in the example videos as a wannabe-standard { type: string, payload: Object } right from the beginning? I'm talking about the counter list example, where the payload is put onto the action object itself; { type: string, index: number }. This looks like an anti-pattern to me.

@gaearon
Copy link
Contributor Author

gaearon commented Dec 6, 2015

What do you think of structuring actions in the example videos as a wannabe-standard { type: string, payload: Object } right from the beginning? I'm talking about the counter list example, where the payload is put onto the action object itself; { type: string, index: number }. This looks like an anti-pattern to me.

It's not an anti-pattern in any way. It's a normal action object. FSA is fine but it's a convention. Nothing in Redux itself depends on or benefits from this convention, so we don't want to enforce it.

People used to think all sorts of magic things about payload, source in original Flux documentation. They blindly copied these things without understanding why they exist and carefully assessing whether they even need them. Later they complained about Flux being complex and verbose, when really in many cases they copied the verbose (but non-essential) parts themselves.

In these lessons I only teach the essential parts of Redux. Note how I don't introduce constants—because people concentrate on them too much, and miss that they don't really matter. You're more likely to understand the benefits of constants after you make a few typos in strings, rather than if I put them into the tutorial videos from the start. I think the same goes for other conventions like FSA—by all means, use it if you find it convenient, but I won't preach it if the lessons don't demand it.

@jugimaster
Copy link

@ianstormtaylor
It's not an accent, by the way :)

I didn't "care" either, but I sure was distracted!

But on the other hand, as someone who's studied six foreign languages, I have always cared about speaking a language correctly, and to that end, I personally appreciate someone pointing out my mistakes.

@trevordmiller
Copy link

Hey @gaearon,

Absolutely loved your course. Nice work! It was very helpful.

I added three Redux testing video lessons to my recent Egghead course:
https://egghead.io/series/react-testing-cookbook

My hope is they will be complimentary to all the amazing work you have done!

@viatsko
Copy link

viatsko commented Jan 30, 2016

Solid course!

Improves not only Redux knowledge but modern practices knowledge overall! Keep on doing such a good stuff 👍 🎉

@ezekielchentnik
Copy link

hey, just realized there is not a link/reference to the code in the videos. Maybe obvious, and perhaps, simple enough users could just copy the videos; but I think a lot of folks could benefit from a repos with the exact code in the videos- why not?

@gaearon
Copy link
Contributor Author

gaearon commented Feb 8, 2016

Code snippets for every lesson are available to Egghead subscribers. :-)
The videos are free but the platform’s gotta make money so it can invest into more courses, send equipment to the instructors, host the videos, improve the website, and so on.

That said we have an examples/todos folder that pretty much matches the course.

@ezekielchentnik
Copy link

... cool, I'm missing it then? Looking for link(s) ...

@ezekielchentnik
Copy link

@gaearon apologizes, just revisited the videos. the code is right there :)... watched the videos, bought membership, watched others... just went back to the redux videos actually logged in. cheers.

@gaearon
Copy link
Contributor Author

gaearon commented Feb 12, 2016

By the way, a few people complained that transcripts are inaccurate.
Please send PRs to fix them: https://github.com/eggheadio/egghead-redux-transcripts

@granteagon
Copy link

@gaearon I decided to use redux, and then found the redux vids on egghead. The videos really helped me start to learn redux. Going forward it would be great to see more real world examples.

So my suggestion for redux video training going forward would be advanced composition, a deeper dive into refactoring, and definitely how to use reselect. You seem to have an intuition on when to refactor. So, since functional programming is so intimately tied to redux, getting some tips from you on when to refactor and how to identify a function that does one thing well would be really useful.

In the app I'm building I have several large data collections and I need to put them into tables and do things like sort and paginate the data. I'm having difficulty deciding when to use selectors and when to use create actions. Currently I have USERS_SORT_TABLE action and SORT_TABLE action because I have the users store "inheriting" some state from the table. I did this because I didn't want a SORT_TABLE action on a todo store getting sorted by the user store.

I know my solution is not DRY, but I'm not sure how to do it correctly. As it stands I'm going to have to create a "SOMETHING"_SORT_TABLE action for every store I want to populate a table, which I know is wrong, but I don't know the right way. Another side effect is that my action name are getting huge because I have to separate different store by prefixing their name to the action. This can't be right.

Here's some example code:

/* actions.js */
// ...
export const USER_MOVE_COLUMN = 'USER_MOVE_COLUMN'

export function userMoveColumn (columnIndex, moveToIndex) {
  return {
    type: USER_MOVE_COLUMN,
    columnIndex,
    moveToIndex
  }
}

export const DATA_TABLE_MOVE_COLUMN = 'DATA_TABLE_MOVE_COLUMN'
// ...

/* reducers.js */
// ...
export default function user (state=userInitialState, action) {
  switch (action.type) {
    // ...
    case USER_MOVE_COLUMN:
      return dataTable(state, assign(
        action,
        {type: DATA_TABLE_MOVE_COLUMN}
      ))
    // ...
    default:
      return state
  }
}
// ...
export default function dataTable (
  state=dataTableInitialState,
  action,
  key='dataTable')
{
  switch (action.type) {
    // ...
    case DATA_TABLE_MOVE_COLUMN:
      return {
        ...state,
        [key]: {
          ...state[key],
          columns: move(
            state[key].columns, action.columnIndex, action.moveToIndex
          )
        }
      }
    // ...
    default:
      return state
  }
}

So you can see I created a dependency between the table and the "model" store that I shouldn't have (the model store must use the collection key for it's array of objects). And the dataTable manipulates the "parent" reducer's state, which it shouldn't. It occurred to me that I need to use a selector there, but at the time I wrote this, I was trying to avoid duplicating a large store just to change what was visible in the UI.

So currently I'm struggling to learn reselect to solve some of these problems and refactoring techniques to solve some of the others. The first Redux course was enough to make me dangerous. Now I'd like to learn how to do it right. :)

I hope that was helpful and not too verbose. Trying to give clear, honest feedback.

@granteagon
Copy link

For any kind soul who might help me, I've already found /examples/real-world/reducers from another of Dan's comments and I'm currently reworking some of the issues I've outlined above. Didn't want you to waste time trying to help if I'd found a solution.

@jugimaster
Copy link

Thanks for the warning :)

@philj0st
Copy link

integrating the redux-devtools within my project was a huge pain for me .. i would have (and still will) appreciated a egghead series that describes what is out there and how / when to use it. I read the PR where you describe when to use what .. but I got so confused there are so many things out there hmr, transform 3, redux hot reloader is different from react hot reloader and so on ..

@granteagon
Copy link

For anyone else struggling with the some of the issues I've outlined above, I created a project that allows you to remove most if not all of the boilerplate in Redux as well as namespace actions. See that here

@gaearon
Copy link
Contributor Author

gaearon commented Jun 14, 2016

@granteagon Doesn’t this couple reducers to action creators? One of the core design principles in Redux is that reducers should be decoupled from actions. Any reducer can listen to any action. There is no 1:1 mapping between them. Otherwise, it’s hard for different parts of the state tree to independently change their state in response to the same actions.

@markerikson
Copy link
Contributor

I've noted that most people creating wrappers or abstractions on top of action/reducer creation tend to assume that they will always be directly coupled together. Admittedly, in my app, so far most of my actions have exactly one corresponding chunk of handling logic, but there definitely have been several where multiple parts of the tree need updated.

@granteagon
Copy link

@gaearon @markerikson It does couple reducers to action creators. However, 90% of the time, that's okay or even desired. The other 10% of the time you can still use a hand-coded approach. I will think about what you've said though and consider it for future development.

@naw
Copy link
Contributor

naw commented Jun 14, 2016

@granteagon @gaearon

Having used a local abstraction that is similar to reduxr, I would argue there isn't any added coupling here. Nothing is forcing 1:1 mapping between actions and reducers. You can still have two reducers in two different slices listening to the same action:

const counterReducersA = {
  // this counter increments by 1 each time
  increment: (state, action) => state + 1
}

const counterReducersB = {
  // this counter increments by 2 each time
  increment: (state, action) => state + 2
}

const counterA = reduxr(counterReducersA, 0);
const counterB = reduxr(counterReducersB, 0);

const rootReducer = combineReducers({
  counterA: counterA.reducer,
  counterB: counterB.reducer
});

store.dispatch(counterA.action.increment());  // increments both counters

Of course, if you have more than one "reducer" function named the same thing (i.e. responding to the same action), implicitly they both need to "expect" the action payload will be in a certain shape --- which is completely analogous to having two reducers in vanilla redux both handling the same type constant --- both have to expect the same action shape.

Perhaps I've misunderstood what you mean by coupling, @gaearon ?

@battaile
Copy link

I think showing an asynchronous flow without middleware before showing the thunk implementation could be helpful.

Somewhat related, but while the documentation has been insanely helpful, this statement on the async flows page really threw me off track in hindsight: "Without middleware, Redux store only supports synchronous data flow."

@markerikson
Copy link
Contributor

@battaile : that's because it's true :) Without middleware, any asynchronicity has to happen completely outside of Redux (so, most likely in your UI layer, such as React components). Any time you call store.dispatch, the action would go straight to the reducer function, do not pass Go, do not collect $200, do not make any stops along the way for AJAX calls.

Store enhancers allow you to wrap up functions like dispatch with your own version, and so applyMiddleware provides the abstraction of a "middleware pipeline" before something reaches the real store's dispatch function. That basically provides a loophole where you can jump out and do whatever asynchronous stuff you want, inside of the standard Redux flow.

So, without middleware, you could still totally do async stuff... it would just all have to happen entirely separate from anything actually related to Redux.

@battaile
Copy link

that's because it's true :)

I didn't say it was false, I said it threw me off track :)

I just wanted to do something like the following, which it seemed to imply that I could not:

const mapDispatchToProps = (dispatch) => ({
  onclick(searchTerm) {
    dispatch(actions.requestOrders(searchTerm));

    return fetch('http://localhost:49984/Order/Search?search=' + searchTerm)
      .then(response => response.json()).then(response => {
        dispatch(actions.receiveOrders(searchTerm, response));
      })
      .catch((err) => {
        dispatch(actions.receiveOrdersError('An error occurred during search: ' + err.message));
      });
  },
});

I realize this could easily get ugly, but conceptually I think its useful to see. Or at least it was in my case.

@naw
Copy link
Contributor

naw commented Jul 13, 2016

I agree that "Without middleware, Redux store only supports synchronous data flow." is misleading.

Technically if you want to draw a distinction between "inside" and "outside", the statement might be true, but if it leads people to believe that the only way to do async is by adding middleware, perhaps we can reword it or elaborate on it.

@markerikson
Copy link
Contributor

Yeah, the difference there is that the async behavior is technically happening more at the component level, rather than "inside" of dispatch. A minor distinction, but a valid one.

@battaile
Copy link

I don't think anyone is actually arguing that the statement isn't technically correct.

@naw
Copy link
Contributor

naw commented Jul 13, 2016

@markerikson Just curious if you have any concrete examples where the distinction between inside and outside matters?

One example might be if you want middleware in the chain before your async middleware to be able to see your dispatched thunk (or promise, etc). I'm not sure what that middleware would do, but I suppose it's feasible to want such a thing.

@markerikson
Copy link
Contributor

Mmm... not sure about "concrete" examples specifically. Overall, the distinction between "outside" and "insie" is a question of whether it happens before you call dispatch the first time, or after. If it's "outside" and "before", then all your asynchronicity and logic is more tied to the view layer, whether that be React, Angular, or something else. If it's "inside" and "after", then your asynchronicity and logic is at the store level, and not tied to the view layer.

This is actually much of the point I was just trying to make in a Reddit discussion earlier today: https://www.reddit.com/r/reactjs/comments/4spbip/has_anyone_inserted_a_controllerpresenter_layer/ .

The question of "what actions do I dispatch and when do I dispatch them?" is a core part of your business logic, with the other half being "how do I update my state in response to those actions?". If the action management is in thunks and sagas and such, then it really doesn't matter whether that code was kicked off by a React Component, an Angular Controller, a jQuery click handler, a Vue component instance, or something else. Your core logic is outside the UI layer, and the UI layer is really just responsible for pulling in the data it needs out of the store, displaying it, and turning user inputs into an app logic function call.

So, in that sense, I'd say the question of "inside" vs "outside" does matter, because it's a conceptual distinction between whether your logic is living at the app level or the UI level.

@naw
Copy link
Contributor

naw commented Jul 13, 2016

@markerikson thanks for bearing with me on this line of questioning. At the risk of coming across as argumentative, I do want to push back a little bit on the idea that this is "app level" vs. "UI level".

Note: For convenience I'm going to use thunk to represent an abritrary middleware approach to async, whether it actually be redux-thunk or redux-promise, or whatever.

Your UI layer just wires user interactions to handlers. When someone clicks a button, your UI is rigged to call some handler. Often, the component receives these handlers as props --- perhaps a bound action creator, for example. The UI is unaware what happens when it calls a handler --- it just calls it.

It makes no difference to the UI layer whether the "handler" dispatches a function (to be handled by middleware) or whether it executes an async call and then dispatches a plain action --- the UI is completely agnostic (or at least, it can be agnostic)

A large part of your "app" is in these "handlers" whether you're using thunks or not. In a typical react/redux app, these "handlers" are often action creators of some kind. You could write all of your async stuff as thunks, and dispatch them. Or, you could write all of your async stuff as functions that accept dispatch as an argument. From the perspective of the component, it's either someHandler(dispatch) OR dispatch(someHandler()), or in the case of bound action creators passed down from higher up, it's just someHandler() in both cases. You could build a version of bindActionCreators which would completely mask this difference from the UI layer.

If you give me a react/redux application written with action creators using redux-thunk, I could completely swap out redux-thunk and use a non-middleware approach without fundamentally changing any of the UI layer. (note: I'm glossing over where/how you inject getState, but I believe that's a minor detail here).

Therefore, I'm having trouble accepting that the distinction between "inside" and "outside" is "app level" or "UI level".

I appreciate the discussion, and I hope I'm not coming across negatively.

@timdorr
Copy link
Member

timdorr commented Sep 8, 2016

This course is great. Closing this out so people can direct their comments to the community notes repo for the course: https://github.com/tayiorbeii/egghead.io_redux_course_notes

Also, be sure to check out the next series Dan put togethe! https://egghead.io/courses/building-react-applications-with-idiomatic-redux

@timdorr timdorr closed this as completed Sep 8, 2016
@reduxjs reduxjs deleted a comment from mk-pmb Oct 27, 2017
@reduxjs reduxjs locked and limited conversation to collaborators Oct 27, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests