Build a Word Document Editor in React with Auto-Save to Amazon S3

syncfusionsyncfusion
14 min read

TL;DR: Auto open, edit, and save Word documents in React with Syncfusion’s Document Editor, integrating AWS S3 for secure storage. Explore S3 setup, ASP.NET Core API, and React components, including File Manager and toolbar customization, for seamless cloud document workflows.

Leveraging a reliable cloud storage solution significantly enhances document access, sharing, security, and performance in a React application. However, setting up and integrating cloud storage isn’t always straightforward; it requires careful configuration to ensure efficiency and security. In this blog, we will explore how to achieve seamless functionality using Amazon S3 (AWS) Storage.

With Syncfusion® React Document Editor, you can effortlessly open, edit, and auto-save Word documents while utilizing Amazon S3 (AWS) for secure file storage and retrieval.

We will examine key functionalities such as auto-save, application-level file management for Amazon S3 using Syncfusion® FileManager, and a customizable toolbar to enhance your document editing experience.

Setting Up Amazon S3(AWS)

Amazon S3 (Simple Storage Service) is a scalable, secure, and highly available cloud storage solution from AWS, ideal for web applications, backups, and content delivery.

Setting up Amazon S3 involves three key steps:

Step 1: Create an S3 Bucket

Sign in to the AWS Management Console and navigate to Amazon S3 via the AWS services menu. Click Create bucket, then provide a unique name, select a region, and configure the public access settings based on your requirements. Finally, click Create bucket to complete the process.

Step 2: Configure Bucket Permissions

Open your newly created S3 bucket and go to the Permissions tab. Configure the Bucket Policy to control read/write access and adjust CORS settings if necessary.

Step 3: Retrieve Access Credentials

Navigate to the IAM service and create a new IAM user with Programmatic access. Attach the AmazonS3FullAccess policy or customize permissions as needed. Save the Access Key ID and Secret Access Key for authentication in your application.

Setting up the ASP.NET Core backend (Server-Side)

Step 1: Create a new ASP.NET Core web API project

Create an ASP.NET Core Web API project using Visual Studio or the .NET CLI. Next, install the AWSSDK.S3 NuGet package to enable interaction with Amazon S3, which allows for file uploads, downloads, and storage management.

Step 2: Add a configuration entry in appsettings.json for Amazon S3

Add the Amazon S3 credentials and bucket details to the appsettings.json file. Include the Access Key, Secret Key, Bucket Name, and Region as shown below:

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AccessKey": "",
  "SecretKey": "",
  "BucketName": "",
  "AWS": {
    "Region": ""
  }
}

Step 3: Implement the Amazon S3 storage service

Create a service class, AmazonS3DocumentStorageService.cs, to handle file operations. This service includes two key methods:

  • FetchDocumentAsync: Retrieves a Word document from Amazon S3 storage.

  • UploadDocumentAsync: Saves and uploads a document file to Amazon S3 storage.

Note: The service class also includes additional methods for file management operations, which you can explore in the GitHub example project.

The following code examples demonstrate the key functionalities of file operations in AmazonS3DocumentStorageService.cs.

public class AmazonS3DocumentStorageService : IAmazonS3DocumentStorageService
{
    private readonly string _accessKey;
    private readonly string _secretKey;
    private readonly string _bucketName;
    private readonly RegionEndpoint _region;
    private readonly IAmazonS3 _s3Client;
    private readonly ILogger _logger;
    private readonly AmazonS3DocumentManager _fileProvider;
    private readonly string _rootFolderName;
    public string basePath;

    public AmazonS3DocumentStorageService(
        IAmazonS3 s3Client,
        IWebHostEnvironment hostingEnvironment,
        IConfiguration configuration,
        ILogger logger)
    {
        _s3Client = s3Client;
        _logger = logger;
        _accessKey = configuration["AccessKey"];
        _secretKey = configuration["SecretKey"];
        _bucketName = configuration["BucketName"];
        _region = RegionEndpoint.GetBySystemName(configuration["AWS:Region"]);

        // Folder name created inside the container
        _rootFolderName = "Files";

        // Initialize basePath from the hosting environment and clean it up
        basePath = hostingEnvironment.ContentRootPath;
        basePath = basePath.Replace("../", "");

        _fileProvider = new AmazonS3DocumentManager();
        _fileProvider.RegisterAmazonS3(_bucketName, _accessKey, _secretKey, _region.SystemName);
    }

    ///
    /// Loads a document from Amazon S3 and returns the serialized document.
    /// 
    ///The name of the document to load.
    /// An IActionResult containing the serialized document if successful, or an error status code.
    public async Task FetchDocumentAsync(string documentName)
    {
        try
        {
            // Create a new S3 client for this operation.
            using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region);
            using var stream = new MemoryStream();

            // Get the document object from the S3 bucket.
            var response = await s3Client.GetObjectAsync(_bucketName, $"{_rootFolderName}/{documentName}");
            await response.ResponseStream.CopyToAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);

            // Load the document using Syncfusion's WordDocument loader.
            var document = WordDocument.Load(stream, FormatType.Docx);

            // Serialize the document to JSON format.
            return new OkObjectResult(JsonConvert.SerializeObject(document));
        }
        catch (AmazonS3Exception ex)
        {
            _logger.LogError(ex, "S3 error loading document");
            return new StatusCodeResult(500);
        }
    }

    ///
    /// Saves a document to Amazon S3 using the provided form file.
    /// 
    ///The uploaded form file to be saved.
    ///The name under which the document will be stored in S3.
    /// An IActionResult indicating whether the save operation was successful.
    public async Task UploadDocumentAsync(IFormFile file, string documentName)
    {
        try
        {
            // Create a new S3 client instance to upload the document.
            using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region);
            using var stream = new MemoryStream();

            // Copy the uploaded file content to a memory stream.
            await file.CopyToAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);

            // Construct the Put object request with the necessary details.
            var request = new PutObjectRequest
            {
                BucketName = _bucketName,
                Key = $"{_rootFolderName}/{documentName}",
                InputStream = stream
            };

            // Upload the file to the S3 bucket.
            await s3Client.PutObjectAsync(request);
            return new OkResult();
        }
        catch (AmazonS3Exception ex)
        {
            _logger.LogError(ex, "S3 error saving document");
            return new StatusCodeResult(500);
        }
    }
}

Step 4: Create API Endpoints in a controller

Create an API controller ( Controllers/AmazonS3DocumentStorageController.cs ) to handle file operations. This controller includes two key methods that internally invoke the corresponding service methods:

  • FetchDocument: Invokes the service method to retrieve a Word document from Amazon S3 storage.

  • UploadDocument: Invokes the service method to save and upload a document file to Amazon S3 Storage.

Note: This controller also contains an additional file operations method. For more details, please refer to the GitHub example project.

The code example below demonstrates the key controller methods for handling file operations in AmazonS3DocumentStorageController.cs.

/// <summary>
/// Controller for handling AWS file operations and document management
/// </summary>
[Route("api/[controller]")]
[EnableCors("AllowAllOrigins")]
public class AmazonS3DocumentStorageController : ControllerBase
{
    private readonly IAmazonS3DocumentStorageService _documentStorageService;

    /// <summary>
    /// Constructor injecting the file provider service dependency.
    /// </summary>
    /// <param name="documentStorageService">Service for performing file operations</param>
    public AmazonS3DocumentStorageController(IAmazonS3DocumentStorageService documentStorageService)
    {
        _documentStorageService = documentStorageService;
    }

    /// <summary>
    /// Retrieves a document from Amazon S3 storage in JSON format.
    /// </summary>
    /// <param name="args">File operation parameters including path and action type</param>
    /// <returns>Result of the file operation</returns>
    [HttpPost("FetchDocument")]
    public async Task FetchDocument([FromBody] Dictionary<string, string=""> jsonObject)
    {
        if (!jsonObject.TryGetValue("documentName", out var docName))
            return BadRequest("Document name required");

        return await _documentStorageService.FetchDocumentAsync(docName);
    }

    /// <summary>
    /// Saves uploaded document to Amazon S3 storage.
    /// </summary>
    /// <param name="data">Form data containing file and document name</param>
    /// <returns>Action result indicating success or failure.</returns>
    [HttpPost("UploadDocument")]
    public async Task UploadDocument([FromForm] IFormCollection data)
    {
        if (data.Files.Count == 0)
            return BadRequest("No file provided");

        var documentName = data.TryGetValue("documentName", out var values) && values.Count > 0 ? values[0] : string.Empty;
        return await _documentStorageService.UploadDocumentAsync(data.Files[0], documentName);
    }
}</string,>

Setting up React Frontend Client-Side

Step 1: Create a React App and add dependencies

Create a React app and integrate the Syncfusion® components, Document Editor, and File Manager, to interact with Amazon S3 storage. This integration enables file uploads, downloads, and storage management within your application.

  • Document Editor ( Word Processor ): Opens, edits, and auto-saves documents.

  • File Manager interactively browses and manages files stored in Amazon S3 Blob Storage.

Step 2: Implement File Manager operations

Create a new TypeScript JSX file named AmazonS3FileManager.tsx to implement the File Manager, allowing users to display, browse, and manage files stored in Amazon S3. The example code below demonstrates this implementation:

import * as React from 'react'; 
import {
    FileManagerComponent,
    Inject,
    NavigationPane,
    DetailsView,
    Toolbar
} from '@syncfusion/ej2-react-filemanager';
import { DialogComponent } from '@syncfusion/ej2-react-popups';

interface AmazonS3Props {
    // Callback function triggered when a file is selected in the file manager
    onFileSelect: (filePath: string, fileType: string, fileName: string) => void;
}

const AmazonS3FileManager: React.FC<AmazonS3Props> = ({ onFileSelect }) => {
    // Base URL for backend API handling Azure file operations
    const hostUrl: string = "http://localhost:62869/";
    // State management for file manager dialog visibility
    const [showFileManager, setShowFileManager] = React.useState(true);
    // Reference to access the FileManager component methods
    let fileManagerRef = React.useRef<FileManagerComponent>(null);

    // Shows the file manager when the open button is clicked and clears the previous selection item
    const handleOpenButtonClick = () => {
        // Clear the previous selection
        if (fileManagerRef.current) {
            fileManagerRef.current.clearSelection();
            setTimeout(() => {
                fileManagerRef.current.refreshFiles();
            }, 100);
        }
        setShowFileManager(true);
    };

    // Handles file open event from file manager
    const handleFileOpen = (args: any) => {
        if (args.fileDetails.isFile) {
            const selectedPath = args.fileDetails.path || args.fileDetails.filterPath + args.fileDetails.name;
            const fileType = args.fileDetails.type;
            const fileName = args.fileDetails.name;
            onFileSelect(selectedPath, fileType, fileName); // Pass the file path and file type to load in the Document Editor
            setShowFileManager(false); // Close the File Manager Dialog
        }
    };

    return (
        <div>
            <button id="openAmazonS3BucketStorage" onClick={handleOpenButtonClick}>
                Open AWS file manager
            </button>

            {/* File Manager Dialog */}
            <DialogComponent
                id="dialog-component-sample"
                header="File Manager"
                visible={showFileManager}
                width="80%"
                height="80%"
                showCloseIcon={true}
                closeOnEscape={true}
                target="body"
                beforeClose={() => setShowFileManager(false)}
                onClose={() => setShowFileManager(false)} // Close the dialog when closed
            >
                <FileManagerComponent
                    id="aws-file"
                    ref={fileManagerRef}
                    ajaxSettings={{
                        url: hostUrl + 'api/AmazonS3DocumentStorage/ManageDocument',
                        downloadUrl: hostUrl + 'api/AmazonS3DocumentStorage/DownloadDocument',
                    }}
                    toolbarSettings={{
                        items: ['SortBy', 'Copy', 'Paste', 'Delete', 'Refresh', 'Download', 'Selection', 'View', 'Details']
                    }}
                    contextMenuSettings={{
                        file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'],
                        layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'],
                        visible: true
                    }}
                    fileOpen={handleFileOpen} // Attach the fileOpen event
                >
                    <Inject services={[NavigationPane, DetailsView, Toolbar]} />
                </FileManagerComponent>
            </DialogComponent>
        </div>
    );
};

export default AmazonS3FileManager;

Step 3: Implement Document Editor operations

Create a new TypeScript JSX file named DocumentEditor.tsx to implement the Document Editor functionalities, which include creating, opening, editing, and saving documents.

a. Custom toolbar options: Import the necessary dependencies and add the following custom toolbar items:

  • New – Creates a new document.

  • Open – Displays the File Manager to load a selected document.

  • Download – Download the current document locally.

import React, { useRef, useState } from 'react';
import {
    CustomToolbarItemModel,
    DocumentEditorContainerComponent,
    Toolbar
} from '@syncfusion/ej2-react-documenteditor';
import AmazonS3FileManager from './AmazonS3FileManager.tsx';
import { ClickEventArgs } from '@syncfusion/ej2-navigations/src/toolbar/toolbar';
import { DialogUtility } from '@syncfusion/ej2-react-popups';

DocumentEditorContainerComponent.Inject(Toolbar);

function DocumentEditor() {
    // Backend API host URL for document operations
    const hostUrl: string = "http://localhost:62869/";
    // Reference to document editor container component
    const containerRef = useRef<DocumentEditorContainerComponent>(null);
    // Reference for the dialog component
    let dialogObj: any;
    // State to hold the current document name
    const [currentDocName, setCurrentDocName] = useState<string>('None');
    // Track document modifications for auto-save functionality
    const contentChanged = React.useRef(false);

    // Custom toolbar button configuration for "New" document
    const newToolItem: CustomToolbarItemModel = {
        prefixIcon: "e-de-ctnr-new",
        tooltipText: "New",
        text: "New",
        id: "CustomNew"
    };

    // Custom toolbar button configuration for opening the Amazon S3 file manager
    const openToolItem: CustomToolbarItemModel = {
        prefixIcon: "e-de-ctnr-open",
        tooltipText: "Open Amazon S3 file manager",
        text: "Open",
        id: "OpenAmazonS3FileManager"
    };

    // Custom toolbar button configuration for downloading the document
    const downloadToolItem: CustomToolbarItemModel = {
        prefixIcon: "e-de-ctnr-download",
        tooltipText: "Download",
        text: "Download",
        id: "DownloadToLocal"
    };

    // Customize the SystemClipboard API name
    let settings = { systemClipboard: 'ProcessClipboardContent' };

    // Combined toolbar items including custom buttons and built-in features
    const toolbarItems = [
        newToolItem,
        openToolItem,
        downloadToolItem,
        'Separator',
        'Undo',
        'Redo',
        'Separator',
        'Image',
        'Table',
        'Hyperlink',
        'Bookmark',
        'TableOfContents',
        'Separator',
        'Header',
        'Footer',
        'PageSetup',
        'PageNumber',
        'Break',
        'InsertFootnote',
        'InsertEndnote',
        'Separator',
        'Find',
        'Separator',
        'Comments',
        'TrackChanges',
        'Separator',
        'LocalClipboard',
        'RestrictEditing',
        'Separator',
        'FormFields',
        'UpdateFields',
        'ContentControl'
    ];

    return (
        <div>
            <div>
                <AmazonS3FileManager onFileSelect={loadFileFromFileManager} />
            </div>
            <div id="document-editor-div" style={{ display: "block" }}>
                <div id="document-header">
                    {currentDocName || 'None'}
                </div>
                <DocumentEditorContainerComponent
                    ref={containerRef}
                    id="container"
                    height={'650px'}
                    serviceUrl={hostUrl + "api/AmazonS3DocumentStorage/"}
                    enableToolbar={true}
                    toolbarItems={toolbarItems}
                    toolbarClick={handleToolbarClick}
                    contentChange={handleContentChange} // Listen to content changes
                    serverActionSettings={settings}
                />
            </div>
        </div>
    );
}

export default DocumentEditor;

b. Open a Document from the File Manager (Amazon S3 Storage)

// Callback function to load the file selected in the file manager
const loadFileFromFileManager = (filePath: string, fileType: string, fileName: string): void => {
    if (!containerRef.current) {
        console.error('Document Editor is not loaded yet.');
        return;
    }
    containerRef.current.documentEditor.documentName = fileName;

    // Update state with the current document name
    setCurrentDocName(fileName);

    if (fileType === '.docx' || fileType === '.doc' || fileType === '.txt' || fileType === '.rtf') {
        // Handle document files
        fetch(hostUrl + 'api/AmazonS3DocumentStorage/FetchDocument', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json;charset=UTF-8' },
            body: JSON.stringify({ documentName: fileName })
        })
        .then(response => {
            if (response.status === 200 || response.status === 304) {
                return response.json();
            } else {
                throw new Error('Error loading document');
            }
        })
        .then(json => {
            const documentEditorDiv = document.getElementById("document-editor-div");
            if (documentEditorDiv) {
                documentEditorDiv.style.display = "block";
            }
            // Open the document using the JSON data received
            containerRef.current.documentEditor.open(JSON.stringify(json));
        })
        .catch(error => {
            console.error('Error loading document:', error);
        });
    } else {
        alert('The selected file type is not supported for the document editor.');
    }
};

c. Automatically save the document to Amazon S3 storage: The edited document will be automatically saved every 1000 milliseconds.

// Automatically saves document to Amazon S3 storage
const autoSaveDocument = async (): Promise<void> => {
    if (!containerRef.current) return;
    try {
        // Save as Blob using Docx format
        const blob: Blob = await containerRef.current.documentEditor.saveAsBlob('Docx');
        let exportedDocument = blob;
        let formData: FormData = new FormData();
        formData.append('documentName', containerRef.current.documentEditor.documentName);
        formData.append('data', exportedDocument);
        let req = new XMLHttpRequest();
        // Send document to backend API for Amazon S3 storage
        req.open(
            'POST',
            hostUrl + 'api/AmazonS3DocumentStorage/UploadDocument',
            true
        );
        req.onreadystatechange = () => {
            if (req.readyState === 4 && (req.status === 200 || req.status === 304)) {
                // Auto save completed
                // Success handler can be added here if needed
            }
        };
        req.send(formData);
    }
    catch (error) {
        console.error('Error saving document:', error);
    }
};

// Runs auto-save every second when content changes are detected
React.useEffect(() => {
    const intervalId = setInterval(() => {
        if (contentChanged.current) {
            autoSaveDocument();
            contentChanged.current = false;
        }
    }, 1000);
    return () => clearInterval(intervalId);
});

// Handles document content change detection
const handleContentChange = (): void => {
    contentChanged.current = true; // Set the ref's current value
};

d. Download a copy of the document to local storage: When you click the Download button in the toolbar, a copy of the document currently open in the editor will be downloaded or saved to the local storage.

// Handles document editor toolbar button click events
    const handleToolbarClick = async (args: ClickEventArgs): Promise<void> => {
        // Get a reference to the file manager open button
        const openButton = document.getElementById('openAmazonS3BucketStorage');
        // Get the current document name from the editor
        let documentName = containerRef.current?.documentEditor.documentName || 'Untitled';
        // Remove any extension from the document name using regex
        const baseDocName = documentName.replace(/\.[^/.]+$/, '');
        // Always check if containerRef.current exists before using it
        if (!containerRef.current) return;

        switch (args.item.id) {
            case 'OpenAmazonS3FileManager':
                // Programmatically trigger Amazon S3 file manager
                if (openButton) {
                    // Save the changes before opening a new document
                    await autoSaveDocument();
                    openButton.click();
                    // Sets the focus to the document editor within the current container reference
                    containerRef.current.documentEditor.focusIn();
                }
                break;

            case 'DownloadToLocal':
                // Initiate client-side download
                containerRef.current.documentEditor.save(baseDocName, 'Docx');
                // Sets the focus to the document editor within the current container reference
                containerRef.current.documentEditor.focusIn();
                break;

            case 'CustomNew':
                // If dialogObj exists, show the dialog; otherwise, prompt for a file name.
                if (dialogObj) {
                    dialogObj.show(); // Display the dialog.
                } else {
                    showFileNamePrompt(); // Prompt the user for a file name.
                }
                break;

            default:
                break;
        }
    };

e. Create and upload a new document: When the New toolbar item is clicked, a prompt dialog appears requesting the document name. Once confirmed, the document opens in the Word Document Editor and is automatically saved and uploaded to Amazon S3 storage within seconds.

// Prompt dialog for entering a new document filename
const showFileNamePrompt = (errorMessage?: string) => {
    const randomDefaultName = getRandomDefaultName();
    dialogObj = DialogUtility.confirm({
        title: 'New Document',
        width: '350px',
        cssClass: 'custom-dialog-prompt',
        content: `
            <p>Enter document name:</p> 
            <div id="errorContainer" style="color: red; margin-top: 4px;">
            ${errorMessage ? errorMessage : ''}
            </div>
            <input id="inputEle" type="text" class="e-input" value="${randomDefaultName}"/>
        `,
        okButton: { click: handleFileNamePromptOk },
        cancelButton: { click: handleFileNamePromptCancel },
    });

    // After the dialog is rendered, focus and select the input text.
    setTimeout(() => {
        const input = document.getElementById("inputEle") as HTMLInputElement;
        if (input) {
            input.focus();
            input.select();
        }
    }, 100);

    dialogObj.close = () => {
        setTimeout(() => {
            // Sets the focus to the document editor within the current container reference
            containerRef.current.documentEditor.focusIn();
        }, 100);
    };
};

// Handler for the OK button in the file name prompt dialog with file existence check and save 
// The new file will be automatically saved to Azure Storage by the auto-save functionality, which is managed within the setInterval method. 
const handleFileNamePromptOk = async () => {
    const inputElement = document.getElementById("inputEle") as HTMLInputElement;
    let userFilename = inputElement?.value.trim() || "Untitled";
    const baseFilename = `${userFilename}.docx`;

    // Check if the document already exists on the backend
    const exists = await checkDocumentExistence(baseFilename);
    if (exists) {
        // If the document exists, display an error message in the dialog
        const errorContainer = document.getElementById("errorContainer");
        if (errorContainer) {
            errorContainer.innerHTML = 'Document already exists. Please choose a different name.';
        }
        // Re-focus the input for correction
        if (inputElement) {
            inputElement.focus();
            inputElement.select();
        }
        return;
    }

    // Proceed with new document
    if (dialogObj) dialogObj.hide();
    containerRef.current.documentEditor.documentName = baseFilename;
    setCurrentDocName(baseFilename);
    containerRef.current.documentEditor.openBlank();
};

// Handler for the Cancel button in the prompt dialog
const handleFileNamePromptCancel = () => {
    if (dialogObj) {
        dialogObj.hide();
    }
};

Running the projects

To run the project, launch the server-side ASP.NET Core API and start the client-side React application to test its functionality. The results of the file operations performed on the client side will be displayed as shown in the image below.

<alt-text>

GitHub reference

For a complete project, refer to the GitHub demo.

Conclusion

Thanks for reading! In this blog, we have explored how to efficiently open, edit, and auto-save Word documents in Amazon S3 (AWS) Storage using Syncfusion®’s Document Editor and File Manager within a React application. By following these steps, you can create a seamless and secure document management system, enabling users to work with Word documents directly in the cloud.

Whether you choose Amazon S3, Azure Blob Storage, or any other cloud storage, Syncfusion®’s Document Editor provides a scalable and reliable solution for document handling based on your application’s needs.

The new version is available for current customers to download from the License and Downloads page. If you are not a Syncfusion customer, you can try our 30-day free trial for our newest features.

You can also contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

See Also

0
Subscribe to my newsletter

Read articles from syncfusion directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

syncfusion
syncfusion

Syncfusion provides third-party UI components for React, Vue, Angular, JavaScript, Blazor, .NET MAUI, ASP.NET MVC, Core, WinForms, WPF, UWP and Xamarin.