React/Redux : Events - state of the art - reactjs

What is the state of the art in React to implement an event based pattern ( Publisher / Subscriber).
From one side we have components that listen to those events. Some of these components are visual, e.g. a chart that draws the result of a query other might not be visual, e.g. if we want to have components that manage queries and results.
From the other, any component can generate events.
Our first idea is adding in Redux the full list of events and their values and on each component implement in shouldComponentUpdate() a smart code that stops if the component does not listen to the changed events.
This doesn't look very elegant as all components listen to all events. Is there a more elegant solution (kind of state of the art) ?
Of course we want to be able to go back, forwars, save state and all this small details :-)

I think what you need to know is not so much "What is the state of the art" as "What is the canonical way" of doing publish and subscribe with React and Redux.
The short answer is that if you have a complex enough application, you should organize your store so that the application state is divided into slices and use the container pattern to separate the responsibility of publishing and subscribing. This way you avoid the problem you mentioned where you don't know what in the code base is changing the store and what is responding to the changes. You can look at a container component and see how it handles UI events from its children and produces a state change event. Or you can see how the container handles a state change and then drill-down the hierarchy. If you can make a Redux slice the responsibility of a single container, it's that much easier to think about events. All other components are not subscribed to the events per se, instead they receive the changes they need to render from props originating from the container component. And they notify the container component of their own events through callbacks passed down through props, so the container can publish them. This can go a long, long way, and if you feel that you need to pass down props too many levels, you can use React.children to flatten the nesting a bit, or in rare cases context.
The longer answer is a bit more difficult, since publish and subscribe is not super meaningful when talking about React. React should ultimately be responsible for rendering. As you mentioned, not all events are UI events. But if you model things such that all events that you can publish and subscribe to boil down to changing the store or reacting to a store change, then you can build your app focused more on Redux. Managing the queries and results you're talking about should be done in simple JavaScript modules without React. Then you can use a container component to glue the store to that module.
There are additional patterns people use like action creators and selectors. Those are good because at least the intent is to keep code bases familiar. But things are still moving a bit fast, with React moving towards the Hooks API and React-Redux trying to catch-up. But slices and container components aren't going anywhere, they are a natural way to separate concerns.

redux to store the events
reselect/connect to subscribe to them (https://github.com/reduxjs/reselect#connecting-a-selector-to-the-redux-store)
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
Now you have your subscribed event as a prop in the component, the component will re-render only if the prop changes. No need for shouldComponentUpdate because your component doesn't have to listen to the whole store, it will only get the part of the store you define in the selector.

Related

What is the recommended approach for large react app with react-query?

I've recently started using react using functional components and react-query and it has been working fine except that I'm not clear about how to organize components properly.
the way I'm used to designing components is by having a top level component that performs all data access and passes data to it's child components through props. it also passes various callback handlers to child components so that when action is required, the top level component would update the data and passes the new data to child component. so in my case all calls to useQuery(), useMutation() reside in top level component but it's making the code extremely messy. but it's much like a page containing various child components that only display data or help user interact with data.
function Page(){
const [page, setPage] = useState(1)
const [size, setSize] = useState(10)
const persons = useQuery('persons', async ()=> await getPersons(page, size))
const addPerson = useMutation(async (args)=> {
const {id, name, desc} = args
await addPerson(id, name, description)
})
const person = useQuery('persons', async ()=> await getOnePerson(page, size), { enabled : false })
const addPersonCB = (id: number, name: string, desc: string)=> {
addPerson.mutate({id, name, desc})
}
// complex if/else logic to choose child components
the second approach is to disperse react useQuery() and useMutation throughout the components where it's need. and to further simplify things, if rendering logic is complex, each component would have a parent component that would perform the action and passes data as prop.
function PersonCard(props: PersonCardPropsType){
const {data, isLoading, isError, error} = useQuery(`personQuery${props.id}`, getPerson)
if(isLoading)
return <Wait />
if(isError)
return <Error reason={error} />
const record = data as PersonModel
return ( <PersonCardUI person={record} />)
}
and there are may compoenents for grid, form and etc each one in form of pair like
<PersonEditor />, <PersonEditorUI />, <PersonGrid />, <PersonGridUI />
in this case the calls are dispersed everywhere in the code. I want to know
For large projects, which approach is recommended and why?
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
It is generally considered a best practice to use useQuery where you need it. The separation into container / presentational components, while still possible, has been largely deprecated since hooks came around. With redux connect / mapStateToProps, it was best practice. Now, even in redux, you just call useSelector and useDispatch close to where you need it. This is no different in react-query.
There is a great talk on this subject from Mark Erikson: Hooks, HOCs and tradeoffs that I can totally recommend watching.
Using react-query hooks where they are needed not only avoids prop drilling, it also makes it easier for react-query to keep your data up-to-date, because more observers (=components that call useQuery) are mounting. This is also why it's best to just set a staleTime when you want to customize refetching behaviour. I've written about this in detail in React Query as a State Manager.
Is the mix-match of Redux & react-Query okay? like for instance a grid has page size and page number which should go in redux, maybe?
Totally, as long as you don't sync server state to redux. page number and page size are considered "client state" because the client is control over that state. The user selects the page, and the server responds with the data depending on it. I also like to abstract that away together in custom hooks:
const useData = () => {
const pageNumber = useSelector(state => state.pageNumber)
return useQuery(["data", pageNumber], () => fetchData(pageNumber))
}
that way, you have a hook you can use wherever you want (without passing anything to it), and it re-fetches data automatically if the pageNumber changes.
Is it okay to use pure axios/fetch at some places with redux/react-query it's considered a frowned upon way of doing things?
If you don't need caching / loading states managed for you etc then sure. The only thing that comes to my mind where I don't want a query / mutation might be file downloads or so :)
Let’s step back for a minute and see what each of these abstractions help us achieve & then it makes it easier to see how one should architect an application.
Very broadly, you have
useQuery useSwr
Redux or any other global state management tool
concept of lift state up
Context api
Saving state in url (filters say or link to say a product/item page)
useQuery useSwr are responsible for managing remote state and provide a snapshot of your data that resides behind a remote API. They help with fetching data, caching, error handling, showing loading spinners. They give us additional features such as refetch after a certain interval or refetch on focus, refetch a certain # of times on error etc. Whether we then decide to call these individually in each component or a parent component is a matter of design i.e. implementation detail.
Redux and other global state management tool help with managing local state, globally throughout your client application. A great example of that would be your auth’ed user. That information probably is required globally so redux sounds like a great place to have that information. Shopping cart is another example that might make sense in a redux store.
Lift state up when you want to share information with siblings. This stackoverflow question is a perfect example of lifting state up. DataTableComponent now becomes a controlled component or what you might call a presentation component.
If lifting state up becomes too cumbersome then look at context api or perhaps redux.
So, taking shopping cart as an example, you might decide that context api makes better sense or perhaps lifting state up makes more sense rather than having it in a redux store. My point being that there isn't one way of doing this and it will be a judgement call.
Lastly, you might have a page with filters say, and you may want to give your users an ability to send a link/Url & you might want the recipients to see the same information as the sender. So, now you must save state in your url via say query strings.
Going back to my comment above, there is no one way of doing things. So, you may start off by lifting state but then realize it's too cumbersome so you may switch to context api or even redux.
But each of these abstractions usually do have a place in your application & I have used all the above abstractions in conjunction with each other quite successfully.

performance: connecting to the Redux store vs passing data through props

I'm using the redux store for all my major components, but for smaller components, I've just been passing down regular react props. Which is more efficient?
If have many smaller components, does it make any difference to pull from redux or pass down in regular props?
The Redux docs says:
many individual components should be connected to the store instead of just a few
This sounds like I should be connecting even my smaller (sometimes very tiny) components too. Currently, I am using the store in many, but not all, components.
connecting smaller components is more performant because when Redux state changes, only the affected components will rerender. Passing down regular React props is less efficient because when state changes, that change gets passed through many nested components down to the small component, causing all those components to rerender.
While connecting small components is more performant, it comes with some drawbacks described in this answer. To summarize, connecting a component couples it to the Redux state. So, the component must be tested with a store, and the component can't be modular unless store implementation goes with it. The solution to this is to separate a component's stateful logic from its stateless logic.
const StatefulTodo = ({ id }) => {
const todo = useSelector(state => getTodo(state, {id});
return <StatelessTodo {...todo} />
}
const StatelessTodo = ({ ...todo }) => {
return (
<div>
<p>{todo.title}</p>
<p>{todo.description}</p>
...etc
</div>
)
}
With this approach, a Todo UI can communicate application state while being decoupled from the state, testable in isolation, and reusable.
In practice, you should decide which components to connect given the tradeoffs. A perceivably performant app doesn't have to prevent every possible unneeded rerender. And you can always use the React devtools profiler to see which components need to be optimized.

Reactjs accessing state of another component

Basically, I have a component called Timeperiod which is a dropdown where users will select values such as "today", "yesterday", "last week", etc.
I want to re-use the <Timeperiod> component in a lot of other components and the problem is I wouldn't be able to access which value they chose if I call it in another.
I know an alternative would be to call my other components inside of Timeperiod and passing them properties based on Timeperiod state but wouldn't that mean I need to create multiple Timeperiod like classes?
Would redux or something be a good way to handle these situations?
Here's my codesandbox: https://codesandbox.io/s/ypyoq8zoz
Right now I only have App.js calling Timeperiod, but I will have many more classes calling it too. I want to be able to access the date chosen in App.js while making the Timeperiod class re-usable.
To decide if you need Redux and how much of Redux do you need, you have to consider carefully the whole application you are developing. Redux adds some boilerplate, which might be too much for the benefits you would gain.
You can however solve your problem without Redux.
Without Redux
One of the key ideas under React is called "Lifting state up". This means that if several components need access to the same piece of data, you should definitely move the storage of that piece of data in a component that is a common ancestor of all those components (see https://reactjs.org/docs/lifting-state-up.html).
In your case, it should not be responsibility of Timeperiod to keep memory of the selected date, but of App (indeed, the closest common ancestor of all components that need access to the selected period). Timeperiod should take the selected date from app via a property, and notify App when user changes the selected date using an event (again, a prop containing a function).
class App {
constructor(props) {
super(props);
this.state = {
start: new Date(), // initial value
end: new Date(), // initial value
}
}
render() {
return (
<Timeperiod
start={this.state.start}
end={this.state.end}
onStartChange={newValue => this.setState({ start: newValue })}
onEndChange={newValue => this.setState({ end: newValue })}
/>
);
}
}
With Redux
Redux allows you to keep a global state and access it in special components called Containers. You can put how many containers you want, in any point of the document tree. This seems great, but has several drawbacks:
Too many container components degrade performance
Having full access to whole state in any point of the tree could create problems if you are not super careful. A component could modify some data it should not be allowed to access, and so on.
Redux introduces some boilerplate that might be too much if the application is simple
For any update you have to define some actions capable of handling it, create a reducer to produce the next state from the previous state and the action, and connect together all these pieces
Conclusion
It really depends on your application whether Redux is good or bad, but if your component hierarchy is not too deep and your app not too complex, vanilla way could be better
So, to follow up our comments. Here's how you could approach it:
The Timeperiod component expects a getDate function that expects a title argument. When you render the Timerperiod component, each time it has a separate state and props.
Check out how I rendered more than one(in the app.js), to show that in action.
Using redux what you could do is have a within your state a timePeriod sub state handled by a dedicated reducer which stores the user's choice.
Then each of your TimePeriod component will be hooked to this state using something like
const ConnectedTimePeriod = connect(
state => state.timePeriod,
{
onTimePeriodChoose (timePeriod) {
return {
type: "CHOOSE_TIME_PERIOD",
timePeriod
}
}
}
)(TimePeriod);
Which hydrates your component with the global shared state and provides a function as a prop to update the time period.
The ConnectedTimePeriod can then be used everywhere and will share the global state.
I would suggest you take a look at the react-redux docs for example and details.

Access state property inside mapStateToProps

I need to access specific properties of the redux state inside my containers. These properties will in turn be used as a key to fetch other nested properties and map them to component props. I use immutableJS.
Is the best way to do this inside the mapStateToProps? Will this cause any performance overhead or is it OK since the mapStateToProps already receives the whole state as a parameter?
Here is an example.
state selectors:
export const userSettingsState = state => state.get('userSettings');
export const transactionsState= state => state.get('transactions');
userSettings is an immutable Map and transactions is also a Map of userID and transaction list pairs.
const mapStateToProps = (state) => {
const curUID = userSettingsState(state).get('currentUserID');
return {
trList: transactionsState(state).getIn([curUID, 'trList'])
};
};
I need the currentUID to access the user transactions. The above sample works fine. But is it the best way?
Great question. Yes, this is the preferred way to handle computed props and to generate derived data. Not only is this the best way, but it's also very performant. Cory House (a very good React dev well known in the community) tweeted about something similar to this a few days ago and his same methodology applies to mapStateToProps(). Your only other option is to add a compute method to componentWillReceiveProps() AND componentDidMount(). This get's messy fast as you could imagine.
Now, some reservations about the way you're doing this.
Don't put async calls in here! This will also cause issues with rendering components as mapStateToProps() runs before the component mounts. If your component is waiting for promises before it mounts, you're going to take performance hits. This is probably obvious.
Speaking of performance, I am personally generating derived data exactly like this for a dropdown box with 8,600 entries that on user input, applies a fuzzy filter to those 8,600 entries, re-renders the dropdown list, and displays a new filtered list. There have been zero performance issues and the component is smooth as butter.

Passing actions down the stack via props

I have been using Redux for a few weeks now and I am very happy with it and I am getting used to a Redux way. I am using it with React. Still plenty to learn as both things are new to me.
I have a one problem - maybe I am doing something wrong ... Let me show you:
I have a component structure that looks like this:
App //root of the application aka smart component
CampaignTable
CampaignHeaderRow
CampaignHeader
CampaignDataRow
CampaignData
The App component is initialized as(only related code):
import * as DashboardActions from '../actions.js'
function select(state){
return {
campaigns: state.campaigns, // array of campaign objects, has name, id, time created etc
order: state.order // sort format "byWhichField"
// will affect the way how campaigns are displayed
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(DashboardActions, dispatch)
}
export default connect(select, mapDispatchToProps)(App);
App has now access to state and all actions as props.
The problem I see with it right now is:
I would like CampaignHeader to fire the action that will change the state.order state. Let say I will make <th>Text</th> inside CampaignHeader clickable. This will fire the action to change state.order which will in turn affect campaigns order on a next rerender.
So I have my action available inside App props. To pass it down to
CampaignHeader I would have to:
pass it down to CampaignHeader as props
assign it to variable inside CampaignHeader and pass it down as props to CampaignHeaderRow
assign it to variable inside CampaignHeaderRow and pass it down as props to CampaignHeader
assign it to variable inside CampaignHeader and fire the action inside onClick event....
This is a lot of boilerplate, assignments and bag passing! Just to get action fired.
All the components along the way are aware of this action.
When I decided to implement this feature I have opened CampaignHeader component file. I have added the logic and called the action, I have added the action to action file. All I needed is to get a props set. CampaignHeader component doesn't hold a reference to its parent so I didn't know straight away where should this props be injected from(in this example is obvious but I hope you get a point).
What if I will have even deeper component structure?
Is this approach correct?
Could I tackle this problem differently?
UPDATE:
As #Errorpro suggested will it be ok to connect single action and state.order to CampaignHeader?
Worried about: If I will do it once I will be doing it all the time.
There's a discussion in the issue-section of the Redux github repo about wether it's okay to use multiple connects or if everything should be passed down from the top through props, and in there Dan Abramov (the creator of Redux say's:
[...]
Nobody advocates a single connect.
[...]
The "single" only refers to small apps like the one we create in the
example. Please feel free to amend the docs to better clarify this. I
am now busy with other projects so please don't expect this issue to
get any movement unless somebody makes a PR. You can do it too.
The comment probably makes more sense in context though so check out the entire issue thread https://github.com/rackt/redux/issues/419#issuecomment-140782462
If you use redux you should know about dumb and smart component. So we use this sctructure:
component
index.js
Component.js
ComponentContainer.js
Dumb component just get props and render it. More interesting in smart component. Here it is:
export default compose(
relay({
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
any data from relay
}
`,
},
}),
connect(
null,
{
onCreate: createUserAction,
},
(stateProps, actionProps, parentProps) => ({
...parentProps,
onCreate={() => actionProps.onCreate('user')},
})
),
)(Component);
So, parentProps and onCreate function will be in dumb component's props. There you can use this.props.onCreate and invoke it or pass it farther.
Passing the actions - like any other props - from parent to child to grandchild etc is the idiomatic React way. In my opinion your approach is correct; even if it feels wrong.
But there are a couple of alternatives.
There is a feature in React called context. Context permits the passing of fields from a higher order component to a lower order component whilst skipping the middlemen. However, it's an experimental feature so I would recommend avoiding it for now. https://facebook.github.io/react/docs/context.html
Additionally, there is a Redux specific way where you can make any lower order node of your choosing a "smart component" (in the Redux sense). That is, you wrap your class export in the connect function to plug it directly to the store, in the exact same way you do for the Root node.
Personally I tend to stick to the top-down way. There may be a fair bit of boilerplate involved but at least it means your application is easy to reason about.

Resources