Monthly Archives: February 2020

How to handle Button clicks in React Native – Part I

I’ve been building an app which lets me find movies “near me” using mocked data. In my last post, I added a progress loader to display when there’s networking latency. I have a dropdown with a list of movie names in my app, and the user can select any movie in the list. Let’s suppose the user wants to see one of these movies, and would like to see a list of closest locations where the movie is showing. We’re going to need a button so that the user can click it to get results.

You might wonder why you shouldn’t just load results when the user selects a movie name from the Picker. Well, it depends on how you feel about UX. Some apps will immediately give you results when an element is selected from a dropdown. However, just because a user selects an item doesn’t mean that is their final choice. Maybe they lifted their finger too soon, and wound up selecting something that they didn’t want. Or, maybe they’re just thinking about their different options.

My personal preference for dealing with a select is to add a confirmation button when the result of selecting an item from the Picker is a resource intensive task, like hitting a database or making a network call. You don’t want to waste resources every time a user selects an item from a dropdown.

So in this example, making a network call should only happen if the user clicks a button. Let’s add a button to our view – just below the Picker:

import { View, Picker, ActivityIndicator, Button } from 'react-native';
...
render() {
    ...
    return <View>
        <Picker...
        </Picker>
        <Button title="Find Movie Near Me"></Button>
    </View>;
    ...
}

You can’t add a Button without a title; you’ll see a warning if you do.

The button is probably the easiest control to add in React Native! Here’s what I see after I add just a couple of lines:

However, clicking the button is a different story. If you’re used to web development, you might expect an “onClick” property. Nope, it’s onPress. And you can’t just add a method like this:

...
handleClick() {
    console.log("Handled");
}
...
    <Button onPress="handleClick" title="Find Movie Near Me"></Button>
...

The app won’t complain if you do this, but if you click the button you’ll see an error: "TypeError: this.props.onPress is not a function. (In 'this.props.onPress(e);, 'this.props.onPress' is "handleClick")".

The method handleClick is actually a function of “this”, the app. So you have to reference it correctly. It doesn’t help to replace onPress="handleClick" with onPress="this.handleClick" because anything in quotes is a “literal”, treated as a string, in JSX. Instead, you have to do as follows:

...
handleClick() {
    console.log("handleClick");
}
...
    <Button onPress={this.handleClick} title="Find Movie Near Me"></Button>
...

Now the button responds to clicks, as you can see below!

Got comments? Write an email to me at fullstackdev@fullstackoasis.com and click the send button! If you found this interesting, click the subscribe button above. I write a new post about once a week.

How to add a progress loader in React Native

In my last post, I took a look at how network latency might affect my React Native app, and I didn’t like what I saw: a pure white screen for several seconds. How is a user supposed to “react” to that? (Pun intended!)

It was pretty clear what was happening. My app’s render method just shows a blank view if it has no state. Here’s the render method:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View></View>);
    }
}

That’s my white screen. There’s a super simple fix:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View><Text>waiting...</Text></View>);
    }
}

Now there’s text which reads “waiting…” at the top. That’s okay for a hobby app. You’re probably going to want that to look a little nicer, however. How about a progress indicator? Something that shows “we’re working on this!”. React Native comes with an ActivityIndicator which does what we want. I’m going to grab some of the code from their documentation and add it to my app. In the render method below, I’ve replaced my “waiting” text with an ActivityIndicator (and I’ve shown the import statement to remind you to add that as well).

import { View, Text, Picker, ActivityIndicator } from 'react-native';
...
render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View><ActivityIndicator size="large" color="#0000ff" /></View>);
    }
}

That helps some, but the progress indicator still doesn’t look very nice. It’s placed at the very top of the screen, like this:

It turns out to be pretty easy to move the ActivityIndicator to the center of the screen. You just have to add some style components to its container View, like this:

render() {
    if (this.state && !this.state.isLoading) {
        ...
    } else {
        return (<View style={[{ flex: 1, justifyContent: 'center' },
            { flexDirection: 'row', justifyContent: 'space-around', padding: 10 }]}>
            <ActivityIndicator size="large" color="#0000ff" />
        </View>);
    }
}

Here’s the result:

With very little trouble at all, I’ve now got a slick little widget to let people know that something is going on. It’s not really enough, though. What if the network request times out, and I get an error? I’ll have to delve into that more deeply later. To remind me to do this, I’ll add a TODO/FIXME comment in the code, and open an issue in my issue tracker.

Got comments? Send them to me in an email at fullstackdev@fullstackoasis.com. If you found this interesting, go ahead and click the subscribe button above. I write a new post about once a week.

How to test networking latency in React Native

In my previous post, I tested my React Native app to see what would happen if a fetch to get data from a URL resulted in an error. In my experience, URLs don’t just fail with a 404 error, however. Sometimes there’s a time delay in getting a response from a URL. This is sometimes called latency or a lag.

I wanted to see how my app would behave if the URL that it fetched had a long delay in responding. But I didn’t have a URL that would reliably take a long time to respond! How could I mimic this situation? That’s what I’ll address in this blog post.

My app does the fetch in the componentDidMount method, and then sets state to update the display, like this:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setState({
                isLoading: false,
                dataSource: responseJson.movies,
            });
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

My initial attempt at adding a delay was to set a timeout around the setState call, but this resulted in an error. To simplify the code and make it easier to test, I moved the call to setState into a separate, reusable chunk of code, like this:

setMovieState(movies) {
    this.setState({
        isLoading: false,
        dataSource: movies,
    });
}

So then my componentDidMount becomes:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setMovieState(responseJson.movies);
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

That looks a little cleaner, but I still need to do something to introduce a lag.

Let me write a new little piece of code which calls the method setMovieState after a specified delay:

handleMoviesResponse(movies, delay) {
    if (delay && delay > 0) {
        const timer = setTimeout(function () {
            this.setMovieState(movies);
        }.bind(this), delay);
    } else {
        this.setMovieState(movies);
    }
}

If there’s no delay, the state will be set immediately. If the method is called with a delay, then the state is not updated until after the input delay.

If you’re wondering what bind(this) is about, you may not be too familiar with JavaScript. A short answer is that the anonymous function that is passed to setTimeout uses this inside it, and that function needs to know what this is (this is my app which contains the method setMovieState.)

Finally, instead of calling setMovieState from componentDidMount, I call handleMoviesResponse, like this:

this.handleMoviesResponse(responseJson.movies, 5000);

Now that I’ve done this, I can see that when my app opens, I just see a plain white screen for 5 seconds, and then I see my movie titles dropdown (Picker) appear at the top of the page. It’s not a crash, but it seems like a bad user experience. In my next post, I’ll look at how to fix that.

blank screen when there’s network latency

Got comments? Send them to me in an email at fullstackdev@fullstackoasis.com. If you found this interesting, go ahead and click the subscribe button above. I write a new post about once a week.

What causes “SyntaxError: JSON Parse error: Unrecognized token ‘<'" in React Native?

TL;DR: In React Native, you will get “SyntaxError: JSON Parse error: Unrecognized token ‘<‘” if your URL returns a 404 error, or in general if the content is not a JSON string.

In a recent post, I showed how to display a list of movies that had been fetched from a REST API. It worked great, but I wondered what would happen to my app’s user if their device was offline, or if the REST API ever went down. To mimic this behavior, I changed the URL by adding the number 1 at the end of it, like this: “https://facebook.github.io/react-native/movies.json1”.

And here’s what I saw in the emulator:

SyntaxError: JSON Parse error: Unrecognized token ‘<‘

The red screen says “SyntaxError: JSON Parse error: Unrecognized token ‘<‘”. That may be confusing, although if you work with REST APIs for any time, you’ll soon come to recognize what it means. Meantime, how do we investigate this?

When I load up this test URL in a web browser, I see content which looks like this:

<!DOCTYPE html>
<html>
  <head>
  ...Page not found...
</html>

It’s a fancy 404 error page. That explains why response.json barfs on this; it’s not JSON. Your app expected a JSON string. It tried to parse the string into a JavaScript object, and couldn’t handle a non-JSON string. As a reminder, here’s that fetch call:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json1')
        .then((response) => response.json())
        .then((responseJson) => {
...
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

In the longer term, I will want to replace that red screen of death with a nice error page which instructs the user what to do. I’m still developing my application, however, and as a dev, I’d rather see the stack trace for errors like this when they occur.

So to deal with this, I’ll do two things: 1) I’ll add a “TODO FIXME” note in my code. When I’m cleaning up code in the end stages of development, I know to look for these types of comments which indicate work still needs to be done. 2) I’ll open an issue in my issue tracker which will let everyone on my team know that there’s something that still has to be handled in building the application. I’ll bring this to the attention of anyone who needs to know (a project manager, perhaps). The project manager may assign a designer to build a page with some graphics or specific text to display to the user in case of this error.

Got comments? Send them to me in an email at fullstackdev@fullstackoasis.com. If you found this interesting, go ahead and click the subscribe button above. I write a new post about once a week.