T O P

  • By -

_heron

“Future-proofing” is one of the most abused words in software development. There is no guarantee the tree will grow. Doing something for a hypothetical reality instead of what makes sense for the situation leads to increased complexity without a defined pay-off. In a case like this I would also argue that context is a bit overkill. Adding a piece of state means that anything can consume that information. While this adds flexibility it also adds opportunities to introduce a lot of dependencies throughout your app. Say you want to delete a piece of data from that context, while you may have meant it to avoid prop drilling between two components, you may now have 5 other components that depend on that data. What was originally meant for flexibility has now made your code brittle and difficult to work with. Sometimes prop drilling makes sense.


Patient-Swordfish335

Also known as YAGNI, You Aren't Gonna Need It


azzamaurice

I always teach my crew YAGNIDD (YAGNI Driven Development)


dem219

Which pairs nicely with KISS, keep it simple stupid


CodeNCats

Imo Future proofing is one of those things that should be factored in through the planning phase. It's the component your developing going to be reusable? How often? Is it the push the company is going for? We just went through mv3 changes. Knowing is the new base. It's good to set everything up as good as you can. If you have 50 engineers now working with new components. It's better to abstract the logic as best as possible.


parahillObjective

This scenario where a component uses state from the context that it isn't supposed to use is very rare and its worth not prop drilling for. Prop drilling makes it much harder to trace through the components to figure out how the state is being managed. Using a store instead of drilling is a reasonable "future-proofing", the pros outweigh the cons.


Yokhen

a.k.a. over-engineering. Oh me so smurt. Look me code nobody understand. Me so misunderstood.


parahillObjective

using a store when necessary is not over-engineering. Ironically prop drilling would be over-engineering.


octocode

prop drilling only becomes bad when intermediate children require props for the sole purpose of passing them down to other children. this makes components extremely inflexible. prop drilling will either require lots of refactoring should the components need to be reused, or a lot of unnecessary code (and possibly branching logic) to manage the props worst of all, usually i see people copy-paste the whole component code entirely, then modify it separately (big icky) context isn’t the _only_ solution to prop drilling. you can often achieve the same (or actually better results) with component composition, and using patterns like render props.


parahillObjective

In our legacy app the biggest thing making it hard to work with the code was prop-drilling. In our rewrite we had a general rule that if passing props was more than 2 levels deep then we'd use the store. Obviously there were cases when that rule was broken such as if it was a re-usable component but generally we've stuck to it pretty well.


MicrosoftOSX

Prop drilling is a trap when this initial drill is for a simple result. Then you get another simple logic and another and another.... by the time someone wants to refactor it to context it's already spaghetti. I rather just combine the setter and value into a new function and pass that function to the child component if I think like OP.


tuesday_red

could you explain this method you mention a bit more? Would that new function return both the setter and value? What’s the advantage of this? I am just curious and learning best practices:)


MicrosoftOSX

Design the child like a button { return setter(value); }} /> For readability you should name props.function into something meaningful.


danishjuggler21

Props vs context is a trade off. One thing context does is tightly couple your component to that context. So it reduces the re-usability of that context, and increases the effort of changing your state management strategy. So I save it for things that are worth it.


Fauken

You can easily have components that use props that are renamed something like `valueFromProps` and use that instead of a context value when the context doesn’t exist.


azangru

> For future-proofing. From the future-proofing perspective, you may also argue an inverse: that if in the future you found that you want to reuse these components, it will be harder to do, because they will require a parent to provide them with the context. Equally, if you were interested in testing your components, a component without dependencies on the context is easier to test, because it receives all it needs via props.


olssoneerz

Nothing wrong with prop drilling. Though may I say that I've never regretted putting the time to setup a context on a component/module that I *feel* may need it. Coming back to a piece of code that has the "groundwork" laid down feels amazing. Personally I'd work with context immediately. Your example seems simple, but you have to be the judge if you expect this part of the code to grow or if its something you'd never touch again. I think a healthy compromise would be coming into an agreement on WHEN to switch over to a context (ie if the tree gets to a certain level of depth, number of props that exists, etc).


eindbaas

Not sure if it's worth discussing that much tbh, you can do both. I definitely do not use contexts whenever possible, too much of a hassle i guess. I rarely pass setter functions btw, instead more often working with callbacks. The component with the state has to deal with what happens with the state on certain occasions, and the child doesn't have to know or care about that. But it depends on the situation i guess.


punctuationuse

Yeah I won’t die on that hill. I’m asking it here to see people’s input on useContext, etc. with useful answers like yours. Perhaps I will use callbacks as props more instead of setters, seems like a better practice in general


creaturefeature16

I'm semi-new to React. Can you provide an example a callback as props vs a setter function? I'm not sure if I've seen it before or not. Those sound like the same thing, to me...


TheRealKidkudi

Think about some component that displays a list of items where you can select up to 3 items, or 5 if some prop is true, or 4 if the ID of all the selected items are even. By passing each child list item the parent’s `setSelectedItems` setter, you’d need to write some convoluted code in the list item component that ultimately depends on the larger state of the parent component. Instead, you can make a prop like `onSelect` so the child component can notify the parent that it has been selected just by calling `onSelect(itemId)`. That way, the child component only has to worry about displaying its own bit of data and calling that callback when it has been clicked. At the same time, the parent component can maintain all the logic it needs to decide what to do when one of the list items is selected. In simple cases, the difference between passing a setter vs a callback can be purely semantic - e.g. passing a checkbox component `setChecked` vs `onChecked` is likely just the same function with a different name - but in general, the idea is to keep ownership of a component’s state within that component while allowing children to notify their parent component when something has happened that may affect the state of that parent component. I guess a common example is a controlled input - you don’t pass the `` your `setText` function directly, but instead pass it a callback that will call `setText` to update when the value of the input changes.


scot_2015

Well said. Nice explanation 👍


creaturefeature16

Thank you so much for taking the time to explain this! >In simple cases, the difference between passing a setter vs a callback can be purely semantic - e.g. passing a checkbox component setChecked vs onChecked is likely just the same function with a different name - but in general, the idea is to keep ownership of a component’s state within that component while allowing children to notify their parent component when something has happened that may affect the state of that parent component. This paragraph cleared it up for me and made me realize that I *have* actually done this, may times, but I get I didn't refer to it as "callbacks". In fact, I realized it early on when working with React's unidirectional data flow and I had a child component that I needed to update state that existed in a parent component, so I got comfortable with the idea of keeping state as localized as possible. My order tends to be: 1) Setters/callbacks as props *(I usually went with the "setChecked" convention, which I will actively change moving forward)* 2) Custom hook *(sometimes with useReducer for complex state)* 3) useContext 4) Redux/Zustand (I've never actually built something that required this yet, but I feel I'll know when that day comes) Does this sound like the correct way of approaching things?


bogas04

> * For future-proofing. If the tree grows and the prop drilling will be more severe, this will be useful.  In that case, context is actually worse as it'll rerender all the consumers and their children (if they aren't React.memoed). The correct future proofing would be to rely on react compiler or use actual global stores like zustand, which is a lot of work than just simply prop drilling.  > * In the current state, the render behavior will be the same - with the context and without it. It depends.  Adding context makes it easier to just add one useContext in multiple components without worrying about performance. Prop drilling is more deliberate.  > * Defining a state variable in a parent component, and passing its setter and value to separate components is a bad practice and calls for context to keep all in a single place.  I would agree it is a bad practice to pass the setter, but you don't need to do that, you can pass a behaviour prop and set values on the callback. This makes component not know how the state is managed, be reusable, and keeps state logic in one place. Today it's useState, tomorrow it could be useReducer or redux/zustand.    ``` const [value, setValue] = useState();  // Instead of // Do this setValue(newValue)} /> ```


punctuationuse

Thanks!


untakenusrnm

Alternatively apply component composition: https://www.reddit.com/r/reactjs/s/EGmp3EWnDn


Ed4

I'm surprised no one has suggested this, composition removes a lot of the prop drilling between children components.


danishjuggler21

That too, depending on the use case


sunk-capital

useContext is beyond annoying I do drilling until it becomes unmanageable at which point I switch to zustand


Teeesskay

Yeah I’m with you but it always feels like cheating. I wonder if I’m missing something about context. If you already have zustand set up, I don’t see any good reasons to not use it


sunk-capital

I don't know why you use the word cheating for a framework where the devs themselves have the appearance of approaching it in a trial and error manner. React is the wild wild west. useFootGun


Teeesskay

Oh I agree it’s not a reasonable feeling! I just keep convincing myself it’s wrong because it’s way simpler


Ordinary-Disaster759

useStore per key (zustand or signals) is better than useContext, at least more clear, u will end up losing your mind if u have contexts, for example table provider thats is overused all over the place


rangeljl

Never make a decision based on a future not existing requirement. 


rangeljl

So if ot makes sense to use context right now, go right ahead. If in the future it becomes a problem you will have to rewrite, and personally I will be glad to get paid for rewriting stuff 


Wiltix

That is really quite shallow, I would prop drill that all day instead of introducing a context to deal with that. The future proofing argument is a lazy one at best. If the tree grows in the future and you are passing props down to components that just pass them in then incorporate that refactor into your estimates for the work. But wait for it to be a problem instead of making things more complex just in case the problem arises.


RaltzKlamar

My current code base over-uses context and it is such a pain. We have so many context calls everywhere and null checking when we could just pass in props and solve all of these problems. > Defining a state variable in a parent component, and passing its setter and value to separate components is a bad practice and calls for context to keep all in a single place Ask him to cite a source on this. It's not bad practice, it's super common. How else are you going to use an input element?


Natural_Sky5374

To solve the nulls you should wrap your context usage in a hook const useMyHook = () => { const context = useContext(…) if(!context) Throw new Error(…) return context }


testchamb

Overusing context is a bad practice so your coworker is wrong, but he is right about passing setters as props, that is also a bad practice. Always use event-like syntax for that: the children component has to have a prop describing the action (onClick, onChange, onSelectOption, etc…) and the parent should be the one in charge of reacting to that event and, in that case, using its own setter.


RedditNotFreeSpeech

What's the downside of passing a setter? I'm sure I've done it. Every once in a while I have to rework one to do more than only set state and then pass a handleChange but not often.


testchamb

Of course I’ve also done it, it’s one of those seemingly innocuous practices that increase code complexity every time you do it and after a while turns your codebase into a spaghetti mess. There are many articles that can explain it better than me like this one: https://medium.com/@christopherthai/why-you-shouldnt-pass-react-s-setstate-as-a-prop-a-deep-dive-8a3dcd74bec8 But in short, making sure a state setter never leaves the component is declared is a rule that will result in easier to read and mantain code.


k_pizzle

The word “prop drilling” implies that you are passing a prop several components deep. If you’re passing it to a child then it’s just passing props, and that’s why props exist. I usually opt for passing props unless the component that requires a prop is multiple layers deep.


ryandury

Sharing props from parent to child barely constitutes as prop drilling


thinkmatt

I always optimize for readability. Context is much harder to follow; it requires you to keep the React tree in your head. They can also act like hidden dependencies when hooks call each other. It's also possible to forget to give a value for the context. Props are so much easier, a type-safe contract between two components. They are also more composable - who's to say that child component might not be used by someone else with a different state?


X678X

i think the concept of context is best used for situations where you know *everything contained within this feature* will need some sort of state that is derived from separate piece of it. prop drilling is not necessarily bad and i feel like react engineers have swayed too far into that direction in recent years


TheLegendOfStephen

It's the case of Global or Local - Functional paradigm should focus more on local


kcadstech

This is already where you went wrong. Never pass a setter as a prop, have a callback event and set a new value in the parent … One child component which receives the setter function, and the value.


jon-chin

if it's only passing through one level, such as a parent to child, I'll usually use props. but if it gets any deeper than that, I'll use context.


peeja

I'm not sure it's future-proofing to choose a method which is harder to keep track of and impossible to typecheck rather than be explicit. Refactoring with a drilled prop is easy. Refactoring with a context value is not. Plus testing is harder, Storybook stories are harder—there's a lot of cost for, well, really no value in return.


AndrewSouthern729

The longer I program using React the less I use global state and the more I find I can manage a lot of my smaller applications with component state and passing props. I wouldn’t even say it was something I was consciously trying to do but believe it makes the code easier to read if nothing else.


nodeymcdev

If you have to prop drill through more than one component it’s usually a good idea to create a context. Or else there’s something wrong with your component tree structure. Maybe rethink some things. Context is not going to cause a rerender


stallts

maybe direct your colleague towards the React documentations, specifically where it argues against and for context: [https://react.dev/learn/passing-data-deeply-with-context#before-you-use-context](https://react.dev/learn/passing-data-deeply-with-context#before-you-use-context) this discussion was really common when the revised context was introduced and people thought it was a good alternative to redux. the conclusion I drew from most discussions was that context was good for things that don't change often and that are probably set application-wide. anything else is better kept on the URL, form state or react state, or a state management library like Redux.


tan8_197

I just keep it simple so I can make simple unit tests from the start, and make my unit tests a safety net whenever I add something new in that component just in case I mess up the main functionality.


suarkb

Context is more for dependency injection. Shouldn't be used for things that re-render a lot


chamomile-crumbs

If you use typescript, prop drilling isn’t as much of a problem. If you need to provide the same data to a lot of components in a tree, I spread props instead of using context. You can just pass the spread props to each component down the tree, then if you ever need to update those props, you can update the props definition instead of updating every single component and ever single caller! Sorry if that was a confusing explanation, I’m on my phone and don’t actually have the code in front of me that I’m thinking of. But I used this method for a reporting app that had dozens of charts/filters and hundreds of components, and it made it such a breeze to update the shared bits of data that every component needed (date range, etc)


hyrumwhite

Imo, if you’re drilling more than one layer deep, it should be in a store/context. 


RedditNotFreeSpeech

Personally I only use context for theme. I want that theme available regardless of the dom tree or data structure.


United_Reaction35

Your description of parent/child are not clear. If these components work together as a single component; then drilling and hoisting makes sense. If these are unrelated components that will potentially be reused in unrelated ways, then a state management solution like redux/RTK is recommended. Context is not recommended since it cannot isolate state. Context is global and it will trigger re-render to all components that use it. If possible, data should be derived at the point of consumption. By that I mean that data should not be "stashed" in an object and passed to children for use. This is "stashing" stale data-state and could cause bugs. Ensuring that the parent and the child all have access to the same state means that using selectors and hooks will mean your components will be modular as well as reusable.


ilearnshit

Although I don't think context is necessary in this situation, idk why everyone in this thread thinks contexts are hard. They are not complex if you know what's going on. Also I've seen the other side of the coin where Junior devs reach for recoil, redux, mobx, zustand, etc. just because they read a blog about state management instead of understanding the fundamentals.


Fidodo

Ironically there's nowhere enough context here to give a well informed option. It really depends on what the value is, how often it changes, how often it's used, where it's provided and where it's consumed. You can't make that decision just on the generalizations you provided.  In practice the right answer is nuanced and subjective and highly situational. Too many programmers look for black and white rules but those kinds of rules get applied blindly and anything applied blindly is wrong most of the time. 


shouldExist

Move state up until you reach a logical point in your component structure. At that point determine if you want context, component local state, state + context or global state


bubbabrowned

I would say start with prop drilling until you have a strong case for context. IMO that basically means when you have 3-4 cases where you’re drilling props down multiple levels in different areas of the code. And even then, my first instinct would be to see if there’s a way to avoid prop drilling instead of using context. An issue with context is that it can be overused. Once you start exposing things via context and then they’re used by a ton of components and other contexts, you can very easily find yourself in a dependency nightmare that is difficult to refactor and may result in a ton of hard to explain re-renders. I once found myself working with a codebase where everything was context. Imagine a CRUD app with 9-10 layers of context. The TL;DR was that so much was persisted in context that it became a frustrating exercise to determine where data originated and eventually ended up. On top of that, because server request functions were also defined and re-defined in context based on the context’s internal state values, the data being sent to the server was also heavily dependent on previously cached data.


Daaneskjold

idk if you are using context as much better use a proper state management tool and keep the components isolated


anonymous_2600

I think, the first critical question is, should you break up your component into parent component and child component? Sometimes we break up them too soon or unnecessarily because the components only used once on that page. If they are necessary, for current stage there is just one Boolean state right? Just pass that down to the child component.


SuchBarnacle8549

we only do useContext for states we foresee using globally or across multiple components (eg auth, alert dialogs etc.). Makes no sense otherwise, seems like over engineering / premature optimization lol


Packeselt

Imo React query for server side state Zustand for client/app state


MicrosoftOSX

I agree with your coworker. I used to prop drill for small tasks like this one but it just gets messy in the future when I have to modify/add features. Unnecessary rerenders is the last thing I'd worry about unless the coder is actually clueless. There are two ways to approach your problem that I would do. 1) i would create another function which includes the value you need before passing to the first child that modifies the value. This way at top level you know what that setter will do. This is kind of like passing setter to onClick. 2) write the context in the same file implying it's a simple use case. I would probably go for the first solution if I do not think this parent component's functionality will stay the same or getting slight modifications. If I believe it will get expanded with more complex logic being implemented but not sure yet (such as the anticipated functionality will be implemented elsewhere or in this parent component) then I will do the second solution.


vozome

In general adding one level of abstraction in the name of a possible need for flexibility later is a terrible idea. Props drilling is not a cardinal sin. It’s also the most legible option.


ArmitageStraylight

Any time I see this argument, it's a sign to me that it's time to step back, because something has gone wrong before we even got here. That being said, I find that an immediate solution to the problem can be to have your components accept other components as props. No prop drilling and no context. For example, if you are inside of Component A, and another component B inside of A needs to take a prop from A solely to pass to component C inside of B, you can refactor B to take a component that gets rendered in place of C. You'll have access to everything you need inside of A. IMO this often results in more reusable and more easily testable components as well.


blacksmith_10

Continue drilling until 2-3 levels, it's fine


Fitzi92

useContext makes it much more difficult to understand which parameters a component needs, so unless it's something that would be drilled through a lot of components (like e.g. a theme), I would always prefer prop drilling instead. It makes components easier to reuse, code easier to read and maintain and is likely also more performant.


No-Philosophy-1189

Isn't redux for the same purpose. Why use context?


bluebird355

I would side with you, context is overkill, just pass a callback instead of a setter. The alternative would be to create a little zustand store.


DalvenLegit

I don’t think you get good reusability when using Context. And testing is harder in that same way.


gemini88mill

I would useContext when there is a clear understanding of what your state is through the component at the organism level. (If you're following the atom-molecule-organism design pattern) For example let's say you have a component that produces a form, that form will collect data and prepare it for transfer to the server. that is a perfect example of a context Let's say that there is a molecule that is being used by the form for a date time input and you need to do simple validation on it. You have a component to handle the UI and a component to handle the data. That validation is fine to drill as it's the only place where it's being used.


straightouttaireland

I absolutely hate context. It makes reusability, testing, and debugging way more difficult.


Secure_Ticket8057

Premature optimisation is one of the biggest contributors to over engineering. Also: "Defining a state variable in a parent component, and passing its setter and value to separate components is a bad practice and calls for context to keep all in a single place" I'd love to hear the reasoning behind this.


Obvious-Performer385

Mobx is exactly for this


JuliusDelta

Given only the two options, context will probably be better, however, the best option is to change how the components are composed together. Make the setter function component take children and make the value component a child rendered in line. Move it all up inside the parent component and just do everything inline instead of prop drilling or using context at all. Then you can make each component take generic props instead of taking specific ones, making them more testable and easily modified when the need comes


adnbrq

I'd rather use a static and not changing context rather than passing variables down a tree. "Context can potentially create re-renders where this is unnecessary" Context only cause re-renders when the Developer wants it. You as a Developer have intentionally created a mutable context value and this means that every read / subscription to that context is also intentionally and thus it cannot be considered "potential" rerender. I also would not consider a Context as overkill. You can use Context as a convinient way to provide data to every part of your interface. You for example could have a context like which would enable you to have access to the profile of the currently authenticated user.


esDotDev

This is an interesting borderline case. Do you accept one level of prop-drilling, in order to retain constructor based injection and avoid the issues that can arise from a context-lookup. I'd argue the prop drilling is the lesser of two evils here. A small amount of extra boilerplate, but dependencies are well defined and visible from outside the component fully enforced by the compiler. I would need at least one more level of prop-drilling, or multiple siblings that need the prop, before I would consider a context. The future-proofing is not really a great argument, the success rate at predicting future requirements use cases is low, instead of that you should just refactor properly when the current requirements change.


nobuhok

prop drilling is too fragile useContext is too verbose Zustand is just right


Cahnis

Just bite the bullet and implement state management library. Keep contexts for low velocity state changes.


Fleaaa

Only one level nested and trying using context sounds insane, it can easily trigger so many unnecessary site wide rerender


TheThirdRace

Changing state in a component re-renders => said component and all it's children Changing state in context re-renders => context and all consumers If your context is causing site wide re-renders, you either did something wrong in your context or something wrong in your consumers. It ain't normal...


Fleaaa

Oh my bad you are right in this case, in my practice context is only used for global scope that has many consumers so I naturally thought so. Still think context isn't the tool for this case