I decided to implement a popup modal which would display error messages in my React application.
I adapted some CSS from Kumar Mathalier's jsfiddle page.
And I adapted some code for how to create a modal from Eden Ella's code on his blog.
I really dug the look of Kumar's modal, and Eden helped direct me to the idea of hiding or showing a component based on a local state. So check those pages for details on how they did those things.
However, Eden's page triggered the modal by clicking a button and I needed my modal to pop up whenever I received an error in my fetch statements. So here's what I did to make this modal appear on it's own.
It all starts when you get an error
When an error occurs in my Rails backend the code sends an error object {message: "some error message"}. So in my fetch statements I need to check to see if I get that object with an error message. When that happens I call an error reducer (instead of whatever reducer I'd call if there was no errors)
fetch(url, {
method: "POST",
headers: {Accept: "application/json", "Content-Type": "application/json"},
body: JSON.stringify({sentence: body})
}).then(r => r.json())
.then(sentence => {
if (sentence.message) {
dispatch({type: "DISPLAY_ERROR", payload: sentence})
} else {
dispatch({type: "ADD_SENTENCE", payload: sentence})
}
})
Redux Store and The Reducer
My reducer does two things. It either sets an error message into my state or it clears it. My state, for purposes of this blog, looks like this:
state = {
other_aspects: {},
error: "" // error message
}
The Modal
The Hulk's trick is that he's always angry and the Error Modal's trick is that it's always there. But it only displays when the props.show changes to true. (Hopefully you looked at the code I linked above that Eden wrote)
I opted to store that Boolean in a local state instead of in props. So this:
class ErrorModal extends React.Component {
state = {
show: false
};
}
In Eden's code he change the value of show with a button click. I want to change that value whenever an error is found (which can happen in any of my fetch calls).
As I showed when my fetch finds an error it sets error in the Redux Store's state. So I made sure to map that error message to props in my Modal Component:
function mapStateToProps(state) {
return {error: state.error}
}
export default connect(mapStateToProps)(ErrorModal)
Now whenever an error shows up this component will render again. When that happens I cam change the value of my local state. So I used componentDidUpdate()
componentDidUpdate(prevProps) {
if (this.props.error && !prevProps.error) {
this.setState({
show: true
});
}
This if statement is very important. This says if there is an error message in my props AND there wasn't one before set state.show to true. If I didn't have that if statement I could get caught in a recursive loop. Let's say instead of true, I wrote it like this:
this.setState({
show: !this.state.show
});
This has the same result as my code. Before it was hard coded, and this is a little more elegant. It toggles it from false to true. But now think about what happens. The error message changes props which would trigger a render, and that would change the state which would trigger ANOTHER render which would change state again which would trigger ANOTHER render which would trigger ANOTHER render... and you get it.
So that if statement protects me no matter how I change that setState command.
Clear the error message
Ok. The modal pops up. The user is shamed about his mistake. Excellent. They click the button to close the modal. Now we need to hide this modal again. This works almost exactly like Eden's code. His onClick function was a prop and mine is local function, but the same thought process is here:
onClick = (e) => {
this.setState({
show: false
});
};
The Modal is no longer visible again (it's back to being mild mannered Bruce Banner if you follow my earlier subtle analogy)
I also needed to remove the error message from the Redux store's state. Changing the state causes the modal to render again but it doesn't make the modal reappear because of that if statement in componentDidUpdate (the prevProps.error is not blank). But when I get a new error prevProps.error will still NOT be blank so the modal won't appear! So I created an action that called the case in my reducer that cleared the message in my store. I called it clearError.
Now I just need to call that after setting state.show to false again.
Here is the final code:
import React from "react";
import {connect} from 'react-redux'
import clearError from '../actions/clearError'
class ErrorModal extends React.Component {
state = {
show: false
};
onClick = (e) => {
this.setState({
show: false
});
this.props.clearError()
};
componentDidUpdate(prevProps) {
if (this.props.error && !prevProps.error) {
this.setState({
show: true
});
}
}
render() {
if(!this.state.show){
return null;
}
return <div className="modal" id="modal">
<div>
<a href="#close" title="Close" className="close" onClick={this.onClick}>X</a>
<h2>Error Message</h2>
<p>{this.props.error}</p>
</div>
</div>
}
}
function mapStateToProps(state) {
return {error: state.error}
}
export default connect(mapStateToProps, {clearError})(ErrorModal)
Conclusion
Props and State changes cause your page to render so managing those values can make it very easy to know when a page will hit their lifecycle methods like componentDidUpdate.
Top comments (0)