GridFS update takes too long to update when using reactjs instead of blaze

I had a meteor app that used GridFS to insert and remove files and it used blaze on the front end; in this case when I uploaded/inserted files and removed /deleted files the update on the screen was instantaneous (reactive to use the language used in meteor). I changed the front end to Reactjs. The update happens in the background when I insert or remove files but the screen does not change instantaneously, it only happens when I refresh the page. I traced the code as it ran and found that FilesCollection’s onAfterUpload is called after withTracker() and render() has rerun, which is too late as it means the file is not loaded yet on the database when the screen re-renders. If FilesCollection’s onAfterUpload ran sooner I would not have this issue. My code is as per below. Is there anything you can think of that I can do to have file insert/upload and removal/delete, update the screen instantaneously when working with Reactjs?

import React, { Component } from 'react'
import { withTracker } from 'meteor/react-meteor-data';
import { Link } from 'react-router-dom'

class DisplayFiles extends Component {
	
	componentWillMount () {
		var isLoggedIn;
		if (Meteor.isClient)
		{
			isLoggedIn = localStorage.getItem("isLoggedIn");
		}
		if (isLoggedIn)
			this.setState({isLoggedIn: true});
	}
	renderUrl(id,ext) {
		var partOfUrl = "/cdn/storage/images/";
		var host = window.location.origin;
		var url = host +  "/cdn/storage/images/" + id + "/original/" + id + ext;
		
		return url
	}
	
	render() {
		const self = this;
		const images = this.props.images;
		
		var isLoggedIn;
		if (this.state)
		{
			isLoggedIn = this.state.isLoggedIn;
		}
		
		const onFileChange = (e) => {
			event.preventDefault();
			
			var id = Meteor.userId();
			var fileName;
			if (e.currentTarget.files && e.currentTarget.files[0]) {
			  var file = e.currentTarget.files[0];
			  var fileName = "User_" + id + "_" + "Details" + "_"

			  Images.insert({
				file: file,
				fileName: fileName + e.currentTarget.files[0].name,
				streams: 'dynamic',
				chunkSize: 'dynamic'
			  });
			}		
			return true;
		}
		
		const onFileRemove = (e) => {
			event.preventDefault();
			
			var id = event.target.name;
			Images.remove({_id:id});
			return true;
		}

		if (Meteor.isClient)
		{
			if (isLoggedIn)
			{
				return (<div id="divIdToPrint">
					<header>
						<h1 class="header1">Paid monies</h1>
					</header>
					<br/>
					<input type="file" name="…" class="fileInput" onChange={onFileChange}></input>
					<br/>
					<table class="table table-striped">
						<tbody>
							{ images.map(function(item) {
	
								return (<tr class="rrow">
									  <td class="rcol">
										<a href={ self.renderUrl(item._id,item.extensionWithDot) } target="_blank">{item.name}</a>
									  </td>
									  <td class="rcol"><button name={item._id} class="deletePersInfoFile" onClick={onFileRemove}>Remove</button></td>
								  </tr>)
							}) }
						</tbody>
					</table>
				</div>)
			}
			else
			{
				return (<div>
					<header>
						<h1 class="header1">Paid monies</h1>
					</header>
					<br/>
					<p align="center">
					<label>You can only view this page when you are logged in.</label>
					<br/>
					</p>
				</div>)
			}
		}
	}
}

export default withTracker( function(props) {

	var images;
	
	if (Meteor.isClient) {
		var id = Meteor.userId();
		var partOfFileName = ".*User_" + id + "_" + "Details_.*";
		
		const subs1 = Meteor.subscribe('detailsImages',partOfFileName);
	
		return {
			loading1: !subs1.ready(),
			images: Images.find({"name":{$regex:partOfFileName}})
		}
	}
	else
	{
		return {
			loading1: true,
			images:images
		}
	}
},
    { 
		reactive: true, 
})(DisplayFiles);
Meteor.publish('detailsImages', function(partOfFileName){
	return Images.find({"name":{$regex:partOfFileName}}).cursor;
});
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
import { createBucket } from './grid/createBucket';
import { createObjectId } from './grid/createObjectId';
import fs from 'fs';

let imagesBucket;
if (Meteor.isServer) {
  imagesBucket = createBucket('allImages');
}

Images = new FilesCollection({
  collectionName: 'images',
  allowClientCode: true,
  debug: Meteor.isServer && process.env.NODE_ENV === 'development',
  onBeforeUpload (file) {
    //if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.extension)) {
    //  return true;
    //}
    return true;
    return 'Please upload image, with size equal or less than 10MB';
  },
  onAfterUpload (file) {
    const self = this;

    // here you could manipulate your file
    // and create a new version, for example a scaled 'thumbnail'
    // ...

    // then we read all versions we have got so far
    Object.keys(file.versions).forEach(versionName => {
      const metadata = { ...file.meta, versionName, fileId: file._id };
      fs.createReadStream(file.versions[ versionName ].path)

      // this is where we upload the binary to the bucket
        .pipe(imagesBucket.openUploadStream(
          file.name,
          {
            contentType: file.type || 'binary/octet-stream',
            metadata
          }
        ))

        // and we unlink the file from the fs on any error
        // that occurred during the upload to prevent zombie files
        .on('error', err => {
          console.error(err);
          self.unlink(this.collection.findOne(file._id), versionName); // Unlink files from FS
        })

        // once we are finished, we attach the gridFS Object id on the
        // FilesCollection document's meta section and finally unlink the
        // upload file from the filesystem
        .on('finish', Meteor.bindEnvironment(ver => {
          const property = `versions.${versionName}.meta.gridFsFileId`;
          self.collection.update(file._id, {
            $set: {
              [ property ]: ver._id.toHexString()
            }
          });
          self.unlink(this.collection.findOne(file._id), versionName); // Unlink files from FS
        }))
    })
  },
  interceptDownload (http, file, versionName) {
    const { gridFsFileId } = file.versions[ versionName ].meta || {};
    if (gridFsFileId) {
      const gfsId = createObjectId({ gridFsFileId });
      const readStream = imagesBucket.openDownloadStream(gfsId);
      readStream.on('data', (data) => {
        http.response.write(data);
      })

      readStream.on('end', () => {
        http.response.end('end');
      })

      readStream.on('error', () => {
        // not found probably
        // eslint-disable-next-line no-param-reassign
        http.response.statusCode = 404;
        http.response.end('not found');
      })

      http.response.setHeader('Cache-Control', this.cacheControl);
      http.response.setHeader('Content-Disposition', `inline; filename="${file.name}"`);
    }
    return Boolean(gridFsFileId) // Serve file from either GridFS or FS if it wasn't uploaded yet
  },
  onAfterRemove (files) {
    files.forEach(file => {
      Object.keys(file.versions).forEach(versionName => {
        const gridFsFileId = (file.versions[ versionName ].meta || {}).gridFsFileId;
        if (gridFsFileId) {
          const gfsId = createObjectId({ gridFsFileId });
          imagesBucket.delete(gfsId, err => { if (err) console.error(err); })
        }
      })
    })
  }
})

if (Meteor.isClient) {
  Meteor.subscribe('files.images.all');
}

if (Meteor.isServer) {
  Meteor.publish('files.images.all', () => Images.collection.find({}));
}

export { Images }

What if you modified it like this

return {
			loading1: !subs1.ready(),
			images: Images.find({"name":{$regex:partOfFileName}}).fetch()
		}

@minhna, I was out of town for a few days without my gadgets. I only saw your reply now. I tried your suggestion and it works. Thanks for your help.

1 Like