Using React

How to write client applications using React

We will base this guide on Book Collection example - React, which shows an editable list of books, with their titles and authors. To try out the example, visit the link and follow the instructions.

Before we go into the code on how to use ResClient together with React, we need to have a look at how ResClient handles state.

State

ResClient stores and manages the state in model and collection objects, which represents the state as it is on the backend. The resource objects are to be considered immutable, and should only be managed and updated by ResClient itself, based on events coming from Resgate.

The basic idea of Resgate and ResClient is to subscribe to resources which is currently relevant to the user. Eg. if a user is browsing a list of company personel, it would be wasteful to store and update the state of the company’s inventory list when it is not in view. Since ResClient keeps track of which resources are being listened to, we can avoid redundant subscriptions by having Components listening for resource events themselves, and unlisten once they are to be unmounted.

Note

Using other state management solutions, such as Redux or MobX, is possible, but will require additional code to keep the stores in sync. In addition, ResClient may no longer be able to track resource usage, unless the other store has means to track it as well.

Initializing ResClient

To get data from Resgate, we need to create a ResClient instance. This can be done in many places, but in our example this is done inside the App component’s constructor:

class App extends Component {
	constructor(props) {
		super(props);
		this.state = { books: null };
		this.client = new ResClient("ws://127.0.0.1:8080");
	}
	/* ... */
}

Getting a resource

Once the App has mounted, we want to load the resources needed for our components, in this case our list of books. With React, this is preferably done inside the lifecycle method componentDidMount:

componentDidMount() {
	this.client.get('library.books').then(books => {
		this.setState({	books });
	});
}

Passing resources as props

When it is time to render App, we pass the list of books as props to the component BookList:

render() {
	return (
		<div className="App">
			{ this.state.books
				? <BookList books={this.state.books} />
				: null
			}
		</div>
	);
}

Rendering the list

When rendering the list, we use the resource object just like with any other React Component props:

class BookList extends Component {
	/* ... */

	render() {
		return (
			<div className="BookList">
				{ Array.from(this.props.books).map(m => (
					<Book key={m.id} book={m} />
				)) }
			</div>
		);
	}
}

Note

Because ResClient collections are iterables, but not arrays, we call Array.from in order to use map.

Listen to events

Once we mount, we also want to listen to add and remove events, until we unmount. This is preferably done in the lifecycle method componentDidMount. Because React will not detect changes inside our props when a book is added/removed, we will need to trigger a re-render ourself, using setState:

onUpdate = () => {
	this.setState({});
}

componentDidMount() {
	this.props.books.on('add', this.onUpdate);
	this.props.books.on('remove', this.onUpdate);
}

Unlisten to events

Once the component completes its lifecycle, we need to unregister our callbacks so that ResClient may be free to unsubscribe. This is preferably done in the lifecycle method componentWillUnmount:

componentWillUnmount() {
	this.props.books.off('add', this.onUpdate);
	this.props.books.off('remove', this.onUpdate);
}

Tip

To make the Component reusable with non-ResClient objects, the registering of listeners can be made conditional:

componentDidMount() {
	if (typeof this.props.books.on == "function") {
		this.props.books.on('add', this.onUpdate);
		this.props.books.on('remove', this.onUpdate);
	}
}

componentWillUnmount() {
	if (typeof this.props.books.off == "function") {
		this.props.books.off('add', this.onUpdate);
		this.props.books.off('remove', this.onUpdate);
	}
}

Listening to model events

The above examples shows how to listen/unlisten to collection events. As shown in Book.js, listening to model events is close to identical:

onUpdate = () => {
	this.setState({});
}

componentDidMount() {
	this.props.book.on('change', this.onUpdate);
}

componentWillUnmount() {
	this.props.book.off('change', this.onUpdate);
}

Calling methods

Resources are not only data, but may also have methods that can be called. In the Book.js file, we can see how such a method is called when editing the book’s fields:

this.props.book.set({
	title: newTitle,
	author: newAuthor
});

Conclusion

That’s it! Communication and real-time synchronization with backend is solved for your React app, and everything updates as expected.

What’s left?

This guide doesn’t bring up transition animations between different states. But that subject has more to do with React than with ResClient, and is better discussed by other people.

Enjoy using ResClient!

Note

By using res-react bindings, the amount of boilerplate code can be reduced. Unfortunately, no such bindings exists yet. Care to create them?