-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
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
Comments
👍 |
|
👍 |
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. |
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 :-). |
What @smashercosmo said 👍 |
Unit testing. TDD. |
What @cateland said 👍 |
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! :) |
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? |
This is so good. Item 3 in @smashercosmo 's list is something I'd like to know as well. |
what @wpannell said! Unit Testing/TDD! Would also love to see videos on the topics covered in the Advanced docs. |
What specifically are you interested in with regards to unit testing? |
...that's a good question. Would the process change very much when they're mocha tests? |
Not really. But I guess that's a good topic for a series of advanced lessons. Unit testing reducers, action creators, components etc. |
Firstly, great videos. Already understand redux but it was refreshing. |
Yeah, I guess it really isn't much else other than wrapping the Thankfully everything is so simple! |
Immutability with object ID hashes (e.g. I find myself relying too heavily on a |
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 |
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. |
I'd also like advanced reducer composition. |
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. |
Watched the whole thing, very good! I appreciated the use of ES6/7 syntax ( Perhaps a video explaining the recommended code architecture. And updating the doc to use ES6/7 syntaxes? (are PR welcome in that direction?) |
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.) |
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. |
@gaearon What do you think of structuring actions in the example videos as a wannabe-standard |
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 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. |
@ianstormtaylor 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. |
Hey @gaearon, Absolutely loved your course. Nice work! It was very helpful. I added three Redux testing video lessons to my recent Egghead course: My hope is they will be complimentary to all the amazing work you have done! |
Solid course! Improves not only Redux knowledge but modern practices knowledge overall! Keep on doing such a good stuff 👍 🎉 |
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? |
Code snippets for every lesson are available to Egghead subscribers. :-) That said we have an |
... cool, I'm missing it then? Looking for link(s) ... |
@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. |
By the way, a few people complained that transcripts are inaccurate. |
@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. |
For any kind soul who might help me, I've already found |
Thanks for the warning :) |
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 .. |
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 |
@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. |
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. |
@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. |
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 Perhaps I've misunderstood what you mean by coupling, @gaearon ? |
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." |
@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 enhancers allow you to wrap up functions like 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. |
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:
I realize this could easily get ugly, but conceptually I think its useful to see. Or at least it was in my case. |
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. |
Yeah, the difference there is that the async behavior is technically happening more at the component level, rather than "inside" of |
I don't think anyone is actually arguing that the statement isn't technically correct. |
@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. |
Mmm... not sure about "concrete" examples specifically. Overall, the distinction between "outside" and "insie" is a question of whether it happens before you call 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. |
@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 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 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 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. |
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 |
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!
The text was updated successfully, but these errors were encountered: