Skip to main content
Version: 1.x.x

Subscribing to Changes

Responding promptly to data changes is what makes your live app feel alive. It's especially important to subscribe to changes in your Record data and update your app in response:

someRecord.listen(function(updatedRecord) {
console.log("Record updated!");
// Update app UI with updatedRecord
});

When the listen callback triggers for a Record, it means the Record's underlying data changed. That change might have come from the same user and window, or from another user or window.

You'll also want to listen on changes to recordList:

recordList.listen(function(updatedList) {
console.log("Record list updated");
// Update app UI with updatedList
});

When the listen callback triggers for a RecordList, it means that list items were added, moved, or removed.

note

Changes to child records won't automatically trigger listeners on their parents - so when nesting records, it is recommended that you add listeners for your children and call this.notifyListners() on the record that your rendering component is listening to.

For instance, here's an example of an application that reduces all app data to a single event on the root by listening to all the children in the record tree:

import React from "react";
import quip from "quip-apps-api";

interface ChildData {
name: string;
}

class Child extends quip.apps.Record {
static getProperties() {
return {
name: "string"
}
}
getData(): ChildData {
return {
name: this.get("name")
};
}
}

interface AppData {
children: ChildData[]
}

class Root extends quip.apps.RootRecord {
static getProperties() {
return {
children: quip.apps.RecordList.Type(Child)
}
}
static getDefaultProperties() {
return {
children: []
}
}

private getChildren = () => this.get("children") as quip.apps.RecordList<Child>
private listenedChildren: Child = []

private childUpdated = () => {
this.notifyListeners()
}

initialize() {
const updateChildListeners = () => {
// First, clear out our existing listeners to avoid leaks
listenedChildren.forEach(child => {
child.unlisten(this.childUpdated)
})
// clear out our listeners so it'll be accurate
this.listenedChildren = []
// then add new listeners for all our current children
this.getChildren().getRecords().forEach(child => {
child.listen(this.childUpdated)
// track all the children we're listening to so we can
// unlisten to them when this list changes
this.listenedChildren.push(child)
})
}
// Add a listener to the list itself which will make sure we listen
// to all children
this.getChildren().listen(updateChildListeners)
// invoke it so we initialize with listeners
updateChildListeners()
}
getData(): AppData {
// Now in our app data, we'll have an always up to date version of all
// child data, without having to write imperative listening logic
// in our React component.
// This function will be called every time we call notifyListeners,
// and our React component can only track one state, making our
// rendering logic pure and deterministic.
const children = this.getChildren()
.getRecords()
.map(record => record.getData());
return {
children
};
}
}

interface AppProps {
root: Root;
}

interface AppState {
data: AppData;
}

class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
// initialize our state to whatever the state is when we instantiate
// this component.
const {root} = this.props;
const {data} = root.getData();
this.state = {data};
}
componentDidMount() {
const {root} = this.props;
// This is now the only listener we need in our entire UI layer.
root.listen(this.updateData);
this.updateData();
}
componentWillUnmount() {
// when our component unmounts, remove our listener so we don't leak.
const {root} = this.props;
root.unlisten(this.updateData);
}

private updateData = () => {
const {root} = this.props;
this.setState({data: root.getData()});
}

render() {
const {root} = this.props;
const {children} = this.state.data;
return <ul>
{children.map(child =>
<li key={child.name}>
{child.name}
</li>
)}
</ul>
}

}

quip.apps.initialize({
initializationCallback: async (dom, params) => {
const root = quip.apps.getRootRecord() as Root;
ReactDOM.render(<App root={root}/>, dom);
}
});

The Record APIs and RecordList APIs have more details on listening to changes in quip.apps.Record and quip.apps.RecordList data. In addition, Subscribing to Events has a list of the environment updates (such as blur and focus) that we currently support, along with the generic interface you can use to subscribe/unsubscribe from these events.