Back to the Code!
Now that you have an idea of what state is in play, we can get back to the code in main.tsx:
baseComponent.state.showHidePleaseWait(true);
Once the UI is built, the next task that must be accomplished is to call the server
and get a list of mailboxes available for the account and a list of contacts the user has
created. Any time we call the server, we’re going to display a “please wait” popup, so the
user knows something is happening. This will also serve the purpose of blocking the UI
for a moment so that the user can’t go and do something that causes problems while the
server works. The showHidePleaseWait() method of the state object, the first of the state
mutator methods we’ve encountered, does this for us:
showHidePleaseWait : function(inVisible: boolean): void {
this.setState({ pleaseWaitVisible : inVisible });
}.bind(inParentComponent)
You must remember that with React, you don’t directly tell components to do
things. Instead, you mutate state in some way, using the setState() method on the
component that holds the state, which causes React to repaint the pertinent parts of the
UI as needed. In this case, to show the please wait popup, all we need to do is update
the pleaseWaitVisible state attribute, setting it to true. React will then redraw the UI,
and what will happen, something you’ll see later, is that the popup element will now
be set to visible. This will make sense when we look at the MainLayout.tsx file, but just
keep in mind for now that pleaseWaitVisible is set to true when we want the please
wait popup to be shown and false when we want it hidden and that React will see that
change in state and redraw the screen as needed. That’s the key thing right now.
The little problem I alluded to earlier is that since we are limited to calling the
setState() method on the component that contains the state object, any code that tries
to call it must execute within the context of that component. When we define a separate
object for state as I did in order to break it out into its own source file, the this reference
in the methods inside state won’t be what we need. In other words, any method inside
the state object won’t have access to setState() because its execution context, at least in
some instances, won’t be the BaseLayout component.
That’s where the bind() statements come in, and you’ll see these on every function
in the state object. When the state object is constructed via the call to createState(), a
reference to the BaseLayout instance was passed in. That’s what we bind all the mutator
Chapter 9 Delivering the gooDs: MailBag, the Client
235
functions to. That way, they will always have access to setState() as we need. Note that
if any of them needs to touch the state object itself, which they obviously would need to
in at least some cases, they can do so by accessing this.state, since components always
expose their state via that property.
With the please wait popup showing, we can now call the server:
async function getMailboxes() {
const imapWorker: IMAP.Worker = new IMAP.Worker();
const mailboxes: IMAP.IMailbox[] = await imapWorker.listMailboxes();
mailboxes.forEach((inMailbox) => {
baseComponent.state.addMailboxToList(inMailbox);
});
}
We know that getting a list of mailboxes is an IMAP operation from our look at
the server code, and since the IMAP Worker class on the client seeks to mimic that
API that is exposed by the server to the IMAP Worker class there, it makes sense that
we’d be calling IMAP.Worker.listMailboxes() here too. And the code looks almost
identical to the endpoint handler function code on the server as a result. We’ll look at
the client-side IMAP close a bit later, but I think you’ll find it rather trivial. The bottom
line, though, is that we get back an array of mailboxes, and we then iterate them and
call the addMailboxToList() method on the state object (which we can do because we
have a reference to the BaseLayout component via the baseComponent variable). That
will update the mailboxes array in state, causing React to render the screen to show the
mailboxes on the left.
And addMailboxToList() method is the next state mutator we’ve hit:
addMailboxToList : function(inMailbox: IMAP.IMailbox): void {
const cl: IMAP.IMailbox[] = this.state.mailboxes.slice(0);
cl.push(inMailbox);
this.setState({ mailboxes : cl });
}.bind(inParentComponent)
First, you have always to remember that when you call setState(), you should never
pass references to objects in state. That may sound weird, but it’s easy to understand
when dealing with arrays, as we are here. Your first inclination would be to directly push
Chapter 9 Delivering the gooDs: MailBag, the Client
236
inMailbox into state.mailboxes and then try to call this.setState({this.state.
mailboxes}). Everyone tries that at first because it seems reasonable! However, it won’t
work because what you pass into setState() replaces what’s in state at the time, and
trying to do that with what’s already there… well, let’s just say React won’t like you very
much!
Instead, we make a copy of the array via slice(0), then push the new mailbox into
that copy, and finally pass that copy to setState(). Now, everything works as expected.
Note that you only have to do this sort of copying/updating/setting when dealing with
objects and collections.
If you’re paying attention so far, you will have noticed that we haven’t actually called
the server to get the list of mailboxes yet, we’ve only defined a function to do so. That’s
because the function that calls imapWorker.listMailboxes() must be marked async
since we’re await’ing the response. getMailboxes() is marked async, so now we need to
call it:
getMailboxes().then(function() {
async function getContacts() {
const contactsWorker: Contacts.Worker = new Contacts.Worker();
const contacts: Contacts.IContact[] = await contactsWorker.listContacts();
contacts.forEach((inContact) => {
baseComponent.state.addContactToList(inContact);
});
}
getContacts().then(() =>
baseComponent.state.showHidePleaseWait(false));
});
We don’t want to get the list of contacts until the list of mailboxes is done so that
we know that all server calls are done before the please wait popup is hidden, so we
use the then() syntax to chain them. Inside the then() callback, another function
is defined, getContacts() this time, for the same reason: async/await usage. Once
defined, we can call getContacts() and again use the then() syntax so that we can call
showHidePleaseWait(), passing false this time, to cause React to hide the please wait
popup.
Chapter 9 Delivering the gooDs: MailBag, the Client
237
The addContactToList() state mutator method is used in there, and it’s virtually
identical to addMailboxToList():
addContactToList : function(inContact: Contacts.IContact): void {
const cl = this.state.contacts.slice(0);
cl.push({ _id : inContact._id,
name : inContact.name, email : inContact.email });
this.setState({ contacts : cl });
}.bind(inParentComponent)
In this case, I’ve constructed the object push()’ed into the contacts array explicitly,
not for any particular reason other than to show that you can. If you wanted the client
contact objects to have different fields than the server-supplied objects for some reason,
this is how you can do that translation.
Do'stlaringiz bilan baham: |