I have a React container component which on page load dispatches an action (using the componentDidMount lifecycle method). The action calls an API, a successful API call results in the data being added to my Redux store via a reducer.
I have some other API's which require data from the response of the initial API call. I have these in the componentWillReceiveProps lifecycle method and I'm comparing this.props against nextProps to work out whether I have the data ready to call the API's.
90% of the time this code works and the API calls inside of componentWillReceiveProps are successfully called, but the other 10% of the API's don't get called because the code inside of the if statement which compares this.props to nextProps doesn't execute.
From testing I think I've found the issue... If the initial API call returns fast enough, the very first time componentWillReceiveProps is called this.props is already populated with data from the API call rather than returning my initial state, at which point the if statement doesn't execute.
What is the best way of bulletproofing this?
<Route
path={'Content/:id'}
component={DrillPage}
/>
class DrillPage extends Component {
componentDidMount() {
this.props.actions.getLoggedInUser();
}
componentWillReceiveProps(nextProps) {
console.log(this.props);
console.log(nextProps);
// Don't fire if user isn't logged in
if (this.props.loggedInUser.id !== nextProps.loggedInUser.id) {
this.props.actions.isFavourite(this.props.content.id, this.props.content.type);
this.props.actions.doIFollow([this.props.content.author.id]);
}
}
}
function mapStateToProps(state, ownProps) {
return {
contentId: ownProps.params.id,
loggedInUser: state.loggedInUser,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, contentActions, userActions), dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DrillPage);
Maybe you can try a solution without using the React lifecycle and handle your logic in your sagas since, as I understand you're using componentWillReceiveProps to fire other API calls only if you have a userId
You could make a custom middleware for Redux that calls the API on an action dispatch from your component. Then it would directly modify the state and have no dependence on the component other than the initial dispatch.
Related
I'm working with Material-UI components on the project and is using AutoComplete component in my app.
In the example from Material-UI team, I came across an interesting example of the AutoComplete Ajax data: https://material-ui.com/components/autocomplete/#asynchronous-requests
They are using React Hooks to fetch the data from the server:
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
const response = await fetch('https://country.register.gov.uk/records.json?page-size=5000');
await sleep(1e3); // For demo purposes.
const countries = await response.json();
if (active) {
setOptions(Object.keys(countries).map((key) => countries[key].item[0]));
}
})();
return () => {
active = false;
};
}, [loading]);
Why do we use active variable here? Why we return a function that changes this variable to false? It is always true in the if-statement.
Thanks in advance for the answer
The function returned from useEffect is a cleanup function. It is called when the component un-mounts - and is usually used to unsubscribe to events, cancel pending promises etc that were used in the useEffect.
The active variable is used make sure that you aren't updating the state on something that doesn't exist anymore. It's somewhat like the isMounted anti-pattern that existed in class components.
When you try to update state on an un-mounted component, React will throw a warning -
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.
Having the active variable prevents that in the following way:
Your component loads
The useEffect calls an async fetch - this will take time
Now say, before the server from the response is returned, you navigate away from the page (or perform some other action that un-mounts the component)
That will cause the component to unmount and the cleanup function to be called:
return () => {
active = false;
};
active is now set to false
Finally, we get our response from the server. And now, it'll encounter the false active value, and not update the state.
// active === false,
// this will skip `setOptions`
if (active) {
setOptions(...);
}
This is a pattern which is used to avoid two situations:
updating the state if component containing this hook has already unmounted before HTTP request could complete.
avoid race conditions
Function returned by callback function of useEffect hook is used to perform the clean up, like componentWillUnmount in class based components. This cleanup function runs when the component unmounts or before running the effect next time.
So if component is unmounted when the HTTP request was in progress, cleanup function of useEffect hook will run and will set active to false. After that, whenever the result of HTTP request returns, state won't be updated because active will be false.
See Effects with cleanup section of react docs to understand the cleanup function of useEffect hook and see Race Conditions to better understand the problem related to race conditions that is solved here by using active variable.
I'm trying to understand how to connect redux-saga to NextJS and am following the sample code they provide -- https://github.com/zeit/next.js. I understand that one can load data from within getInitialProps but I don't understand what is going on with the call to Component.getInitialProps:
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
const {Component, pageProps, store} = this.props
return (
<Container>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</Container>
)
}
}
export default withRedux(createStore)(withReduxSaga({async: true})(MyApp))
Is this allowing all async loads within the getIntialProps of pages to load? That is, in index.js we have the code
class Index extends React.Component {
static async getInitialProps (props) {
const { store, isServer } = props.ctx
store.dispatch(tickClock(isServer))
if (!store.getState().placeholderData) {
store.dispatch(loadData())
}
return { isServer }
}
componentDidMount () {
this.props.dispatch(startClock())
}
render () {
return <Page title='Index Page' linkTo='/other' NavigateTo='Other Page' />
}
}
Will this getInitialProps wait to return until all the data is loaded? How will it know when it's loaded?
Any help much appreciated!
Since _app.js is a HoC your Component inside the _app.js is basically the page you want to load from the pages folder (if you compose further you can propagate another HoC and afterwards load your page, depends on your application, but then in your compose HoC you have to implement getInitialProps again and then execute the getInitialProps of your final page.js). Each page can have an own getInitialProps (eg. on your contact page you want to load your companies address and only there in your entire application). _app.js executes Component.getInitProps (if set). If the Component's method returns a non-empty object, it becomes your pageProps which you finally provide to the Component inside the render method.
Your page.js now has a getInitProps method implemented, which dispatches a saga task and returns isServer ... that given, only your store is hydrated by dispatching the action - the getInitProps does not have to wait for anything and returns a boolean. After the store hydration your component will be updated, since your props in store are loaded/updated.
However I face the same issue at the moment:
I dispatch saga Tasks in my getInitProps, but since the actions are async I receive the props after ComponentDidMount.
EDIT:
Link to read
TL:DR;
Saga tasks are threads or if you want a kind of event listeners.
Everytime you dispatch a saga action (depending on your sagas) a listener is spinned up
In our case if we fire an action in our getInitialProps we dispatch actions to the store using saga tasks, however the store.dispatch method is not async => which leads to getInitialProps is not blocked by the action and instantly returns props although the fired tasks aren't finished
Victor's solution as well as the one from next-redux-saga package is 1. to dispatch the 'END' action which stops the current root task and 2. apply the toPromise() method which gives you the ability to asynchronously wait for the sagas to end. => You kind of block the getInitialProps and force the method to wait for the sagas to end before the method return;
Important: when you stop your saga threads it is okay and MUST/SHOULD be done on the server, but your application needs the sagas on the client side to properly handle store side effects thus you MUST rerun the sagas if !isServer.
UPDATE:
It doesnt work. You can only stop the sagas on the server because you only need them for the initial execution, the sagas on the server are pretty useless afterwards. However on the client side you cannot stop the sagas.
Here is my store.js... you can execute initial sagas and the server will wait for them to finish in getInitialProps, since the toPromise() makes the stopping async.
On the client side you have to work your way through lifecycles etc... client side store hydration does not work as I expected it to work. Maybe it is better since otherwise you would block React from rendering, which goes against React in general.
store.runSagas = () => {
if (!store.currentSagas) {
store.currentSagas = sagaMiddleware.run(rootSaga);
}
};
store.stopSagas = async () => {
if (store.currentSagas) {
store.dispatch(END);
await store.currentSagas.toPromise();
}
};
store.execTasks = isServer => async (tasks = []) => {
tasks.forEach(store.dispatch);
if (isServer) {
await store.stopSagas();
}
};
I have the following function:
onSelectDepartment = (evt) => {
const department = evt.target.value;
const course = null;
this.setState({ department, course });
this.props.onChange({ name: 'department', value: department });
this.props.onChange({ name: 'course', value: course });
if (department) this.fetch(department);
};
The question is, after the setState function get called, the render function on the component will be executed immediately or after function call is finished?
render function on the component will be executed immediately or after
function call is finished?
No one can take the guarantee of when that render function will get called, because setState is async, when we call setState means we ask react to update the ui with new state values (request to call the render method), but exactly at what time that will happen, we never know.
Have a look what react doc says about setState:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
Check this answer for more details about async behavior of setState: Why calling setState method doesn't mutate the state immediately?
If you are looking execute some piece of code only once the setState is completed you can use below format.
this.setState({
flag: true
}, ()=> {
// any code you want to execute only after the newState has taken effect.
})
This is the way to make sure your desired piece of code only runs on the new state.
I am trying to perform an asynchronous call to an API in the componentWillMount method. Indeed I would like the render method to executed after the componentWillMount method as I need to pass props to the component in my render method.
Here is my code :
class TennisSearchResultsContainer extends React.Component {
componentWillMount () {
// TODO: Build markers for the map
// TODO: Check courtsResults object and database for tennis court
this.courtsMarkers = this.props.courtsResults.map((court) => {
return new google.maps.Marker({
position: new google.maps.LatLng(JSON.parse(court.LOC).coordinates[1], JSON.parse(court.LOC).coordinates[0]),
title: court.NAME,
animation: google.maps.Animation.DROP
});
});
}
render () {
return <TennisSearchResults criterias={this.props.criterias} courtsMarkers={this.courtsMarkers} />;
}
}
I don't understand then why my render method seems to do not wait for the asynchronous call to finish and pass undefined props to my child component...
Am I right? And what should I do to fix that? What is the way to handle this?
You might need to understand javascript async behavior better. Async means "don't wait". That the task will happen in the background and other code will continue to execute. A good way to manage this is to set state on your component. For example, when you enter componentDidMount set a loading state to true. Then when your async function completes, set that state to false. In your render function you can then either display a "loading..." message or the data.
Here is some code that shows a simplified example of fetching data async and how you could handle that in React. Open the developer tools in your browser and look at the console output to understand the React lifecycle better.
EDIT: Code has been updated to use the new React Lifecycle recommendations as of April 2018. In summary, I replaced componentWillMount with the safer componentDidMount.
It might seem inefficient to update the state after the component has already mounted, as 'componentDIDmount' correctly implies. However, per the official React documentation on componentDidMount:
"If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
"Calling setState() in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state."
Here's the complete example code:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log('This happens 1st.');
this.state = {
loading: 'initial',
data: ''
};
}
loadData() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('This happens 6th (after 3 seconds).');
resolve('This is my data.');
}, 3000);
});
console.log('This happens 4th.');
return promise;
}
componentDidMount() {
console.log('This happens 3rd.');
this.setState({ loading: 'true' });
this.loadData()
.then((data) => {
console.log('This happens 7th.');
this.setState({
data: data,
loading: 'false'
});
});
}
render() {
if (this.state.loading === 'initial') {
console.log('This happens 2nd - after the class is constructed. You will not see this element because React is still computing changes to the DOM.');
return <h2>Intializing...</h2>;
}
if (this.state.loading === 'true') {
console.log('This happens 5th - when waiting for data.');
return <h2>Loading...</h2>;
}
console.log('This happens 8th - after I get data.');
return (
<div>
<p>Got some data!</p>
<p>{this.state.data}</p>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementsByClassName('root')[0]
);
And here is the working example on CodePen.
Finally, I think this image of the modern React lifecycle created by React maintainer Dan Abramov is helpful in visualizing what happens and when.
NOTE that as of of React 16.4, this lifecycle diagram has a small inaccuracy: getDerivedStateFromProps is now also called after setState as well as forceUpdate. See this article from the official React blog about the Bugfix for getDerivedStateFromProps
This interactive version of the React lifecycle diagram created by Wojciech Maj allows you to select React version >16.04 with the latest behavior (still accurate as of React 16.8.6, March 27, 2019). Make sure you check the "Show less common lifecycles" option.
The above answer is probably overkill for what you are trying to do. All you need to do is make compoentDidMount an async function. Then you can use the await keyword on a function call that returns a promise.
class MyComponent extends React.Component {
async componentWillMount () {
await myAsyncCall();
}
render () {
}
}
In the react docs it recommends making initial network requests in the componentDidMount method:
componentDidMount() is invoked immediately after a component is mounted. 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. Setting state in this method will trigger a re-rendering.
If componentWillMount is called before rendering the component, isn't it better to make the request and set the state here? If I do so in componentDidMount, the component is rendered, the request is made, the state is changed, then the component is re-rendered. Why isn't it better to make the request before anything is rendered?
You should do requests in componentDidMount.
If componentWillMount is called before rendering the component, isn't it better to make the request and set the state here?
No because the request won’t finish by the time the component is rendered anyway.
If I do so in componentDidMount, the component is rendered, the request is made, the state is changed, then the component is re-rendered. Why isn't it better to make the request before anything is rendered?
Because any network request is asynchronous. You can't avoid a second render anyway unless you cached the data (and in this case you wouldn't need to fire the request at all). You can’t avoid a second render by firing it earlier. It won’t help.
In future versions of React we expect that componentWillMount will fire more than once in some cases, so you should use componentDidMount for network requests.
You should use componentDidMount.
Why isn't it better to make the request before anything is rendered?
Because:
Your request will almost certainly not finish before the component is rendered (unless rendering large amounts of markup, or you are on a zero latency quantum entanglement connection), and the component will ultimately need to re-render again, most of the time
componentWillMount is also called during server-side rendering (if applicable)
However, if you were to ask, isn't it better to initiate a request in componentWillMount (without actually handling it in place), I would definitely say yes (ES6), and I do this myself to occasionally cut a few milliseconds from load times:
componentWillMount() {
// if window && window.XMLHttpRequest
if (!this.requestPromise) {
this.requestPromise = new Promise(resolve => {
// ... perform request here, then call resolve() when done.
});
}
}
componentDidMount() {
this.requestPromise.then(data => ...);
}
This will start preloading your request during componentWillMount, but the request is only handled in componentDidMount, whether it is already finished by then or still in progress.
You should make the request in componentDidMount as no side-effects requests should be made in componentWillMount. It's fine to setState in componentWillMount, if you setState in componentDidMount you will immediately trigger a second re-render.
You will read that it's an anti-pattern (UGHHH) and some linters have it prohibited (eslint-react-plugin), but I wouldn't pay huge attention to that as sometimes it's the only way to interact with the DOM. You can set your default state either in willMount or as a method property ( state = { } ), if you're using the associated babel stage
As you say the component will be rendered already once, but this is good because you can display some kind of Loader or any other form of information that a resource is loading.
class MyComp extends Component {
// What I do with stage 0
state = { mystate: 1 }
// What you might want to do if you're not
// on the experimental stage, no need to do
// the whole constructor boilerplate
componentWillMount() {
this.setState({ mystate: 1 });
}
componentDidMount() {
dispatch(yourAction());
// It's fine to setState here if you need to access
// the rendered DOM, or alternatively you can use the ref
// functions
}
render() {
if (!this.props.myCollection) return <Loader />
return (
<div> // your data are loaded </div>
)
}
}
The real reason to avoid fetches in lifecycle hooks before the render method is because the React community is planning to make the render method calls asynchronous.
Check the response from gaeron here : https://github.com/reactjs/reactjs.org/issues/302
Now this means placing an asynchronous action like fetch(or any asynchronous operation for that matter) in any of the lifecycle methods before render, will interfere with the rendering process.
So unlike the synchronous execution that you would imagine today :
1. constructor << imagine firing fetch() here >> => 2. getDerivedStateFromProps << fetch completes, callback added to callback queue by event loop >> => 3. render => 4. componentDidMount => 5. callbacks executed in the order that they were added so fetch callback runs now.
it would instead be like this :
1. constructor << imagine firing fetch() here >> => 2.
getDerivedStateFromProps << in the meantime, fetch completes and callback gets queued >> << imagine firing async render() here. its callback too gets queued >> => callbacks get executed in the order that they were added 3. so fetch callback runs first => 4. render callback runs => 5. componentDidMount
This interference may result in state changes getting reverted because render may apply an earlier state that overrides the changes made by fetch.
Another other reason being that fact that it is the componentDidMount lifecycle that guarantees the presence of the corresponding component on DOM and if fetch tries to manipulate the DOM even before its available or is updated, it could result in faulty application display.