What’s in a callback

So we left it where we could post new tweets but then had to manually refresh the page to see the tweet we’d just added.

The last piece of our React puzzle is to update the user interface (list of tweets) automatically when a new one is posted.

React employs a handy concept called callbacks for exactly this kind of requirement.

We can modify PostNew to invoke a callback when we’ve finished saving the new tweet. We can then react to this callback in List.js and trigger a refresh of the tweets from our API.

Head over to PostNew.js and update your handleSubmit handler to look like this:

async handleSubmit(event) {
    event.preventDefault();
    const options = {
        method: 'POST',
        body: JSON.stringify(this.state),
        headers: { 'Content-Type': 'application/json' }
    };
    await fetch('Tweet', options);
    this.props.onPosted();
}

The only change here is the last line, where we make a call to this.props.onPosted().

This will invoke whatever function is passed in to our component via onPosted so let’s go pass something in!

Head over to List.js.

Now we can pass in a handler for onPosted and make our List component run our tweets query again, and update itself accordingly…

First up you’ll want to find the <PostNew /> line and update it as follows:

<PostNew onPosted={this.handleNewTweet}/>

Then you’ll need to update the rest of the component to look like this…

import React from 'react';
import Tweet from './Tweet'
import PostNew from "./PostNew";

export default class List extends React.Component {

    state = {
        tweets: []
    };

    constructor(props){
        super(props);
        this.handleNewTweet = this.handleNewTweet.bind(this);
    }
    
    async componentDidMount() {
        this.fetchData();
    }
    
    async fetchData(){
        const response = await fetch('Tweet');
        const data = await response.json();
        this.setState({tweets: data.tweets});
    }

    async handleNewTweet(){
        this.fetchData();
    }
    
    render() {
        return (
            <>
                <h3>Tweets</h3>
                <PostNew onPosted={this.handleNewTweet}/>
                {this.state.tweets.map(tweet => <Tweet text={tweet}/>)}
            </>
        );
    }
}

I’ve extracted the little bit of code needed to retrieve our list of tweets into its own fetchData method. This way we can still call it from onComponentDidMount but also call it any other time we need to.

Then I’ve added a function called handleNewTweet and made sure it also calls fetchData. It’s this handleNewTweet function that will be invoked every time we post a new tweet.

And of course, I ran into the javascript this problem again, so I’ve added a constructor to make sure handleNewTweet has access to the component via this and can call this.fetchData().

MyTweets-React

And there it is, post a new tweet and the list automatically refreshes accordingly.

Separation of concerns FTW

Now you have a really clean way to model your application as a serious of queries and commands.

I’ve used this approach on several large projects and the good news is this scales; so no matter how complex or large your application becomes, you can nearly always break it down into a number of commands and queries, modelling them as we’ve done here.

The basic rule of thumb is to avoid mixing the two (queries which retrieve data, and commands which change data in some way) so when you’re adding new features try to be clear whether you’re just retrieving data to show in the UI, or actually making a change to the data.

If you are making a change, you’ll typically execute a query (as we did here) to “refresh” the data once you know its changed.

Remember these are the three core concepts in React.js…

  • State
  • Props
  • Callbacks

These are the building blocks of all React.js applications.

State represents the current state of any given component. Changes to that component (entering values in form fields etc) will update that state, and the UI itself can react based on changes to the state.

Props are used to pass information to other components. These components can then render that information, react to it and take further actions when that property changes.

Callbacks are useful for signaling “back up” the component tree, to tell something higher up that something interesting has happened. Components higher up the component tree can handle these callbacks, perform whatever actions they need, then push data back down into their child components (using props).

1572882396048

This is why people talk about state “moving in one direction” with React.js.

The state comes in at one level (our List component), is pushed down into other components (often via Props), who subsequently raise callbacks which go back up the tree to any components who are registered to handle it.

These components then do something (like refreshing the data from a server) and can push state back down the component tree again.

Next Steps

If you’re wondering where to go next. Here are a couple of ideas for next steps…

  • Reverse the order of the tweets list (currently showing newest tweets at the bottom)
  • Add the ability to delete a tweet
  • Add some more properties to a tweet (like date posted etc) and show these in the UI

As you approach these and subsequent tasks, just remember to keep all your business logic in the MediatR commands and queries. Keep the UI as dumb as possible and you’ll be well on your way to building an application which adapts and scales as new requirements come in.