I'm confused, and I have searched a lot for the answer to this (seemingly) basic question.
I'm learning React, and I have a rather common component hierarchy with one top component (lets call it App) which contains a number of subcomponents (a grid, a graph, a table etc).
They all show information regarding one product.
Now when I select a row in the grid, I want to inform the other subcomponents about the change. App therefore passes a callback method
onSelectedProduct={this.onSelectedProduct}
to the grid. This gets called OK. In this App method I set the state:
onSelectedProduct(product) {
this.setState({ product: product });
}
In its render(), App has declared another subcomponent:
<ProductGraph product={this.state.product} />
Since ProductGraph needs to fetch some data asynchronously "to-be-rendered" later, where should I catch this property change??
The old "componentWillReceiveProps" sounded like the proper place, but will be deprecated and should not be used, I understand.
I have also tried shouldComponentUpdate, getDerivedStateFromProps and even to catch it in render, but they all have downsides and eventually lead to horrible code.
Somewhere, somehow, I should be able to detect that props.product !== state.product and issue an async load call for the data...
When the async method I call returns with the data, it will set the state and render itself.
So where is the optimal place to catch changed properties?
I have read a lot about the React Lifecycle but I just can't seem to find this basic information. Am I stupid or maybe blind? Or have I got this completely wrong somehow?
You are looking for componentDidUpdate() the lifecycle method that triggers when a component receives new props or has an updated state.
https://reactjs.org/docs/react-component.html#componentdidupdate
In your ProductGraph component, you would do something like:
componentDidUpdate(prevProps){
if(this.props.product !== prevProps.product){
fetch(`myApi/${this.props.product}`)
.then(res => res.json())
.then(data => this.setState({ data: data })) <-- identify what you need from object
.catch((errors) => {
console.log(errors)
})
}
}
Related
I have an app and when I click on a navigation bar it is causing a completely un-related component to render.
I looked at the code and can not find the connection.
It does not break anything but I find it bizarre.
I am aware of React lifecycles and was wondering if how I can troubleshoot further to see what is causing it to render().
I noticed that componentDidUpdate() is called but I don't know why it is being called by React or what is causing it to update. How can I troubleshoot further?
Maybe relevant code is, but maybe not.
componentDidUpdate(prevProps) {
console.log('DEBUG: componentDidUpdate() called', prevProps.Modal);
// Set the state of the form to the data returned from the server.
// This will allow us to PUT / Update the data as this is a controlled form.
// That is the state holds the form input.
// Typical usage (don't forget to compare props) or infinite loop will ocur.
if (this.props.Modal.data !== prevProps.Modal.data) {
// becasue the form did not update but populates we must call update manually for both URLs
this.url.updateURL(this.props.Modal.data.link);
this.img_url.updateURL(this.props.Modal.data.image);
this.setState(this.props.Modal.data);
}
}
prevProps is always the same for each call. i.e. the props do not change.
It is only mounted once and props and state do not change but it keeps updating!
See image:
If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read
I've googled around and couldn't find anything definitive relating to my question so thought i'd just ask here.
I'm fairly new to react and redux so go easy on me.
Lets say I have this bit of code.
componentDidMount() {
this.props
.getUnitData(this.props.match.params.unit_code)
.then(unit => this.setState({ unit, loading: false, success: true }))
.catch(() => this.setState({ loading: false, success: false }));
}
//redux action
export const getUnitData = unitCode => dispatch =>
api.units.getUnitData(unitCode).then(unit => {
dispatch(unitDataReceived(unit));
return unit;
});
Is it OK to add a return statement to the redux action that returns an object (unit in this case) and setState with it after the Promise returns?
OR should I be using mapStateToProps, and then use componentWillReceiveProps() to setState()?
Obviously the first way seems a lot simpler. But at the same time if you're not planning to use the unit data in other components, it kinda makes the whole purpose of dispatching redundant, which obviously doesn't feel right.
Example of the second way for clarity.
componentDidMount() {
this.props
.getUnitData(this.props.match.params.unit_code)
.then(() => this.setState({ loading: false, success: true }))
.catch(() => this.setState({ loading: false, success: false }));
}
//redux action
export const getUnitData = unitCode => dispatch =>
api.units.getUnitData(unitCode).then(unit => {
dispatch(unitDataReceived(unit));
});
function mapStateToProps(state) {
return {
unit: state.unit
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.unit !== this.state.unit) {
this.setState((unit: nextProps.unit));
}
}
Here is my humble opinion. You are mixing your logic up and when in doubt I always suggest to pick an approach and stick with it across your app (yes there can be exceptions).
What I'm trying to say is that your decision should be based on where the data retrieved is consumed. If such data should be available on other components too then you might as well not set state locally and update the store. Then your components will simply "listen" to changes of that part of the store and update accordingly. Dispatching an action just for the sake of it but then using local state is kind of confusing. If you think about the Flux architecture you are emitting an event that does nothing, but then you are actually updating the local state.
On the other hand if this behavior is very well contained within this component only, then you don't need to dispatch an action at all and just handle everything within your component. I would avoid mixing things up as it could be confusing. Future you could come back to the code maybe check the redux devtools and notice that an action is dispatched, but then the store is never updated, why is that ? Is there an error somewhere ? Did I not pass any data back to the store from the action ? Am I reading the wrong fields from the action dispatched ?
Everything should be as straightforward as it can be, either:
dispatch an action => update the store => component updates based on new store
(there is a little more to it but I am simplifying).
One pattern that I sometime see is the one that has thunks return a Promise. So you normally use a thunk and dispatch an action and so on, but you return a Promise so that you can do some extra logic right after your asynch action has been dispatched. There are some use cases for it but I am not going to get into too many details now. I hope this helped somehow clarify things but if you have any other question let me know in the comments!
Okay, so we are in gray area. But if we are to take another prespective to your requirements, the fetched data is only consumed by a component (kept in state) and not shared. For just this purpose the first approach is fine. You can call the the api code directly in react component, better in componentwillmount. And remove the action, redux only requires to keep data that needs to be shared.
On the other if ever if it need to be shared, the second approach is best. An added bonus, if the data only need to be fetched once and thereafter reused, it can be cached. It can remain in the redux state. Components can spawn and die as the situation presents, data will retain, even though multiple user sessions, judging by the type of data.
I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)
I am reading several articles about how to prevent react-redux from re-rendering the whole page, when only one little thing changes.
One article suggests that instead of wrapping all into one big container (as in figure 1 here) wrapping all into smaller containers (as in figure 2 here). If something changes in Container 2, only Component 2 and Component 3 are getting re-rendered. Component 1 would not re-render.
Figure1
Figure2
I have following questions:
If I wrap everything in smaller containers, I would need "several" global states, for each container one (as indicated with the pseudo-code on the bottom of the figure). Is that common practice?
If it is ok to have "several" global states and I would need in some property from Container1 in Container2, I would need to connect that with two global states. To me that feels like it could get messy very quick. Where does what come from?
When and where would I use the react method shouldComponentUpdate()? Using the Big Container approach how would I differ which Component should be rerendered?! If implemented in the Components, they would not be "dump" anymore, because they need to access the global state in order to decide whether to re-render or not. I would not be able to reuse Components because every Component has its own special case when to rerender and when not. I am not sure where and when to use shouldComponentUpdate()
Please note that I am pretty new to this and might have made wrong assumptions etc. I basically want to know how not to re-render the whole page, when only one thing needs to be updated. The results from asking google differ a lot.
Your second approach is the way to go, though your definition of a global state is a bit misleading.
Basically, you want to have exactly one "global state". This is what is referred to as "store". All components that need to receive parts of the store are connected to it using react-redux' connect function.
Now, connect(...) is actually a HOC which wraps your component and passes only defined parts of the store to it. This way, the component (and its' children) only re-render when its' defined props change.
Don't be afraid to use connect() more often. You just have to be careful what parts of the store you pass to the container and this is exactly where performance can become an issue.
This should answer your first question. The second one is a question of design. Design in terms of how your app and maybe also in terms of how your datasource is structured. As said before, you want to have a minimum of props passed to a component so it doesn't re-render when other parts of the store change.
For the third question, you first have to understand that 'dumb components' can, of course, receive props from their parent components/containers. Dumb just means that they don't get to decide whether a re-render should happen or not. Dumb components are there to present/display data and that's it.
Let's say you have a really simple store:
const store = {
posts: {
all: [],
isFetching: false,
err: {},
}
}
And you connect your container to it like this:
function mapStateToProps(store) {
return {
posts: store.posts.all,
isFetching: store.posts.isFetching,
err: store.posts.err,
};
}
#connect(mapStateToProps)
And this container has three dumb components it can use:
A posts component, which receives all posts and displays them using another dumb child (pseudoCode, you get the point):
function posts = (posts) => {
posts.map((post, id) => (
<otherDumbComponent post={post} key={id} />
));
}
One to display just a spinner while isFetching
One to display the error if there's one.
Now, if only isFetching has changed, only the second component will re-render and that's it. Oh, and shouldComponentUpdate() is something you probably don't want to use, because, well.. there are many good blog posts about it.