Note: I am not using Redux (only Context + Hooks)
Introduction
I have a screen "Profile", that renders a TabView.
This TabView, renders two components as scenes:
UserPosts
UserInformation
My Profile screen, fetches the user data from my database, as it has to render some stuff that depends on this data.
Also, it is passed down via props to my "UserInformation" component, as it just renders the user data. So... there is no data fetching in my "UserInformation" component.
In the other hand, my "UserPosts" component is responsible of fetching the user posts, as it renders a component "UserPostsGrid" which renders those posts with pagination (endless FlatList).
Note: Data fetching is performed in custom hooks, not directly inside
the component (also, those hooks manages the respective stateful
data).
Problem
The main problem comes when I need to refresh the user data and the user posts when the user pull-to-refresh inside the "Profile" screen.
There is no problem for this screen to fetch the user data, as I have said before the "Profile" screen is responsible of that action.
But the posts are the stateful data of my child...
Multiple solutions?
I have thought to solve this problem using the current solution:
React.forwardRef() + React.useImperativeHandle()
With this, I will be able to pass a ref to my "UserPosts" and use imperative programming for accessing the data fetching method: userPostsRef.current.refreshPosts();
I have never seen other scenarios for solving this problem like this, but it should work. Instead, other coders implement a ContextProvider to handle all the data and be able to access it wherever they want. The main problem I notice here is: extra memoization + extra re-renders.
Another solution might be to implement all the data fetching inside the parent, and pass the respective callbacks via props to the child. The main problem I notice here is that we are creating some kind of "God Component" and we might have multiple lines of code...
My question
Is it an anti-pattern or a bad practice to implement the first solution ?
To answer your question:
Yes, it is an antipattern in React, because React is built around the idea that you specify the state of your app, and then let React render the components depending on this given state.
If something should change, you are supposed to specify a new state that represents the difference to the old state.
Refs should only be used if there is no other option. Basically, if you use Refs you say "I don't want to use React for this specific part of my code".
As always, imperative code using refs should be avoided in most cases
To use state instead of commands:
I would approach this kind of problem by trying to translate the imperative action into some state of information.
The user says "get me the latest data", that is a command, i.e. imperative.
Now I would ask "what is the user telling me about the state of the data right now ?"
An answer would be "this data is not up to date anymore", and this can be expressed as a state, and passed around via props.
A solution for your problem:
E.g. you might use a prop needsUpdate, which is set to true when the data should be updated, and to false when fetched.
To avoid unnecessary renders (setting needsUpdate = false would cause another call of e.g. useEffect, even if nothing happens there), I probably would use a child component prop like latestUpdateRequest and a child state like latestUpdate which holds a counter or timestamp, and then compare if(latestUpdateRequest > latestUpdate).
To inform the parent about the change just pass a callback function like dataUpdated() as a prop.
Related
I'm working on a project right now, where I have App.jsx, and my other components inputComponent and articleComponent. App.jsx contains both my inputComponent and my articleComponent. However, I've written the fetch request to be within inputComponent.
In this case, my articleComponent relies heavily on the data retrieved from the fetch request, and so I'm thinking, a possible solution would be to move the fetch from inputComponent to articleComponent. Another one was to use callbacks to pass data from the child component to the parent component. However, based on some answers I've read here and online, it seems that this method of passing data from the child component to the parent is a React anti-pattern as well.
I also think that the fetch call ideally shouldn't be on App.jsx, but feel free to correct me if I'm wrong.
Ideally, what I need is for the data retrieved from the fetch call (which comes from user input submission in inputComponent) to be able to be passed in as props to the ArticleComponent so that I can render the articles with the proper data after fetching it from my server. What would be the "React" way of doing it, and also the best way in general?
As #misternobody said, the 'normal' way is to pass data down to children as props and yes, if a parent's state change - it re-renders all of its children (that's the idea behind React).
So you would pass your input from inputComponent up to App (with the use of a callback passed as a prop), fetch inside your App, then pass the response down to ArticleComponent
If for some reason you want to dodge the rendering of App component in this process, you'd have to use ie. Redux and NOT connect App to the store. Then, if you'd properly map store's state and diapatch to props in your 'child' components, it should work as you intended without bothering App.
Normally the properties you want in all children sit in the parent class.
I would write the fetch right there, and pass it to the children as props.
When I am in a situation where I need pass a significant size of data to a child component, it seems the easiest way to do this is to store it in state and then pass it to children as props inside the render function. I am wondering if there is a less-intensive way to do this.
For instance, if I have a relationship like so:
<PhotosPage>
<PhotoFeed/>
</PhotosPage>
I know that I want to fetch the 'photo feed' data when the PhotosPage mounts. Let's say I fetch this data in PhotoPage's componentDidMount() function. It seems so obvious to me that after the fetch request is completed to just set state, something like:
this.setState({feedData: response.data})
And then I can simply pass this.state.feedData into the PhotoFeed component as a prop:
<PhotoFeed feedData={this.state.feedData} )}/>
This is a very basic example that does not include the rest of the complexity of the component. Is there a less state-intensive way to do this? I'm sure this is a very common container to view relationship and I want to know if there are any 'better' ways to do this without redux,etc. I just worry that I may be using state when I don't have to.
This is generally an accepted method, the technique is called container components. Here is a good write-up about these.
Here is another discussing some different options.
Basically, I would like to fetch the latest data every time user navigates to different tab. For example, I have summary page and details page for customers, which both includes the same customers container component. I'm doing my initial fetching in componentDidMount function, which doesn't get triggered in route change, as component stays in the DOM. Component necessarily doesn't receive new props either.
One solution would be to customize router link with fetch action being dispatched based on the href, but I'm pretty sure this is an anti-pattern, as it complicates code a lot, is hard to maintain, mixes concerns, and may result in a double fetch if target route didn't contain the component already.
Does React provide any lifecycle hook or mechanism that allow me to fetch in response to navigation event alone?
I'm using React-router-component and Redux.
As far as I understand both tabs happen to contain the same component and so React re-uses the DOM and doesn't create a new component instance, right?
You can use React's special key prop to force React to replace a specific instance.
E.g.:
<div className="tab-content>
<div key="tab-1" className="shared-component-across-tabs">
shared-component-across-tabs
</div>
</div>
And in the second tab you'd use key="tab-2".
Otherwise your route handler component will receive props.location.pathname from the router. You can also implement componentWillReceiveProps and check if the pathname has changed and re-fetch the data in that case.
I am a bit lost on what to keep in the state tree of Redux.
I saw two conflicting statements on what to store in the state tree(s).
React doc tell us that only user input should be stored in state trees.
The original list of products is passed in as props, so that's not state. The search text and the checkbox seem to be state since they change over time and can't be computed from anything. And finally, the filtered list of products isn't state because it can be computed by combining the original list of products with the search text and value of the checkbox.
Redux doc tells us that we often should store UI state and data in the single state tree:
For our todo app, we want to store two different things:
The currently selected visibility filter;
The actual list of todos.
You’ll often find that you need to store some data, as well as some UI state**, in the state tree. This is fine, but try to keep the data separate from the UI state.
So React tells that we should not store data (I am talking about data of the todos) and, for me, Redux tells the opposite.
In my understand I would tend on the React side because both React and Redux aims to predict a UI state by storing:
all what can't be computed (eg: all human inputs) and are part of the UI:
checkbox value
input value
radio value
...
All minimal data that could be use to build a query and send it to the API/database that will return the complete user profil, friends lists, whatever...:
user Id
creation dates range
items Ids
...
For me that excludes all database/API results because:
that stands on data level
could be computed by sending the right (and computed by pure reducers) query.
So what is your opinion here?
React documentation about the View Component state, but Redux documentation about the Application state. So, there is no conflicts between definitions.
If we talk about Redux - you make all your components without state (and transform stateless root component to stateful with help of react-redux's connect function). If you have large response from the server and you show your data with pagination / filters, you can treat your application state as what you see on screen and not put all data in Redux store, only what you need to render (for example, 100 rows to show page and total number of rows to show pagination). There is no restriction for this. The whole data you can put into another place. For example, in another data container in web-worker (I make a full request in web-worker and fetch from there only needed data to display).
Added after question edited:
The original list of products is passed in as props, so that's not state.
In that example, the reason why list of products isn't state - it's already in props. It means that the one of parent components have this as state.
I feel that the problem is that originally Redux was pushed really hard, and some people were so purists, that they argued for separating everything to Redux and re-rendering the whole application on every change. And then we ended up with this response of the creator, which actually only added a confusion, because redux was and still is a de-facto standard for new react applications, and a lot of tutorials assume it.
So, I feel that people are pressured from each side, and often they do some things without real understanding why they should (especially newcomers creating constants, actions and reducers). So, for those who read it, please start without redux, and keep it just in local state (but try to keep in some component like DataContainer).
For those who need redux, rule thumb is to put all async data (so all requests go through redux), and data which is needed for independent components. If components obviously located nearby, keep it in a local state and pass as props.
Redux is a very helpful library, but it's power is needed only after you start to have at least several routes, different query options and some complex UI. Before that there is a good chance that you are overengineering (but, of course, if you are sure that you will exceed this size, feel free to start with Redux). And again, you'll literally never will want to put your slider or small dropdown position in the store -- react's state serves perfectly for it.
I am writing a complex react app and using Cortex as my central model. The philosophy with cortex is that it wraps your data and on changing the data, calls a complete re-render from the root. This works great especially when you have non hierarchical views changing state and affecting the other.
The issue that I am facing is maintaining states/props on re-render. For example I have a certain hierarchy which goes like this:
<Page>
<EditorCard>
<Editor/>
<PublishButton/>
</EditorCard>
</Page>
The EditorCard needs the JavaScript instance of the Editor in order to make changes to the Editor on clicking the PublishButton (I am using an external library inside Editor which exposes methods for editing). Hence the Editor on ComponentDidMount sets the instance as a prop on the EditorCard by calling a function passed down to it.
My issue is that when I click the PublishButton I change the value of the cortex object which causes a re-render from the root and I loose the props for that Editor (since component is already mounted ComponentDidMount is not called again).
The way I took care of this problem is by caching of getDefaultProps.
Inside EditorCard my default props are:
getDefaultProps: function() {
return {
cachedData: {},
}
},
And is saving the editor instance as this.props.cachedData.editor = editorInstance
This saves props over multiple re-renders.
Is this how getDefaultProps caching was meant to be used? On saving props over multiple re-renders am I breaking some of the core react rules with this hack? Could you suggest a better structure if so?
No, getDefaultProps is what it means to be: getting the default props in case the owner hasn't passed those to you. You could say it's a shorthand for a = this.props.bla || 'hello';.
That being said, if I'm understand your question correctly, I see three ways to solve it.
Cache that in your state instead. Props are passed by the parent and are meant to be read from, inside the child, at least in vanilla React.
Instead of putting that props passing logic in your componentDidMount, why not put it in componentDidUpdate?
ref lets you grab the instance and call its methods directly.