Last time, I played around with CSS modules and managed to make them work. Today I think is time to finally deal with error messages in the console, as well as some bugfixing. The one bug I’m aiming at is the fact, that you cannot really use twice the button, which opens the popup, because I’ve developed it with no state whatsoever. If you don’t know about state management, it doesn’t exist, right? I’ll be messing around with the application’s components to make everything better.
What’s the problem again?
I might have been too cryptic :). If you haven’t read these series from the beginning and have no idea what is happening here’s a summary: I’ve developed a Login form as a way to learn React. It took me some time, but I’ve written down all the silly errors I made during the development.
So this Login form works like that: You click a button, a popup with the form shows up. Then you can enter username and password and click another button, to log in. Both of these buttons are named “Log In” which is a different level of User Experience, I’m sure. Anyhow, this is not important. What’s important is that once you decide to actually log in, your username and password are displayed on the screen, for the whole world to see them. This, I argue, is a very important feature, because it means I see what’s happening very fast and I don’t need to check elsewhere if the Login form works. Later, I’ll fix it, when I write some backend code.
Once your password and username are on the screen, you can click the button for the popup as long as you like, but nothing actually happens. This is the bug I plan on fixing today. When I’m finished, I would have a form that can be reused, or a complete mess of a code, which can physically destroy my computer. Basically success, or fireworks!
Searching for the root cause
(Sometimes we need to use complex words…)
Back to work: I’m going to look into the generated html and see what happens when I click the faulty button. I’m back! Here’s the structure of the “App” component:
<div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <button className="App-link" onClick={this.openLoginForm.bind(this)} > Log In </button> <div id="logIn"> {loginForm} </div> </header> </div>
You can see where the popup goes: in the div with id “logIn”. And it is rendered when clicking the “Log In” button. Look:
openLoginForm () { console.log("bazinga!"); const openLoginState = {isOpen: true}; this.setState(openLoginState); };
That sets the state of the popup, and the next piece of code renders it:
let loginForm; if (this.state["isOpen"]) { loginForm = <LoginForm onClose={this.onLoginFormClose.bind(this)}/>; }
Now, what does the other “Log In” button do? The one in the LoginForm component? It gets the entered username and password and renders them in a barbaric way using ReactDOM.render, rewriting everything in the “logIn” div of the App component:
onSubmit() { console.log("Double bazinga!"); ReactDOM.render( <div> <span>username:{this.state["username"]}</span> <span>password:{document.getElementById("password").value}</span> </div>, document.getElementById("logIn"), ); }
Ah, I love it when I look at the code and say “Who is this barbarian that wrote this code”, knowing it was me :).
How can I prevent the popup from being replaced by the results div?
Now, let me think about it. Previously we moved some state up to the App component and changed it using a callback in the LoginForm component. But I don’t think the “username” and “password” fields of the state are actually going to fit in the App component state. It seems pretty fine for me to keep them in the LoginForm. And if I think of it, they don’t really control whether the popup is opened or not.
Also, why am I making this LoginForm component actually commit seppuku? Its click handler gets triggered, and then the entire component is wiped! Not good! I’ll stop that.
If I look at it again, the markup which shows the credentials actually feels like a completely different component. Perhaps I can move it to the App component, but I don’t really want an entire new class for it, after all it is just a div. Right! Let’s start doing things.
Moving the credentials div to the App component.
Now this is a very complex and exhausting task! First, I will highlight the div and all its children in my editor. Then I’ll cut the highlighted text. Third, I will find the App#render function, and paste whatever I cut in the beginning of it.
If you’ve read the last paragraph, I would like to apologize for your lost time. I just felt it’s time to fool around a bit :D.
Let’s be more serious now, though. I’ll put an if statement like the one that renders the LoginForm component. See – they are the same:
if (this.state["isOpen"]) { loginForm = <LoginForm onClose={this.onLoginFormClose.bind(this)}/>; } if (this.state["isLoggedIn"]) { credentials = <div> <span>username:{this.state["username"]}</span> <span>password:{document.getElementById("password").value}</span> </div>; }
Uh-oh, I see trouble! I need to know the username and the password in the App component. And they are in the state of the LoginForm… The password is even in the DOM! Sacrebleu! I guess I’m having the username and password in the App state. And I’ll fix this ugliness with the DOM.
Managing LoginForm related state
Next, I think I have to make another event handler, like the one for closing the popup. It will update the state of the popup (if it is open or closed) and it will save the newly entered credentials in the App state. And I’ll pass it as another property to the LoginForm:
submitLoginForm (username, password) { const appState = { isOpen: false, username: username, password: password }; this.setState(appState); }; if (this.state["isOpen"]) { loginForm = <LoginForm onClose={this.onLoginFormClose.bind(this)} onSubmit={this.submitLoginForm.bind(this)}/>; }
There! Let’s try it out! Oh my, I forgot to define the “credentials” variable before using it. Let’s try again. I only get “Double bazinga!” message in the console. Because the onSubmit method of the LoginForm gets executed. Instead of the new event handler. I’ll switch that. The LoginButton’s onClick handler has to be changed to this.props.onSubmit
There! The popup works again. But the credentials don’t show at all. Instead I have a warning, that says the credentials variable is never used. That’s the one holding the HTML that shows the username and password. Why isn’t it used? Oh, because I don’t set the “isLoggedIn” state property anywhere… I’ll set it to true in the submitLoginForm method.
Good! The warning is gone! But the credentials still don’t show. I’ll be debugging this event handler now!
Debugging event handlers
Wait! I don’t even hit this function! Does the old onSubmit method still get triggered? Nope, It doesn’t. I think something is fishy here. My breakpoints aren’t being hit anywhere. Let’s try with console.logs.
I’ll print a message from the submitLoginForm method showing us the password and the username, as well as the appState object.
console.log(username); console.log(password); console.log(appState);
Cool! The appState object is fine. We have a problem with the username and password, though. The password is undefined, but the username is even stranger! It’s some kind of Class object. I’ll read a bit about that. It might be the LoginForm itself. I guess React tries to execute the call function on the event handler and passes this as the first argument. This doesn’t help me getting the username and the password arguments though.
I’ll use the local submit handler and call the App component submit handler in it. Let’s botch things up:
onSubmit() { console.log("Double bazinga!"); this.props.onSubmit(this.state.username, this.state.password); }
There you go! The method is called with all the right parameters!
But why is the credentials div still missing?
Oh, I don’t really render it. Whoops :) I’ll put it next to the loginForm. Here’s the App’s markup:
return <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <button className="App-link" onClick={this.openLoginForm.bind(this)} > Log In </button> <div id="logIn"> {loginForm} {credentials} </div> </header> </div>
Great! Now I can open the Login form again! But when I do that, the credentials stay underneath it. I want them gone! Let’s update the “isLoggedIn” state property on the handler that opens the form:
openLoginForm () { console.log("bazinga!"); const openLoginState = { isOpen: true, isLoggedIn: false }; this.setState(openLoginState); };
And done! Let’s look at what warning messages are left in the console:
webpackHotDevClient.js:120 ./src/App.js Line 3: 'ReactDOM' is defined but never used no-unused-vars Line 17: Do not mutate state directly. Use setState() react/no-direct-mutation-state
Alright, it’s time for some clean-up
Cleaning up warning messages
First, let’s get rid of the ReactDOM variable. It’s this import:
import ReactDOM from 'react-dom';
Next, the state! I’ve already dealt with warnings like this. It is pretty straightforward. This is at line 16. That’s the change event handler of the LoginForm. There, I actually do that:
this.state[event.target.name] = event.target.value;
I do that, because there are more than one property in the state, and I’m not updating all of them. I think I need to get the old state, change the property in the new object and set it through the setState function. However, I have read something about immutability of the state in the React tutorial. That’s why I’ll get the state, copy it and change the copy to create the new state. Then set the new state to the component:
change (event) { const newState = Object.assign({}, this.state); newState[event.target.name] = event.target.value; this.setState(newState); // this.state[event.target.name] = event.target.value; }
I’ve commented out the old function implementation just in case. Let’s look at the console.
Yeah! That did it! I got rid of the warning. But does it work? I am amazed! This worked! Awesome!
What I’ve learned
Today I learned a way to execute callbacks with parameters on an event. It looks like I’ve used a sledgehammer for a tiny needle, but it works. Further, I found out how to set the state of a component properly: with immutable objects and all the good stuff.
What’s next
Since things are getting easier already, perhaps it is time to make the Login form navigate away from the homepage next time. I’ll be looking into working with the React Router. Stay tuned and
Happy coding!
Why are you still here?
I have created a Bitbucket repository for these series. You can find it here: https://bitbucket.org/geneshki/entering21stcenturywithreact/src/master/
I intend to have a branch for each article from now on. Feel free to use this code if you wish.
Do you want more? If you are from the future, perhaps the next chapter is here: React Router and Creating a User Space.
I also have slightly different articles. Here I described, how I built myself a table.
Have fun!