Skip to content

Commit

Permalink
Merge pull request #96 from googleinterns/real-time-updates
Browse files Browse the repository at this point in the history
View Trips Page Real time Updates
  • Loading branch information
zghera authored Aug 5, 2020
2 parents e1e5e44 + d54c962 commit a278a64
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 105 deletions.
8 changes: 1 addition & 7 deletions frontend/src/components/ViewTrips/delete-trip-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const LIMIT_QUERY_DOCS_RETRIEVED = 5;
*
* @param {Object} props These are the props for this component:
* - tripId: Document ID for the current Trip document.
* - refreshTripsContainer: Handler that refreshes the TripsContainer
* component upon trip creation (Remove when fix Issue #62).
*/
const DeleteTripsButton = (props) => {
/**
Expand Down Expand Up @@ -78,8 +76,6 @@ const DeleteTripsButton = (props) => {
/**
* Deletes a trip and its subcollection of activities corrsponding to the
* `tripId` prop and then refreshes the TripsContainer component.
*
* TODO(Issue #62): Remove refreshTripsContainer.
*/
async function deleteTrip() {
if (window.confirm('Are you sure you want to delete this trip? This' +
Expand All @@ -101,9 +97,7 @@ const DeleteTripsButton = (props) => {
}).catch(error => {
console.error("Error removing document: ", error);
});

props.refreshTripsContainer();
}
}
}

return (
Expand Down
18 changes: 0 additions & 18 deletions frontend/src/components/ViewTrips/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,12 @@ class ViewTrips extends React.Component {
constructor() {
super();
this.state = { showModal: false,
refreshTripsContainer: false,
refreshSaveTripModal: false,
tripId: null,
defaultFormObj: null,
};
}

/**
* Handler that flips `refreshTripsContainer` property which causes that
* TripsContainer component to be reloaded.
*
* This allows a trip creator's view trips page to be updated in real time.
*
* In the future, the use of refreshTripContainer and the key prop on Trips
* container should be removed with the addition of real time listening with
* onShapshot (Issue #62).
*/
refreshTripsContainer = () => {
this.setState({ refreshTripsContainer: !this.state.refreshTripsContainer });
}

/**
* Handler that flips `refreshSaveTripModal` property which causes that
* save/edit trip component to be reloaded.
Expand Down Expand Up @@ -97,7 +82,6 @@ class ViewTrips extends React.Component {
<SaveTripModal
show={this.state.showModal}
handleClose={this.hideSaveTripModal}
refreshTripsContainer={this.refreshTripsContainer}
tripId={this.state.tripId}
defaultFormObj={this.state.defaultFormObj}
key={this.state.refreshSaveTripModal}
Expand All @@ -109,8 +93,6 @@ class ViewTrips extends React.Component {
</div>
<TripsContainer
handleEditTrip={this.showEditTripModal}
refreshTripsContainer={this.refreshTripsContainer}
key={this.state.refreshTripsContainer}
/>
</div>
);
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/components/ViewTrips/save-trip-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const db = app.firestore();
* @param {Object} props These are the props for this component:
* - show: Boolean that determines if the add trips modal should be displayed.
* - handleClose: Event handler responsible for closing the add trips modal.
* - refreshTripsContainer: Handler that refreshes the TripsContainer
* component upon trip creation (Remove when fix Issue #62).
* - tripId: For adding a new trip, this will be null. For editting an existing
* trip, this will the document id associated with the trip.
* - defaultFormObj: Object containing the placeholder/default values for the
Expand Down Expand Up @@ -132,18 +130,15 @@ class SaveTripModal extends React.Component {
} else {
this.updateExistingTrip(this.props.tripId, tripData);
}

}

/**
* Handles submission of the form which includes:
* - Creation of the trip.
* - Refreshing the trips container.
* - Closing the modal.
*/
handleSubmitForm = async () => {
await this.saveTrip();
this.props.refreshTripsContainer();
this.props.handleClose();
}

Expand Down
7 changes: 1 addition & 6 deletions frontend/src/components/ViewTrips/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ export function moveCurUserEmailToFront(collaboratorEmailArr) {
* - tripData: Object holding a Trip document fields and corresponding values.
* - tripId: Document ID for the current Trip document.
* - handleEditTrip: Handler that displays the edit trip modal.
* - refreshTripsContainer: Handler that refreshes the TripsContainer
* component upon trip creation (Remove when fix Issue #62).
* - key: Special React attribute that ensures a new Trip instance is
* created whenever this key is updated
*/
Expand Down Expand Up @@ -87,10 +85,7 @@ const Trip = (props) => {
<p>{description}</p>
<p>{collaboratorEmailsStr}</p>

<DeleteTripButton
tripId={props.tripId}
refreshTripsContainer={props.refreshTripsContainer}
/>
<DeleteTripButton tripId={props.tripId} />
<Button
type='button'
onClick={() => props.handleEditTrip(props.tripId, formattedTripData)}
Expand Down
117 changes: 48 additions & 69 deletions frontend/src/components/ViewTrips/trips-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,25 @@ import Trip from './trip.js';
const db = app.firestore();

/**
* Returns a promise of a query object containg the array of Trip Documents
* corresponding to the trips that the current user is a collaborator on.
* Returns a `<div>` element with an error message. The error message `error`
* will be logged but not seen by the user.
*
* @param {firebase.firestore.Firestore} db The Firestore database instance.
* @return {Promise<!firebase.firestore.QuerySnapshot>} Promise object
* containing the query results with zero or more Trip documents.
*/
function queryUserTrips(db) {
const curUserUid = authUtils.getCurUserUid();
return db.collection(DB.COLLECTION_TRIPS)
.where(DB.TRIPS_COLLABORATORS, 'array-contains', curUserUid)
.orderBy(DB.TRIPS_CREATION_TIME, 'desc')
.get();
}

/**
* Grabs Trips query result from `queryUserTrips()` and returns an array of
* `<Trip>` elements as defined in `trip.js`.
*
* @param {Promise<!firebase.firestore.QuerySnapshot>} querySnapshot Promise
* object containing the query results with zero or more Trip documents.
* @param {EventHandler} handleEditTrip Displays the edit trip modal.
* @param {EventHandler} refreshTripsContainer Refreshed the TripsContainer
* component (Remove when fix Issue #62).
* @return {Promise<!Array<Trip>>} Promise object containing an array
* of Trip React/HTML elements corresponding to the Trip documents included
* in `querySnapshot`.
*/
function serveTrips(querySnapshot, handleEditTrip, refreshTripsContainer) {
return new Promise(function(resolve) {
const tripsContainer = querySnapshot.docs.map(doc =>
( <Trip
tripData={doc.data()}
tripId={doc.id}
handleEditTrip={handleEditTrip}
refreshTripsContainer={refreshTripsContainer}
key={doc.id}
/>
)
);
resolve(tripsContainer);
});
}

/**
* Returns a `<div>` element with the specified error message.
* TODO(Issue #98): Turn this func into component and add to Errors directory.
*
* @param {string} error Error message in `componentDidMount` catch statement.
* @return {Promise<HTMLDivElement>} Promise object containing a `<div>` element
* with the error message `error` inside.
* @return {HTMLDivElement} `<div>` element containing the error message that
* the user will see on the view trips page.
*/
function getErrorElement(error) {
return new Promise(function(resolve) {
console.log(`Error in Trips Container: ${error}`);
resolve(( <div><p>Error: Unable to load your trips.</p></div> ));
});
console.log(`Error in Trips Container: ${error}`);

return (
<div>
<p>Oops, it looks like we were unable to load your trips.
Please wait a few minutes and try again.
</p>
</div>
);
}

/**
Expand All @@ -73,38 +36,54 @@ function getErrorElement(error) {
*
* @param {Object} props These are the props for this component:
* - handleEditTrip: Handler that displays the edit trip modal.
* - refreshTripsContainer: Handler that refreshes the TripsContainer
* component upon trip creation (Remove when fix Issue #62).
* - key: Special React attribute that ensures a new TripsContainer instance is
* created whenever this key is updated (Remove when fix Issue #62).
* @extends React.Component
*/
class TripsContainer extends React.Component {
/** @inheritdoc */
constructor(props) {
super(props);
this.state = {trips: []};
this.state = {tripsContainer: []};
}

/** @inheritdoc */
/**
* When the TripsContainer mounts, a listener is attached to the QuerySnapshot
* event that grabs all trip documents where the current user uid is contained
* in the collaborator uid array (collaborators field). This allows real-time
* updates for all collaborators on a trip whenever a trip is updated (add,
* edit, or delete).
*
* In the case where there is an error, an error component is returned in
* place of the array of trips.
*
* @override
*/
async componentDidMount() {
try {
const querySnapshot = await queryUserTrips(db);
let tripsContainer = await serveTrips(querySnapshot,
this.props.handleEditTrip,
this.props.refreshTripsContainer);
this.setState({ trips: tripsContainer });
}
catch (error) {
let errorElement = await getErrorElement(error);
this.setState({ trips: errorElement });
}
const curUserUid = authUtils.getCurUserUid();
db.collection(DB.COLLECTION_TRIPS)
.where(DB.TRIPS_COLLABORATORS, 'array-contains', curUserUid)
.orderBy(DB.TRIPS_CREATION_TIME, 'desc')
.onSnapshot(querySnapshot => {
const tripsArr = querySnapshot.docs.map(doc =>
( <Trip
tripData={doc.data()}
tripId={doc.id}
handleEditTrip={this.props.handleEditTrip}
key={doc.id}
/>
)
);

this.setState({ tripsContainer: tripsArr });
}, (error) => {
const errorElement = getErrorElement(error);
this.setState({ tripsContainer: errorElement });
});
}

/** @inheritdoc */
render() {
return (
<div>{this.state.trips}</div>
<div>{this.state.tripsContainer}</div>
);
}
}
Expand Down

0 comments on commit a278a64

Please sign in to comment.