So I'm starting to learn Typescript+React in an industry setting (I've worked with a mixture of Coffee, Backbone, Marionettejs and a few others previously) and I was wondering what the idiomatic way to handle hierarchical views with respect to state transfer is.
For instance, let's pretend I'm attempting to make a card that will be placed on some dashboard. The card is composed of a header region, and a content region. The header region has a region for Title / subtitle, and a region for actions that can be done upon the card.
Now, lets say that there are a handful of objects from which the state necessary for rendering a card exists. What is the best way to pass down state to the children? For illustration lets say I have the following
class Report {
const title: string;
const subtitle: string;
const data: any[];
...
}
Go the Backbone way I know of passing a large options object that just has everything that an entire hierarchy needs and let each child grab what it wants and append what it wants. In this case, that would be effectively flattening the stateful objects and grabbing the queries to shove into props. For example <DashboardCard title={report.title} ... />
Pass the objects with state down so that the children can grab what they need. <DashboardCard report={ report }/>
Not quite flatten everything but pass down groups of attributes bundled into the props that the children expect. E.g <DashboardCard headerProps={ headerProps } ...>. This seems like it breaks down pretty fast because then a parent needs to know the exact shape of every child node props object. <DashboardCard headerProps={ } contentProps={ }/>
Scrap the hierarchy notion altogether, and create simple base components and then views need to manually stitch them altogether. This makes passing state trivial because it's literally view -> all components, but then there is no nice abstraction that can help with adding new features to components and other things.
Maybe (probably) there is something fundamental that I am missing with frontend development and react in general.
In the situation you are describing, I would lean towards passing down the necessary objects as props to the children (option 2). I would say that options 1 or 3 might be preferable if you have a large number of objects to pass down, and option 4 isn't too enticing due to the loss of abstraction as you mentioned.
Related
I'll mention in advanced that this is not a technical question on how to do the data update between 2 sibling components, rather what is the correct way to do so.
I have the following component tree structure:
App
|
-- Home
|
-- SearchBar
| |
| -- Filters
|
-- ItemsList
ItemsList has logic in it to load the list of items from an API call and show the list of items on the page. It also manages the state of the articles. If an article is deleted it removes it from the list and updates it's state.
SearchBar is a component that contains a textual search that is displayed above the list where a user can enter text to search and also has a button that opens the Filters component where the user can filter different parameters. Once the user search's or filters the list in the ItemsList component should be updated accordingly.
There are a few ways I can think to achieve this:
Using react context - The provider will be on the Home component (or another component for holding SearchBar and ItemsList - and both SearchBar and ItemsList will contain a consumer that will updated the state with a method in the provider - those updating the components. This in my opinion create some dependency between those components and they are not really standing on their own (the ItemsList component should be used in other pages as well - of course this is still possible, but yet does not feel so "clean").
The ItemsList component will contain public methods such as "delete item", and "clear list" and those methods will be called from the home component. The SearchBar will get a property with a function as an event - something like "onFilterChanged" and will call the method on ItemsList (I will need to hold a ref to that component). But working like that feels like each component can stand and be re-used on it's own merit - but loosing the "reactiveness" and more wire up that needs to be done.
Are there any other ways to achieve what I'm looking for that I'm not thinking of?
What is the correct way to architect this kind of solutions?
When two siblings need access to the same state, keeping the state in a mutual parent component is a common and recommended way of handling it (the official React documentation encourages that approach for most cases). That way, both the state and any methods to update the state can be passed to both children as needed, and any updates made by one child will be reflected in the other child.
There can be drawbacks to that approach: Depending on the size of the application, prop drilling can make code confusing and difficult to maintain—especially if one component that is using said data is being used in multiple places, deeply nested, or both.
For such occasions, holding the state in context or redux is a more appropriate approach. The first option that you listed is completely legitimate and what I would recommend. Maybe extracting your context entirely (not keeping it in your Home component and instead creating the context in its own file) would help things feel more "clean."
Determining when to use which approach is something that comes with time and experience. When choosing an approach, it is helpful to keep your future plans in mind. If you know the application will be small and simple, keeping the state in a parent component is a great move. If you have big plans for your application, using context or Redux from the start will be easiest.
Thankfully, the worst case scenario is that you decide to change from one approach to the other, which can always be done (confusing and tedious as it may be at times).
Let's say that I'm fetching some images from an API in the App component.
Then I want to pass it to the component responsible to rendering images. But this component is not a direct child to the App component. It is the child of a direct child of App component.
Here's how I would pass the images array down to the image component, but I feel like it might not be the best approach.
But what would happen if this hierarchy gets more complex - even just by one more component:
Intuitively, it might not be the best thing.
So, what would be the best way to pass the images array down to the image component, if there are many other children between them?
The problem is usually called Prop Drilling in the React world: https://kentcdodds.com/blog/prop-drilling
A few options:
Passing props may not be that bad. If the app is small, is a very modular -and easy to unit test- option . You can reduce the verbosity by using prop spread: <Comp {...props} /> and by keeping your component interfaces similar. (many people dislike prop spreading as you can unintentionally pass unsupported props, but if you use TypeScript the compiler will catch that).
You can use a React Context: https://reactjs.org/docs/context.html (as other mention in the comments). However, keep an eye on how your context objects are defined (keep them small). React will re-render all the childs using the context when the value changes, is not smart enough to automatically detect changes at the property level. Frameworks like Redux or Zustand use other mechanisms to allow a granular control of the shared state (you'll see examples of a useSelector hook).
You can also take a look to a state management framework (Zustand is my favorite, but Redux is more popular). However, it may be an overkill for small things.
My personal choice is to start with prop drilling, it's easier to modularize and unit test. Then you can think on your app in layers: upper layers depend on a context (or a state framework), and lower layers receive properties. That helps when you want to refactor and move reusable components to other projects.
So im working on a React "Todo" list as a first React project, im using Rails as an API backend (strictly taking in and sending back .json), I am fairly familiar with Rails or at least the basics.
Currently I have a few Components for handling the actual "List". This being a ListContainer parent component (Holds the state for "Lists" which is an array of Lists) as well as all the Add/Update/Delete/Index(Set Lists initial state) functions.
Along with some child components (ListForm and List), both pretty self explanatory. List being a dumb component just holding the List title and description, and ListForm being the actual form to submit a new list.
I am using axios, and so far Create/Index/Update/Delete are working great for Lists. However I am running into the issue of being unsure how to handle the list items themselves. Currently in the rails side Lists has_many list_items and list_items belong_tolist`.
So rails has the relationship side all buttoned up...but im really unsure how to handle the actual items on the javascript side (for each list).
My first initial guess was to switch "List" dumb component to a smart component that handles state, in this state would be an array of "list_items" that belong to that particular list. When the List is loaded I imagine axios performing a GET request for that lists items within the component. And then basically handling add/delete/update similar to how the ListContainer component handles it for Lists (but instead making "List" component the de-facto container component for ListItems (which is currently a "dumb component")
Does this make sense? I honestly am still pretty new to react so handling relationships on the front end side is something im not familiar with yet. But storing state within a child that "belongs_to" a parent state/component makes the most sense initially? Unless I am overthinking it?
I think your proposed solution makes sense. Following your pattern you would probably have another Component, ListItem, and you would map through the listItems state in List and display a ListItem for each item.
That being said, many people learning react have a tendency to over-complicate it by having too many components/different files interacting. At one time the React community encouraged this, but they have since backed off on doing so. Many people now consider having presentational components (or "dumb" components, as you call them) an antipattern. See, for example, this note from Dan Abramov enter link description here
I've got a big component that I am making that will take one large standardized object and display it. I will have many child components within the main distributed component and I don't want to have to make it ugly by typing in props all the way down the chain...
const User = ({ user }) => {
return (
<BasicInfo
name={user.basic.name}
tagline={user.basic.tagline}
email={user.basic.email}
...more props
>
<OtherInfo
infoprops={info}
...long list of props
>
)
}
It would end up being a very long list of props that might go 3 or 4 levels deep and I really don't want to have to keep track of what is passing what on manually...
I got used to using redux, so I could do something like connect() but can I use redux here with a distributed component? would the state of my one component be kept separate from the end users redux (if they are even using redux)?
Is this even wise? Maybe there is a a better way than using redux? Thanks
I don't think its a good idea to use redux to make your component reusable since redux is based on a single store for a whole application so, if you create a store, the app would not be able to create its own.
Given that, I do think you could create your custom store using the Singleton pattern (as you usually would do with android) without forcing your possible users to add redux to their project just to use your component.
I can't guide you more without knowing your component hierarchy or behaviour.
I come from a Backbone / Marionette background and have been working in React for a few weeks now. A lot of people say you should pair React with something like Backbone or Ampersand.js, using React as the V and Backbone / Ampersand as the "M" and "C" in the classic MVC model.
However, the more I think about this, the more I wonder if it's really necessary. In Backbone / Ampersand, the model's main purpose is to track state, and "tell" views to update when the model's state changes. Of course in React, the view takes care of this responsibility via the view's props and state, which seems to make a full blown Backbone / Ampersand model unnecessary and duplicative.
What am I missing?
First, let's define model in MVC terms. The following is from Wikipedia.
[...] the model, captures the behavior of the application [...] independent of the user interface.
The model directly manages the data, logic and rules of the application. A view can be any output representation of information [...] multiple views of the same information are possible [...].
The third part, the controller, accepts input and converts it to commands for the model or view.
In React you inevitably will create View+Controller components; much like angular, knockout, and most other JS application frameworks.
Why models?
While you could also throw the model into the component at this level, it turns out to not work well in practice. You have problems like overfetching (and other optimization limitations), difficulty testing, no separation of concerns, and it's difficult to see what the component actually is until you mentally separate the controller behavior from the model behavior.
So, backbone?
If you have this code, and you want to make it better, you'll eventually end up with models. It could be backbone models, or it could be flux stores, or it could just be simple objects with functions that call $.ajax.
It makes little difference what kind of models you use, but you do need them.
MODEL ALL THE THINGS
Woah! Hold on there. Models aren't free. Every time you use a model you're crossing the abstraction boundary, and leaving component land. It's an imperative action in a declarative system, so we need to keep things predictable.
Most of your components are pretty dumb. Props, and maybe some UI state. You have controller components ("View Controllers"), which are 100% tied to your model layer, and you have the rest of the components which are (ideally) 0% tied to your model layer.
What you seem to be describing in the original question is a small application where you have one of these controller components. However as these grow, you need to coordinate between them (not fetching the same user twice, for example). You nest these controllers inside other controllers to build an application. The model is the glue.
You can think of a React component as a functionally pure function that takes props and state as input arguments. Its render() method's job is to produce and returns a (virtual) DOM element or JSX (syntactic sugar) based on props and state. Backbone still "owns" the model. When the Backbone model changes via user inputs or socket events or whatever, setState() can be called (add some magic here) which causes React components to render again. The point is React component does not hold the state. This is NOT to say that one must uses Backbone with React as React is simply a rendering library.
Update: In react-future, it's very clear that render() should be treated as a pure function. It takes props and state as input arguments and its job is to produce a JXS, so no need to refer to the this keyword.
https://github.com/reactjs/react-future/blob/master/01%20-%20Core/01%20-%20Classes.js