I have a function that interacts with local state/hook that I want to be able to call from other distant component(s)
For instance, calling a React Apollo mutation that has local side-effects from the response in that component from other areas. Also keeping code DRY without duplicate the hook in everywhere its used, and setting global state that is only read in this one component.
Another similar use-case would be calling a refetch() from specific UI elements - like a button in a distant component (so refetchQueries would not be applicable here)
Let me know if additional clarity or a specific example would help.
Options I've considered:
EventBus: there seems to be little about event buses as a recommended approach. Most posts link to React recommending this in old docs from years ago. It seems feasible, potentially could even setup a pubsub with hooks, but I'm not sure if it's an anti-pattern. In Vue it is common.
Global State:, Context would seem to be overkill for such a specific and small use case, but I am already using an atomic state library. I could set an atom to hold the function on mount, and call the function from the atom where I want.
What would be some practical ways to do this? I'm curious as to other possible approaches, trade-offs, or insight into other implementations you may have used.
Related
I may choose to put some states as component states, and I thought that's what useState() is for.
However, the book Learning React, O'Reilly, 1st Ed, 2nd Release, p. 185, said:
With Redux, we pull state management away from React entirely.
Also, in the official Redux website, the example "Real World" also even make isFetching a Redux state, instead of component state. (Its GitHub repo).
I also found that in some project in some company, coworkers seem to favor everything as Redux state even when it can be a component state.
For example, in that same book, p. 185, it said we even keep which messages is expanded or not into the Redux store. But which message is expanded, seems entirely local to this component and it has nothing whatsoever to do with other components at all. In the case of isFetching, as least I can understand it that what if the whole app wants to unite the isFetching of any component into a global spinner indicator.
This webpage also says:
The solution in idiomatic React – i.e., code that was written the way an experienced React developer would write it – is to have what's called a single source of truth, which is one master state for most if not all of your application, then send that state down as props to your child components.
be a pragmatic programmer: go for stateless components where possible
I don't quite understand it. How does it work? When a state can be a component state, would it be perfectly ok to put it as component state? Or in React / Redux, the rule is to make everything into a Redux state? (in such case, then what is useState() for?)
Update: I like #RemcoGerlich's answer, and I put two links as a comment under his answer. Those are official docs stating "Don't put all states into Redux".
It is an eternal discussion. There are several types of state that have their own best ways to solve them:
Navigation related state, to go to different "pages" or kinds of views in your application. For this, using the browser URL has many usability advantages, and using React Router is much more natural.
State retrieved from the backend through its API, this isn't really state of your frontend at all, you have a cache. For this a library like React Query is much more suited (it handles e.g. your "isFetching" state, as well as reloading things after a while).
Small bits of state that only have local significance, like whether a small control that hides some detail is now open or closed. I feel things that are used only locally should be stored only locally, like in useState.
Often the number of things left is quite small, and putting them in one or a few Contexts is fine, except if your application becomes quite complicated.
But, Redux comes with its own advantages -- a single way to make undo functionality, a way to serialize / rehydrate its entire state, and Redux dev tools that allow looking at the action history in case you find yourself debugging complicated effects to do with the order in which happened. If you use this heavily, then you would be inclined to store more state in Redux than you would if you only make a little use of these advantages.
So it's matter of degree, it's more art than exact science, there are no strict rules. "Put everything in Redux" certainly sounds suspect to me, but who knows about your team.
If your state and operations on that state are moderate in size then react Context API is really smart enough to support you. Even, #danAbrvmov writes:
React Redux uses context internally but it doesn’t expose this fact in the public API. So you should feel much safer using context via React Redux than directly because if it changes, the burden of updating the code will be on React Redux and not you.
You may like reading his article: You Might Not Need Redux
As for, you see some companies and projects using Redux, this is because Redux is out there for a long time and Context API is newer. Moreover, if you really need some features like redux-thunk, you can still use it.
I doubt you clearly understand how the state is handled in React.
In a typical React application, data is passed top-down (parent to child) via props. You may like my answer on another post to learn when we may need Context API or Redux at all: https://stackoverflow.com/a/62980048/9857078
Since React 16, the ordering of componentWillMount and componentWillUnmount is no longer guaranteed due to support for async componentWillUnmount.
In our project that uses React + Redux, however, from page A navigates to page B, for example, page A calls componentWillUnmount to clean up the application state then jump to page B, which calls componentWillMount to fetch data and update the state. Since the ordering of them can not be controlled, sometimes the state get messed up.
What would be the best solution for this issue ?
There are several ways to solve such issue. But almost all of them are bad practice (e.g. using HOC with return null in first render and then clear up and do async queries, etc.) I don't recommend such ways.
So I recommend spend a little more time to refactor your architecture and separate the state in Redux.
I found a good Dan Abramov's post about this issue: https://github.com/facebook/react/issues/11106#issuecomment-344573660
The easiest way to do so is to not reuse the same part of the tree in such a way. Think about it: components are generally expected to be independent. What happens if you render two such "exclusive" components at the same time? They will conflict. This doesn't seem like the right component thinking.
I understand it is more code to avoid such patterns, but in my experence replacing a single state tree branch with an object or an array was enough. For example you can key it by ID (even a dynamically generated one), and then instead of tearing down the old one in time for the new component mounting, you can just point the new component to the branch with new ID, while keeping the old one alive. Then eventually you can emit an action that cleans up unused state branches and only leaves the one with the most recent ID.
If possible I would separate the state in Redux so the state isn't shared between pages: https://github.com/reactjs/redux/blob/master/docs/api/combineReducers.md
If that's not possible you could try adding your logic into the functions that navigate.
Some more detail to your question might help me to debug such as what you use for routing and what the state is.
for more information on migrating to the new lifecycle methods look here:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#migrating-from-legacy-lifecycles
You should avoid using componentWillMount life-cycle method and migrate to using newly introduced ones
One of the biggest lessons we’ve learned is that some of our legacy component lifecycles tend to encourage unsafe coding practices. They are:
componentWillMount
componentWillReceiveProps
componentWillUpdate
These lifecycle methods have often been misunderstood and subtly misused; furthermore, we anticipate that their potential misuse may be more problematic with async rendering. Because of this, we will be adding an “UNSAFE_” prefix to these lifecycles in an upcoming release. (Here, “unsafe” refers not to security but instead conveys that code using these lifecycles will be more likely to have bugs in future versions of React, especially once async rendering is enabled.)
You should do data fetching in componentDidUpdate life-cycle method and I prefer doing catching props in getDerivedStateFromProps(nextProps, prevState) method.
I explained this a little In this stackoverflow post and there is a example of using this method.
Without some global state management library like Redux, what would be a good way to pass the user around through my components? I'm using Firebase, so I'm imagining my top-level app component will be listening to auth changes and storing the user somewhere. Here are the options I've thought of:
Passing 'user' to each route in props (and on to children that need it)
Using a global object of my own
Using Context
1 seems like a hassle.
2 doesn't seem like the right way and like more of a hassle, wouldn't I have to listen for changes and set the user in my states? It's what they show in the React Router docs but I'm guessing that's just for simplicity's sake and not best practice.
3 seems like the best way to me but it seems like it's discouraged:
The vast majority of applications do not need to use context.
If you
want your application to be stable, don't use context. It is an
experimental API and it is likely to break in future releases of
React.
I would still say that you use context. It's the best way I have found to thread a common state throughout the react tree.
Even though they discourage it, all popular react libraries (including react-redux) use it big time. It's not going to be removed any time soon. And the use case seems very legit, provided you know what you are doing.
I am trying redux right now and very excited about ideas behind it, but first real task ruined the whole thing.
In Redux we should store immutable state and produce some reducers to transform it. It means that every state could be reproduced by given previous state and a list of actions fired.
But what if I need to store third-party/legacy stateful object? For example it may be something like gallery or websocket client.
I assume that I'm able to use reducers to start/stop/manage it somehow, but the state I have no longer stateless and it not guaranteed to be repeatable with given list of reducers (websocket client may generate new session id or even be unable to maintain connection).
What is convenient way to solve these issues?
As I see this, your problem boils down to: How do you mix Redux with stateful components (legacy/third party)?
You are right, Redux is better suited for controlled components, that is, components that are mostly stateless and are supposed to receive everything as props. Just keep in mind that having some state doesn't necessarily make the component unusable in Redux. Example: For an Autocomplete component, the "open" state (whether or not the dropdown is visible) doesn't necessarily has to be controlled by Redux. So, depending on the how the component is implemented, you're definitely having a hard time integrating it into Redux, or maybe not.
You have 2 options: You either refactor the problematic components so they're now controlled, or you don't keep their state on Redux (you're not required to). The answer will vary depending on the circumstances. There's not a globally accepted solution for your case, that I know of.
Here's a nice explanation from the FAQ docs. In short:
Do I have to put all my state into Redux? Should I ever use React's setState()?
There is no “right” answer for this. Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state.
Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it.
There's a bunch more info there -- worth reading the whole thing.
Remember: There is no magic to redux; it's just a wrapper/container that is setting the props for you.
You can even use shouldComponentUpdate to manage how changing your stateful component's props should trigger the rendering.
Or leverging on connect's
areStatesEqual
areStatePropsEqual
areOwnPropsEqual
areMergedPropsEqual
for greater control
related: https://stackoverflow.com/questions/58027300/what-is-the-main-difference-between-using-react-redux-hooks-and-react-redux-conn#:~:text=connect%20can%20be%20used%20with,used%20with%20function%20components%20only.&text=Using%20hooks%20is%20simpler.,disposal%20in%20comparison%20with%20connect%20.
I'm starting a new React app and, seeing all the news in the ecosystem, I want to go slow and actually consider my choices, starting with just React/Webpack/Babel, and introducing more.
The first of these choices is whether to use Flux or not (more precisely, Redux, which looks great and seems to have won the flux wars). Here is where I am:
I understand Redux's benefits, summarized on SO by Dan Abramov. They look great, but I'd rather introduce things one step at a time.
In plain React, parent→child communication is done with props, and child→parent communication happens with callbacks. See Doc / Communicate Between Components, or SO / Child to parent communication in React (JSX) without flux, or this codeacademy Redux tutorial which starts out by saying "no need for Redux if you're fine with plain React and all your data in a root component".
Which looks just fine for my purpose...
... however, the sad part is that these callbacks have to be passed down the component chain, which becomes quickly tedious as the levels of nesting grow.
To solve this without introducing new dependencies, I found two articles (1: Andrew Farmer, 2: Hao Chuan) encouraging usage of the recently-introduced context feature of React.
→ Using context would let me expose my model-mutating callbacks to my child components. To me it doesn't sound like a horrible misuse: I wouldn't be passing model data, just references to functions for binding on event handlers.
Does it sound sane?
Any other plain-React suggestion for convenient child→parent communication?
Thanks.
Answering my own question after watching Dan Abramov's Getting Started with Redux series, which I warmly recommend.
Yes it looks like it's sane: Redux faced the very same problem and solved it with Context (at least initially, the implementation may have changed). It is implemented and packaged (among other things) in the react-redux bindings under the <Provider> component and the connect() function.
Initially, at the start of step 24 - Passing the Store Down Explicitly via Props , we have a Todo app with a Redux store available as top-level variable. This sucks (for 1. testability/mockability, 2. server rendering needing "a different store instance for every request because different requests have different data"), so the store is demoted from top-level variable to a root component prop.
As in my case, having to pass the store as prop to each component is annoying, so in 25 - Passing the Store Down Implicitly via Context, Dan demonstrates using Context to pass the Redux store to arbitrarily nested components.
It is followed by 26 - Passing the Store Down with <Provider> from react-redux, explaining how this was encapsulated in the react-redux bindings.
And 27 - Generating Containers with connect() from React Redux further encapsulates generation of a container component from a presentational component.
Personally, I find the question quite simple to answer, if you think about the way dependency injection in Angular works. In Angular you have your DOM, and then you have all those services, providers, factories, constants and whatnot, which are independent of the DOM structure and can be imported simply by mentioning their name when creating modules or controllers.
I liken the use of this.context to DI. The difference w.r.t to Angular is that instead of stating the dependencies using function parameter names, you use childContextTypes and instead of getting the dependencies as function arguments, you get them through this.context.
In this sense, asking the question whether passing model mutators through this.context is sane, boils down to whether it makes sense in Angular to register your model for dependency injection. I've never seen a problem with the latter, therefore I deduce that the former is also quite OK.
I'm not saying that it suits the spirit of the library, though. Dependency injection and in particular managing dependencies between injected component is not as explicit, so one may argue that it's not the React way. I leave this philosophical discussion to others.