reactjssurveysurveyjs

Populate SurveyJS file upload with remote data


I'm using SurveyJS to create a multi step form for users to check their existing profile. The survey is pre-populated with values from an API, so the user can check those and submit changes. Therefore, I also want to populate file upload fields, but can't find a way to do that with remote files.

I'm aware, I can load default values with base64 encoded images, like so:

import { Model } from 'survey-core'
import { Survey } from 'survey-react-ui'
import './App.css'

const surveyJson = {
    elements: [{
        name: "file1",
        title: "Upload a file",
        allowMultiple: true,
        type: "file"
    }]
};

const App = () => {
    const survey = new Model(surveyJson);

    survey.mergeData({
        file1: [
            {
                name: 'file1.svg',
                type: 'image/svg+xml',
                content: 'data:image/svg+xml;base64,.....'
            }
        ]
    })

    return (
        <div style={{ minWidth: '500px', minHeight: '400px' }}>
            <Survey model={survey} />
        </div>
    )
}

export default App

But this only works once the survey is created and mounted. Since my survey consists of multiple pages and images in them, I would need to download every image and convert it to base64, before loading the survey, like:

useEffect(() => {
    fetch('https://api.example.com/person')
        .then(response => response.json())
        .then(data => {
            const person = data.person;

            person.files = person.files.map((file: any) => {
                // fetch file and convert to base64
                const base64 = "....";

                return {
                    name: file.name,
                    type: file.type,
                    content: base64
                }
            });

            survey.mergeData(person);
        });
}, []);

This seems quite ineffective to me. Maybe there is a more elegant solution to achieve this?


Solution

  • Appreciate you taking the time to nicely describe your question.

    You're correct that the current approach is suboptimal. Preloading all images before initializing SurveyJS can indeed cause unnecessary delays and UI thread blocking, especially in a multi-step form scenario and multiple images you need to process for the model.

    A more efficient approach would be to defer image loading until after the survey is initialized and to lazy load images as they are needed (about to become visible). This is particularly useful for multi-step forms, where users might take some time to navigate to the next step and your cases is a great fit for this approach.

    Fortunately, SurveyJS supports lazy rendering based on the viewport visibility, which can be enabled with the following setting: settings.lazyRender.enabled = true. This brings additional benefits as the lazy loading render speeds up the time to interactive for your form.

    Additionally, SurveyJS provides the onAfterRenderQuestion event, which fires after each individual question is rendered (documentation link). This event can be leveraged to lazy load images only when the respective file question is actually rendered. By using this event, you can assign the image value to the file question dynamically with the expression options.question.value = imageObj; //imageObj has the structure you have described {type: "...", content: "..."}.

    To demonstrate this approach, I’ve created a solution that you can explore.

    Plnkr Demo - https://plnkr.co/edit/RmubK3OLNEsS9FcJ

    Main code is here

    Survey.settings.lazyRender.enabled = true;
    
    function updateFileTemplate(_, options){
        if (!options.question || options.question.getType() != "file") return; // we only care for file questions
    
        const imageQuestion = options.question; // get the image question
        getRandomProfileImage()
            .then(profileImage => {
                imageQuestion.value = profileImage; // set the value
            })
            .catch(error => {
                console.error(`Error during loading ${imageQuestion.name}. Error is: ${error}`)
            });
    }
    
    const survey = new Survey.Model(surveyJSON);
    survey.onAfterRenderQuestion.add(updateFileTemplate); // attach the updateFileTemplate function as a `onAfterRenderQuestion` event handler
    
    

    Notes

    Things to consider

    Hope this helps!