Now that my Button
responds to clicks (see previous post), I can program my React Native app to do something interesting in response to the click. Remember that my user is going to select a movie title from a dropdown list of movies, and then click a button to get a list of nearby movie theaters where the movie is showing.
There are REST APIs that will return the information we want. However, I’m not going to use them – at least not yet. Why not? Because I don’t want to go down a rabbit hole researching all the possible REST APIs. Even if it wouldn’t take very long, I don’t want to get sidetracked right now.
It’s important to note that if you’re building an app for business purposes, you should not lift one finger to code up your app until you’ve figured out what 3rd party services you need, and whether there is one available which will let you access it within your budget constraints. Suppose you’ve decided that you want to build an app which supplies turn-by-turn directions to every independent coffee shop across the world. This sounds kind of like a niche app, “Google Maps for coffee”. Well, just because the Google Maps app can do something like this, doesn’t mean that a REST service exists which will be available to you. Don’t make that kind of assumption! And even if the service does exist, it may not be affordable. Suppose it exists but there’s a charge for each API call… will your business model be profitable if you take into account the cost?
Since we’re not building a real app, but just messing around to become familiar with React Native, we don’t have to worry about the above business realities. Instead, I’m going to build a very simple REST API which returns a list of movie locations. By mocking the API, I can keep my flow of building the app working, and worry about third-party features later.
I can easily mock the API because I’ve got a domain name and a shared server. All I have to do is upload a PHP file with the following content:
<?php
$theaters = array();
$theaters[] = array( "name" => "Paris Theatre", "phone" => "(212)688-3800", "address1" => "4 West 58th Street", "city" => "New York", "state" => "NY", "zip" => "10019");
$theaters[] = array( "name" => "French Institute Alliance Francaise", "phone" => "(212)355-6160", "address1" => "55 East 59th Street", "city" => "New York", "state" => "NY", "zip" => "10022");
$theaters[] = array( "name" => "Roxy Cinema Tribeca", "phone" => "(212)519-6820", "address1" => "2 Avenue of the Americas", "city" => "New York", "state" => "NY", "zip" => "10013");
$out = json_encode($theaters);
echo $out;
?>
The list of movie theaters gives this static output:
[{"name":"Paris Theatre","phone":"(212)688-3800","address1":"4 West 58th Street","city":"New York","state":"NY","zip":"10019"},
{"name":"French Institute Alliance Francaise","phone":"(212)355-6160","address1":"55 East 59th Street","city":"New York","state":"NY","zip":"10022"},
{"name":"Roxy Cinema Tribeca","phone":"(212)519-6820","address1":"2 Avenue of the Americas","city":"New York","state":"NY","zip":"10013"}]
You can do that with a static HTML page if you want, it doesn’t matter. If you don’t have a server and don’t want to spend time setting one up, just reuse my URL.
I’m going to do a little copy/paste here. People rant against copying and pasting code, and there are reasons not to do it, but it’s actually pretty reasonable to do this in rapid application development of prototypes. The idea is “let’s just get this done, since the code might be thrown out tomorrow.” Yes, doing this will lead to technical debt. However, you may never have to pay off the debt, if the application is just an experiment. Further, you’re not at a stage where refactoring for reusability makes sense, yet. So I just copy the code that was used to fetch
in componentDidMount
, paste it, and make some small changes. Ditto for my setMovieState
and handleMoviesResponse
methods. Here’s what I wind up with:
...
setNearbyTheatersState(theaters) {
this.setState({
isLoading: false,
nearbyTheaters: theaters,
});
}
...
handleNearbyTheatersResponse(theaters, delay) {
if (delay && delay > 0) {
const timer = setTimeout(function () {
this.setNearbyTheatersState(theaters);
}.bind(this), delay);
} else {
this.setNearbyTheatersState(theaters);
}
}
...
handleClick() {
console.log("handleClick");
return fetch('https://www.fullstackoasis.com/rnb/theaters.php')
.then((response) => response.json())
.then((responseJson) => {
// TODO FIXME handle timeout / delay
this.handleNearbyTheatersResponse(responseJson);
})
.catch((error) => {
// TODO FIXME replace the red screen with something informative.
console.error(error);
});
}
...
This is pretty much a repeat of the REST call to get the list of movies, only now I’m calling my own mocked API, and setting a state parameter named “nearbyTheaters”. Looks good! But when I click the button, I see a RedBox which reads "TypeError: _this2.handleNearbyTheatersResponse is not a function. (In '_this2.handleNearbyTheatersResponse(responseJson)', '_this2.handleNearbyTheatersResponse' is undefined)"
.
You might think that copy/paste didn’t work so well for us… but I’d disagree. It got us to an error faster than if we wrote all the code from scratch. Now it’s just a matter of figuring out what’s gone wrong.
To help debug this problem, I add a comment just before the call to this.handleNearbyTheatersResponse
:
handleClick() {
console.log("handleClick");
return fetch('https://www.fullstackoasis.com/rnb/theaters.php')
.then((response) => response.json())
.then((responseJson) => {
// TODO FIXME handle timeout / delay
console.log(this);
this.handleNearbyTheatersResponse(responseJson);
})
.catch((error) => {
// TODO FIXME replace the red screen with something informative.
console.error(error);
});
}
Then I click the button again. I see the following logged in my terminal:
{"accessibilityLabel": undefined, "accessibilityRole": "button", "accessibilityStates": [], "background": {"attribute": "selectableItemBackground", "type": "ThemeAttrAndroid"}, "children": <RCTView style={[[Object]]}><ForwardRef(Text) style={[Array]}>FIND MOVIE NEAR ME</ForwardRef(Text)></RCTView>, "disabled": undefined, "hasTVPreferredFocus": undefined, "nextFocusDown": undefined, "nextFocusForward": undefined, "nextFocusLeft": undefined, "nextFocusRight": undefined, "nextFocusUp": undefined, "onPress": [Function handleClick], "testID": undefined, "touchSoundDisabled": undefined}
At this point I was a little puzzled; it looked to me like this
was a reference to my Button
, and not my app. I expected the latter. Here’s the relevant part of the render
method, again:
<Button onPress={this.handleClick} title="Find Movie Near Me"></Button>
In fact, JavaScript’s correct behavior is a little hazy to me. I could find discussions that made claims about the standard behavior, but no references to the specification, and I didn’t go hunting for the official answer. Let’s not waste time tracking this down right now. Let’s assume this
refers to the Button
being pressed. That may be useful in some cases, but in my case, I want this
to reference the app itself so that I can call handleNearbyTheatersResponse
within the handleClick
method.
I took a stab at fixing the problem by binding the Button
‘s onPress
method to this
:
<Button onPress={this.handleClick.bind(this)} title="Find Movie Near Me"></Button>
That worked! Once I do that, clicking the button does not produce an error. The call to setState
in setNearbyTheatersState
is made. I verified this by adding a new Picker
which is populated with theater listings if this.state.nearbyTheaters
exists.
Here’s the new code in my render
method:
if (this.state && !this.state.isLoading) {
let items = [];
var length = this.state.dataSource.length;
for (var i = 0; i < length; i++) {
var item = this.state.dataSource[i];
// Really, really important to not put quotes around these braces:
items.push(<Picker.Item label={item.title} value={item.title} key={item.id} />);
}
var theatersPicker = null;
if (this.state.nearbyTheaters) {
let theaters = this.state.nearbyTheaters.map(function (t, i) {
return <Picker.Item label={t.name} value={t.name} key={i} />;
});
theatersPicker = <Picker>{theaters}</Picker>
}
return <View>
<Picker selectedValue={this.state.movie}
onValueChange={(itemValue, itemIndex) =>
this.setState({ movie: itemValue })
}>
{items}
</Picker>
<Button onPress={this.handleClick.bind(this)} title="Find Movie Near Me"></Button>
{theatersPicker}
</View>;
...