Writing Money Transfers in React — Part 3
Part 2 created the foundation for our application, and our goal is to show how to create this page with React and Redux. This part of the walk through is going to explain a few basics in how state can be used to determine layout and trigger renders. We will also see how communicate between components, passing functions as properties and using callbacks to allow child components to talk back to their parents.
The code for part 2 is available here, the finished code for part 3 is available here.
Change state and handle events.
So lets start with something simple. When a user clicks on one of the accounts, a drop down list of accounts is displayed for the user to choose from. It’s actually a little more complex than that, but lets start simple.
If you take a look at the TransferAccount component at the end of Part 2, you’ll see that it either shows a card with the account passed to it, or it shows a blank card.
render() {
if (this.props.account) {
return (
<ul className="card-list">
<li className="card-item">
<AccountCard account={this.props.account} />
</li>
</ul>
);
} return (
<a className="card add">Select account</a>
);
}
We need to introduce a 3rd option, if a person wants to see a list of accounts then we need to have something to say that the list is ‘open’ and display a list of accounts if it is. Because I believe in a component having a very simple job we’ll add a new component that’s only job is to display a list of account cards. Lets create that component and then wire it up to be shown on the screen.
AccountList
import React, { Component } from 'react';
import AccountCard from '../components/account-card';var accounts = [{
bank: 'Smile',
account_type: 'Current account',
account_number: '1234',
sort_code: '11-11-11',
}, {
bank: 'Natwest',
account_type: 'Current account',
account_number: '4321',
sort_code: '22-22-22',
}];export default class AccountList extends Component { accountListItem = (account, i) => (
<li className="card-item" key={i}>
<AccountCard account={account}/>
</li>
); render() {
return (
<ul className="card-list">
{this.props.selectedAccount ?
null :
<li className="card-item">
<div className="card blank"> </div>
</li>
}
{accounts.map(this.accountListItem)}
</ul>
);
}
}AccountList.propTypes = {
selectedAccount: React.PropTypes.object,
};
This should all look pretty straightforward by now, we just show a list of account cards. If the component is called without a currently selected account then a blank one is put at the top, to make it look right. The account names are hard coded for now, just to get the idea across.
React to event and show list
Lets start with state, we’ll introduce an open and close state into TransferAccount component. At the top of the source for TransferAccount add:
constructor(props) {
super(props);
this.state = {
open: false,
};
}
ES6 classes can have constructors. The constructor for a React component is called with a parameter called props, and the first thing you always do is call the parent class with the same parameter, then do what you need to do. The constructor is the ONLY place you set the state using ‘this.state = {}’, everywhere else MUST use setState, so as to trigger the re-render. So we introduce a state property called ‘open’.
Now in the render method we can use this state variable to choose to show the list.
render() {
if (this.state.open) {
return (
<AccountList selectedAccount={this.props.account} />
);
} else if (this.props.account) {
return (
<ul className="card-list">
<li className="card-item">
<AccountCard account={this.props.account} />
</li>
</ul>
);
} return (
<a className="card add">Select account</a>
);
}
All well and good, but we still can’t actually see the list. We need to introduce a way to change the state. This is really easy, and hopefully demonstrates why I love React. We create a method to change the state and call it on a click.
clickEdit = () => {
this.setState({
open: true,
});
}
And in the render method make a small change
<a className="card add" onClick={this.clickEdit}>Select account</a>
If the user clicks on the add card then it calls the ‘clickEdit’ method, this changes the state and that triggers a call to ‘render’ and the drop down list appears. We can’t click on an existing card though, that’s because the AccountCard is a custom component, plus we want it to be a bit smarter.
Callbacks
An AccountCard represents an account, and it would be useful when it is clicked if we knew what account is associated with it. If a component uses the AccountCard how does AccountCard tell it what happened? As you’ve seen we can pass properties to a component and this is Javascript, functions are just another datatype Javascript loves callbacks. The AccountCard needs to accept a callback function and call it with the account when it is clicked.
export default class AccountCard extends Component { selectAccount = () => {
this.props.selectAccount(this.props.account);
} render() {
const account = this.props.account;
return (
<div className="card" onClick={this.selectAccount}>
<div className="account card-title">
{account.bank} {account.account_type}
</div>
<dl className="labelvalue">
<dt className="label">Account number:</dt>
<dd className="value">0000{account.account_number}</dd>
<dt className="label">Sort code:</dt>
<dd className="value">{account.sort_code}</dd>
</dl>
</div>
);
}
}
Back in the TransferAccount, make this small change and you can click on a card that shows an account and it will open the list.
<ul className="card-list">
<li className="card-item">
<AccountCard
account={this.props.account}
selectAccount={this.clickEdit}
/>
</li>
</ul>
So now you can see a list of accounts, lets complete it so that you can select one. Now we are going to cheat a little. When you select the account we’ll output that account to the log and the dropdown list will change, but the account selected wont. The only way we can actually show a changed account it to use state to store all the transfers. I was going to include in this step a way to do this but the code would have got big real fast and as the whole point is to show how to use Redux, we’ll leave that for the next part.
Passing callbacks to grand-children
Lets pickup the fact that the user clicks on an account to select it. In TransferAccount you show a AccountList which in turn shows a list of AccountCards. AccountCard has a ‘selectAccount’ property, so lets add one into AccountList too that can pass the function down to the AccountCard. When the callback function is called, the account is sent all the way back up to TransferAccount.
In AccounList make a change to the accountListItem method, which is responsible for rendering a list of AccountCard’s.
accountListItem = (account, i) => (
<li className="card-item" key={i}>
<AccountCard
account={account}
selectAccount={this.props.selectAccount}
/>
</li>
);
In TransferAccount create a new function to handle when a user selects an account, and pass it to AccountList.
selectAccount = (account) => {
console.log('selected account:', account);
this.setState({
open: false,
});
}...<AccountList
selectedAccount={this.props.account}
selectAccount={this.selectAccount}
/>
Hopefully now you are starting to see a pattern. You pass a callback function as a property which can be a function in the calling component class or a property sent to it and it’s passing it down the chain.
If you compare part 2 and part 3 you’ll actually see that not much code changed, but hopefully you’ll understand how to communicate between components and control what is rendered.
What next?
The application now handles some interaction, but you can’t actually change any data. You could add the functionality to handle changes in regular React components as state in the MoneyTransfers component, but Redux gives us an alternative. Redux takes responsibility for managing state and listening to requests to change that state and communicating it to the components it affects.