As I see it, I could make my entire app a presentational/dumb component and then have a single container component pass the appropriate props.
On the other extreme, I could make every single component smart.
So, is there a rule of thumb to decide when to make your presentational component smart?
Quoting from Presentational and Container Components by Dan Abramov:
When to Introduce Containers?
I suggest you to start building your app with just presentational components first. Eventually you’ll realize that you are passing too many props down the intermediate components. When you notice that some components don’t use the props they receive but merely forward them down and you have to rewire all those intermediate components any time the children need more data, it’s a good time to introduce some container components. This way you can get the data and the behavior props to the leaf components without burdening the unrelated components in the middle of the tree.
... This is an ongoing process of refactoring so don’t try to get it right the first time.
I would argue that there should be a nice balance between the size/complexity of the app and the number of smart components (containers).
Consider introducing a container when:
The hierarchy becomes too deep and passing props around becomes a hassle.
It is likely that an entire feature (UI + data mapping) will be moved/re-used across the app.
There's a need to encapsulate a feature for reasons such as the above or any other (versioning, security, lazy-loading etc.)
You identify unnecessary updates as a result of data mapping.
On the other hand, consider sharing a container when:
Multiple components use data of the same nature and form a single unit that might be considered a "feature".
Mapping (mapStateToProps/mapDispatchToProps) becomes repetitive amongst multiple proximate components.
Also, keep in mind that refactoring might be necessary as your app evolves.
Related
I'm working on a part of a React app in which a high-level component creates and passes certain props down through a few layers of components that don't use them to a final component that does.
When validating props with propTypes, is there a good reason to list these props to be checked at every level, going down through the layers? Or is it acceptable to check them only in the final component that uses them?
It seems to me that the former method is redundant; the latter seems to make more sense to me, but I'm curious if there is a reason why I ought to do the former. I haven't seen any discussion on it, which could mean it's an unimportant question, but I'd be interested to know.
I agree with you about if you use props only for dril down for children in the tree, it can be done only once at the leaf components, where you realy use this data. I recently find out that one more place is important for props validation: the components which fetch data from out of app scope, such as backend, because sometimes the structure of the data changes or the data types, then it will be dificult to find which part is broken without props validation.
I'm pretty new to Redux and would like to use it my application but I'm stuck at architecture/design phase for the Redux part. Here are my requirements and my suppositions regarding the design.
Application details:
SPA with AngularJS. Other libs used ng-redux, reselect, rxjs.
Component details:
Re-usable grid component to render huge amounts of data.
My idea:
Create a plug-n-play kind of component-based architecture, where all the internal components of the grid are independent of the parent/composing component like search, sort, row, header, cell.
All the components will have their own set of reducer, action, selector, and slice of state from the store.
Because all the components have their own reducers and can be plugged-in on demand, I need them to be lazily registered to the store instead of being accumulated in one place.
Some of the components like search, sort along with having their own state, also can affect other components state. Ex: setting up of query parameters (searchText, sortOrder etc.) to fetch the grid data which would be handled by another component(s).
My thoughts:
For the 1st point, I'm looking into reselect for supplying the dependent slice of state.
For the 2nd point, I'm still confused about which to use combineReducers/replaceReducer for the lazy registration. I feel combineReducers will not fit if I want access to multiple parts of the state.
For the 3rd point, I'm thinking of following approaches:
a. Passing entire state via getState() wherever required to update multiple parts of the state. Though this approach gives me feeling of improper use of Redux.
b. Component A fires its own action which updates their part of the state, then another action is fired for the other component B to update its slice of state. This approach as well feels like breaking the whole idea of Redux, the concept of side-effect could be used here though I don't know how to use it, maybe redux-saga, redux-thunk etc.
NOTE: Use of either of the approaches shouldn't lead to the component knowing about the other components hence whatever has to be done will be done by passing a generic config object like { actionsToFire: ['UPDATE_B'] }.
I need state management while navigating back and forth between the pages of the application, but I don't require hot-reloading, action-replay, or pre-fetching application state from server-side.
Components will also be responsible to destroy their state when no longer required. And state will have a normalized structure.
I know the requirements might seem weird or not-seen-often but I would keep them that way.
Few things I already know are:
I don't need to use Redux like the classic article from Dan says, but I think I need it here in this case.
I know about the Smart and Dumb components, mostly my components might seem smart (i.e aware of application state) but that is how I want to keep them, I might be wrong.
Diagram of the grid component:
Grid Component Diagram
Redux's global store makes encapsulation and dynamic plug-and-play behavior more difficult, but it is possible. There's actually many existing libraries for per-component-instance state and dynamic registration of reducers. (That said, the libraries I've seen thus far for component management are React libraries - you'd have to study some of those and reimplement things yourself for use with Angular.)
As a react web app gets complex, some components have a number props, which is less readable, harder to expect what this component does, and tedious to add lots of prop types checks and pass props from top-most component to bottom. This is mostly caused by passing flux/redux actions and stores passed from top to bottom.
Is there a great way to reduce the number of props passed in?
There are two solutions I thought of, which are not perfect:
Pass props with a spread operator {...props}. This does reduce writing a number of props and prop type checks, but there may be conflict in naming, so the names of actions/stores should be unique. Another downside is to be extra careful of what props to pass or not to avoid side effects.
Wrapping the component with a container which directly connects actions/stores to the component, in an hoc-fashion. For example, react-redux's connect() can be used. This is cleaner and simpler than Solution #1, but it's hard to write component tests if the component contains a container because of a Redux error.
One example of the error is
Invariant Violation: Could not find "store" in either the context or props of "Connect(Header)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Header)".
You've described two approaches and their pros and cons pretty well. I'll add a few design considerations to what you've said.
Approach #2 is my preferred approach. Reducing the amount of props passing through the components avoids complexity. There are ways to test components without triggering that error, but I think that deserves its own separate Stack Overflow question, to honest. For now, I'll just say look into shallow rendering, and consider whether you really need to just do unit testing here vs integration testing. If you will also be creating automated tests in something like Selenium, then that can serve as your integration testing, perhaps.
Approach #1 can be improved by making scrupulous use of PropTypes to validate what is being passed through. It's reasonable to leave out the PropTypes checking on the middle components that are just passing ...props through, but the end components (components that actually use the props instead of just passing through) should have really stringent PropTypes declarations. Use Shape instead of Object. Use ArrayOf instead of Array, and basically take all opportunities to be specific in your PropTypes declarations.
Regarding your concern about name conflicts, it sometimes helps to group props together as members of one object and pass that object as a single prop. If the props are conceptually related or have a single destination component, this makes some sense. I still greatly prefer just having more container components (approach #2) since it causes less information to flow through props. And writing good PropTypes for objects with nested members takes a little more time and yields warning messages that take longer to troubleshoot.
People sometimes forget about .state after they start using Redux/Flux, with some purists preferring to send and receive everything through the store. The elegance of the stateless component declaration further biases me against adding .state to a component. But .state is great for tracking ephemeral things like animations and tooltip visibility. Not everything needs to be in the store and pass through props.
When passing data between two elements that are very far away from each other in the hierarchy of components, passing data through props can be tedious. In these use cases I've resorted to using Redux just because it is less to keep track of when there is a large amount of components.
What I've done in one little project is to use a closure to encapsulate state and export that variable and consume it elsewhere. I feel this is a an antipattern but it does work.
The way it works is by declaring some variable that is going to be modified within a component. This same variable is the imported from elsewhere and consumed from elsewhere.
Here is a small sample with what I am doing (just pretend there is a large component hierarchy): https://codesandbox.io/s/2R9RvYkN1
So my questions are: is there a better way to achieve the same results? Should we use a Flux implementation for these use cases? Is it ok to just pass props around through a large hierarchy of components?
As you stated yourself, redux solves this problem by providing an "App state" that's global to your app, which allows you to connect any component you want to that state.
Your "closure" is merely a poor-man's Redux, it's also a global state, only it lacks any of the features provided by Redux(unless you write them specifically).
let's CompA needs to re-render based on a click event on CompB, how do you do that automatically with a closure? you'd have to add listeners, check if a relevant state was changed and then re-render.
all these things are done for free by Redux, so I don't see any added benefit(except for not using redux, which can be a benefit in it's own).
If it's that important not to use redux, this can be "fine", yet very dangerous and I'd say it won't scale well.
I'm fairly new to react, and really enjoying it. In creating components, is there a good rule of thumb (or simple generalization) to consider when deciding if a component should manage it's own state or not.
As example (only as example), an input that gets different classes added based on state, like 'hover', or 'not empty'...
Would it be better to create a component that manages those states internally or just handle that wherever I'm rendering an input?
I know this question may be 'primarily opinion based', but I'm hoping to get a general feel for how to think about it.
Thanks in advance,
-Ted
This is a constant internal battle that you'll just decide on down the line and you're right that it's primarily opinion based (meaning no answer will be correct). However, I can share my own experience and the process I take to decide on how to split the logic of my components.
I think of these things:
How will having/not having that piece of logic affect unit tests? If the component would need too much setup to be tested, then I move some logic into it and away from a parent Container component.
How often will I reuse the component? If it's many many times, then I look at the types of Container components that would render it and, again, if it seems like too much boilerplate is needed, then move the logic.
Does the value change through its own behavior or based on outside queues? In your example of the hover, the behavior changes due to its own behavior so it feels like the className (a prop of itself) is reacting to the component itself.
Most importantly, do you benefit from removing the logic and placing it in the Container? If you think that other component could benefit from knowing the hover state of your input field, then you may want to put the logic in the container. Otherwise you're abstracting away too much.
Application state management libraries such as Redux will often suggest to use their libraries as little as possible and instead rely on internal state of the component. I mention this because as you figure out where to put your logic, you have to think that about the end goal, which is usually to create a web application, with multiple components working together. Abstract too little and you end up creating non-reusable components. Abstract too much and you have tons of boilerplate and configuration lying around that could be trimmed by using internal state.
Zeke has some absolutely great points. I'd just like to add my own guideline, which is:
If the behavior of the component is the same, no matter where it's used, and is not tied to the behavior of the app/environment at large, then state should be internal
otherwise, manage state elsewhere and pass in props