React - accessing context in componentDidMount - reactjs

I'm using Context API for the first time and I'm having some difficulties to understand how to update context in lifecycle methods. I had a look at the official doc and this answer which made sense but didn't help me much to find a solution.
I have 2 components for now: one parent and one child.
In the parent, I'm using an axios get() request in componentDidMount() to fetch data.
The child uses context to render elements so it needs the updated context once the request is complete.
My main issue is I can't find out where to update the provider state once the axios response is received. Ideally I'd like to assign the axios response to the provider state but I can't figure out how to access the context from there.
I made it "work" by assigning the axios response to the parent state then by updating the context provider state with it through a handler from my Provider and called this handler in render() under my Consumer since it's the only place I believe I have access to the context:
class ParentComponent extends Component {
render() {
return (
<MyContext.Consumer>
{(context) => {
if(axiosSuccess) {context.updateContextState(parentState);}
return (
<ChildComponent/>
)
}}
</MyContext.Consumer>
);
}
}
However I know it's not the right implementation since updating in render() is an anti-pattern.
What is the best way to update provider state in lifecycle methods?

Using context not differs much from usual state/props/redux usage. Pass handlers (beside values) from context the same way as from parent (by props). Move entire fetch to context (component) or at least pass handler to update state. Look at toggleTheme in docs.

Related

Preserve react component state on unmount

React newbie here: I have noticed that the component state gets cleared on unmount. is there a way to prevent it? is it using redux?
As you say, when a component is unmount you can not access to the state. And thats the way it is because the lifecicle of the component.
What you can do is try to save the state of the component that was instantiated while it is mounted and every time it is updated.
You can use the storage and use simple javascript.
You can have the state in the parent or another ancester instance.
You can have the state in the redux store. Note that your component will receive the vars as props, so it wont be the state properly said.
You can use redux in order to manage the state and the states values through time. I recommend you the redux-devtools-extension and also this article about it.
You have a plethora of options. You can either use a state management tool, like redux, context API and so on, or you can pass-in a callback to your parent component and trigger it on childComponentWillUnmount like this:
ParentComponent.jsx:
childComponentWillUnmount = (data) => {
console.log('my data', data);
}
render(){
return <div>
<Child componentUnmountCallback={this.childComponentWillUnmount()}/>
<div>
}
<div>
Child.jsx
...
componentWillUnmount() {
this.props.childComponentWillUnmount(this.state);
}
...

Use HOC, React Context and componentDidMount API to get uid form Firebase

I try to use a HigherOrderComponent (HOC) to provide the uid of a Firebase user to a component (Comp). The value is succesfully retrieved but I don't succeed in getting it in the wrapped component (Comp).
Below I describe the order of events in the HOC and wrapped Component (Comp):
HOC:
constructor() sets state.uid = null;
render() wraps the Comp in a Provider component with value=this.state.uid.
The Consumer component with prop uid={value} (where value is provided by the Provider component) wraps the Comp
In the componentDidMount() I make an asynchronous call to the Firebase backend (firebase.auth.onAuthStateChanged()).
Comp:
render() fires;
componentDidMount() fires. this.props.uid is null because the asynchronous call didn't finish yet;
HOC:
in componentDidMount() the call to Firebase backend resolves. If not null I do this.setState({uid: value}) where value is the value from asynchronous call. That triggers a new render() of the HOC. A Provider component with the new value this.state.uid is returned.
Comp:
render() runs for 2nd time;
componentDidMount() should run so I can access the this.props.uid and use that value to retrieve data and other stuff but... this.props.uid is null.
I thought componentDidUpdate is supposed to run after a Component's render methode fires but here this doesn't happen. Setting breakpoints or console.log() shows this.
Funny thing is that when in the Comp render() method I display this.props.uid object (via JSON.stringify()) it shows! So the render method can access the updated props value but componentDidUpdate never...
What's wrong here? Am I missing something re: Reacts lifecycle methods?

How to handle render method until redux has returned results?

I am creating a component that fetches some apis to populate result.There are some transformations needed on those api results before rendering. I've used componentWillMount method to set redux state. I need to handle render method until redux those results to props. This takes some time. Since data is not available, render method will fail at that moment. I've tried setting default state but that doesn't seem to work since redux state will be mapped to props with mapStateToProps method. What is the correct and efficient approach to handle such case??
Thanks in advance
Normally you will render a different thing (or you won't render at all) when your data is not present.
{this.props.unreadMessages.length > 0 &&
<h2>
You have {this.props.unreadMessages.length} unread messages.
</h2>
}
or:
if (this.props.unreadMessages.length > 0) {
return <Main unreadMessages={this.props.unreadMessages} />;
} else {
return <Loading />
}
You can also have your default initial state and render based on that when props does not have data. maybe with something like:
<h2>
Hello, {this.props.username || this.state.username}.
</h2>
Also, the recommended lifecycle hook for Ajax call is componentDidMount.
If your data is not ready, you can use conditional rendering. Add additional indicator about your data, Like this
render() {
if (!this.props.data) {
return null;
}
return <YourComponent />;
}
And you'd better to fetch data in componentDidMount lifecycle.
I would check for the props in the render method with a ternary expression as a sort of guard clause. Something like this, for example:
render(){
return (<React.Fragment>
{ this.props.user ? <IntendedComponent ...this.props.user /> : <Loading user={defaultUser}/> }
<React.Fragment/>);
}
If the user prop has not been populated yet, it should be empty and display an alternate component (like the loading component in the example) or the intended component with default, hardcoded values. Once the data is in place, you can pass the data to a component as you see fit.
First of all componentWillMount is not a good method to call apis,
as per react documentation:
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
This method is a good place to set up any subscriptions. If you do
that, don’t forget to unsubscribe in componentWillUnmount().
Here is the link: https://reactjs.org/docs/react-component.html#componentdidmount
Here is what you can do till data is loaded:
get one flag into redux state called dataLoaded:false
Once the data is loaded you performed an operations you can easily change it.
And in render()
if(this.props.dataLoaded){
<loadingComponent></loadingComponent>
} else {
//operation is performed
}
Since you're using Redux, you can set the default Redux state in Reducers using ES6 default parameters. Something like this:
const apiStatus = (state='fetching', action) => {
// handle dispatch action
}
And in React, you can use Conditional Rendering to render a loading icon, for instance, while state is fetching. Then when state is finished, take the result and render the real elements.
To work with Async in Redux, see this link:
Async Action

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

React one time API call for multiple routes design

I am new to React and trying to implement an application. Basically
my application have several routes. Each
route is backed by the same set of back end data +some api calls
specific to route taking back end data attributes as api params.
So I wrote a Higher order component to call the
API to retrieve the back end data,store the state in redux store and
pass it as props to the wrapped component which works fine.
const withData = (WrappedComponent) => {
class DataWrapper extends Component {
constructor() {
this.state = {
c: {},
d: []
};
}
componentDidMount(){
this.props.fetchData();
}
componentWillReceiveProps(nextProps){
this.setState({
c: nextProps.data.CSubset,
d: nextProps.data.DSubset
});
}
render() {
return <WrappedComponent {...this.props} {...this.state}/>;
}
const mapStateToProps=(state)=>({
data:state.appData
});
return connect(mapStateToProps,{fetchData})(DataWrapper);
}
export default withData;
class AppComponent extends Component{
componentDidMount(){
this.props.fetchComponentSpecificData(c.dataAttribute);
}
render(){
return <div />;
}
}
export default connect(null,{fetchComponentSpecificData}(withData(AppComponent);
But the issue is API gets called for all routes.It should be one
time per full application flow.
The component specific data fetch happens before the common back end data is available causing the component specific API call to fail.
User can type in the URL and launch
into any route within application and the API has to be called only
once and the HOC has to systematically route to the correct route
based on data.
Please advise on the design approach
I would say that the most "Reacty" way of doing this would be to implement the global API calls in the componentWillMount method of the top-level component (say, AppComponent). Right now, you have put it in a component that will be mounted for each subcomponent of your app, and it might be tricky to prevent the API call from being fired every time it mounts.
Using a HOC to provide those data to other components of your app is not a bad idea, but it makes your app a bit more implicit, and I don't think you're gaining a lot : you're adding a component with implicit behaviour instead of just adding a line in mapStateToProps.
Firing page-specific API calls after the global API call has succeeded is quite tricky. You're going to need a way to monitor the fact that the global API call has resolved, and in React/Redux way of thinking, this means dispatching an action. To dispatch actions asynchronously, you're going to need a Redux middleware such as redux-thunk or redux-saga (I believe redux-thunk is a lot easier for beginners). Now, if you have a way to know whether or not the global API call is a success, you can wait for a specific action to be dispatched (say, GLOBAL_API_CALL_SUCCESS). Now, this is the part where I'm doing myself a bit of publicity : I've written redux-waitfor-middleware that allows you to wait for a specific action to be dispatched. If you used it, it might look like this :
export async function fetchComponentSpecificData() {
await waitForMiddleware.waitFor([GLOBAL_API_CALL_SUCCESS]);
// perform actual API call for this specific component here
}
I'm not saying that waitFor middleware is the best tool to achieve this, but it's probably the easiest to understand if you're beginning!

Resources