Back End Server

App.jsx

The full survey builder application

In these notes I am going to complete the application that allows users to create surveys and post them to the server.

Here is what the final result will look like:

There are three divisions here separated by horizontal lines. The middle division is where we will type the prompts and the choices for our questions. Clicking the Add Question button will add a new question to the list of survey questions displayed in the top div.

The survey questions displayed in the top div will have a Remove button associated with them. Clicking this remove button will remove that question from the survey we are building.

Once the user has created all of the questions they want, they can fill in additional details associated with the survey in the bottom div and then click the Upload Survey button to upload the survey and its questions to the server. Once this is done the application will display a confirmation message showing the id number for the survey on the back end server.

The back end server

The application we are about to build will interact with a back end server that will store the survey questions we create and eventually serve them up to users. There is a button at the top of these notes that you can use to download the NetBeans project for the back end server. Inside that project folder you will find a database folder containing the files for the database the server uses. To install the database in MySQL start by making a new empty schema with the name 'survey', and then import the database files to set up the database structure.

State variables in the App component

The App component for this example will contain three state variables. The questions variable will contain a list of question objects. I am going to assign a unique key value to each question I create: to help manage that process I added an index state variable that stores the next key value that will be assigned to a question. To keep track of whether or not we have uploaded the questions to the server I will also maintain an uploadDone state variable.

Managing the list of survey questions

The top div shows the questions we have created so far in our survey. I will be using the Question component I developed in the previous set of lecture notes to display those questions. In the applicaiton that builds the survey I am going to rename these components QuestionPreview, since for the moment all we want is a component that will show us what the question will look like on the survey. To enable the user to remove questions they don't want from the survey, I am going to embed each question in a special Remover component that displays the question along with a Remove button that will allow you to remove that question. To make it easy to identify which question to remove when the user clicks one of the remove buttons, I am going to add an extra key property to each question object in my list of questions.

The Remover component will wrap each question q we display in the list of questions using an arrangement like this:

<Remover key={q.id} doRemove={remove}>
  <QuestionPreview prompt={q.question} choices={q.responses} />
</Remover>

The remover will supply a Remove button that the user can click to remove the Question that Remover contains.

Here is the code for the Remover component:

function Remover({key,children,doRemove}) {
  const action = () => doRemove(key);
  return (
    <>
    {children}
    <button onClick={action}>Remove</button>
    </>
  )
}

An interesting aspect of this component is that it does not know or assume anything about the component or components it wraps: React will pass those components to the Remover automatically via its children property, and the Remover will render its children when it renders its own content to HTML. Likewise, the Remover does not know how to carry out the removal action; instead, we pass the Remover a function in its doRemove property and the Remover will call that function to do the actual removal when the user clicks the Remove button. All of these design choices make the Remover component completely generic and reusable - this makes it easy to reuse this component in other projects.

Adding new questions

The main purpose of this application is to allow the user to construct a set of questions for a survey. To allow the user to enter a new question we have a QuestionMaker component that displays a pair of text input fields where the user can enter a prompt and a set of choices for a new question:

function QuestionMaker({handleNew}) {
  let promptInput = useRef();
  let choicesInput = useRef();

  const action = (e) => { handleNew({question:promptInput.current.value,responses:choicesInput.current.value}); }
  return (
    <div>
      <p>Question prompt: <input type="text" ref={promptInput} /></p>
      <p>Question choices: <input type="text" ref={choicesInput} /></p>
      <p><button onClick={action}>Add Question</button></p>
    </div>
  )
}

To allow the QuestionMaker to read the values of the two text input fields we use the React reference mechanism: this involves creating a reference variable for each input field via a call to the useRef() hook. We pass this reference variable to the input field via a special ref attribute. When it comes time to read the value of the input field we get the value via the reference variable's current.value property.

The QuestionMaker takes a single property, handleNew, which is a function it will invoke when the user clicks the Add Question button to create a new question. The QuestionMaker assumes that the handleNew function takes a single parameter, which is new question object.

The App component will include a QuestionMaker component:

<QuestionMaker handleNew={newQuestionAction} />

The newQuestionAction we pass to the QuestionMaker handles adding the new question to App's questions property:

const newQuestionAction = (newQ) => { newQ.key = index;setQuestions([...questions,newQ]);incrementIndex();}

Uploading the survey and its questions

When the user has completed the list of questions for a survey they will want to upload the survey to the back end server. The SurveyPoster component handles that process:

function SurveyPoster({handlePost}) {
  let titleInput = useRef();
  let promptInput = useRef();
  
  const action = (e) => { handlePost({title:titleInput.current.value,prompt:promptInput.current.value}); }
  return (
    <div>
      <p>Survey Title: <input type="text" ref={titleInput} /></p>
      <p>Survey Prompt: <input type="text" ref={promptInput} /></p>
      <p><button onClick={action}>Upload Survey</button></p>
    </div>
  )
}

The back end server expects us to start the upload process by uploading some basic information about the survey. Each survey is expected to have a title and a prompt, so the SurveyPoster creates an object containing that information and passes it to a handlePost() function we will pass it as a property.

<SurveyPoster handlePost={uploadSurvey}/>

The uploadSurvey() function posts that object to the server.

function uploadSurvey(toPost) {
  fetch('http://localhost:8085/surveys', {
    method: 'POST',
    body: JSON.stringify(toPost),
    headers: {
      'Content-type': 'application/json; charset=UTF-8'
    }
  }).then(response => response.json()).then(response => uploadQuestions(response));
}

The server will respond by sending back an id number for the newly created survey. We will pass that id number ot the uploadQuestions() function, which will handle posting the individual questions to the server:

function uploadQuestions(id) {
  questions.map(q=>{q.survey = id;uploadQuestion(q);});
}

function uploadQuestion(q) {
  fetch('http://localhost:8085/questions', {
    method: 'POST',
    body: JSON.stringify(q),
    headers: {
      'Content-type': 'application/json; charset=UTF-8'
    }
  }).then(response=>incrementUploaded());
}

const incrementUploaded = () => {uploaded++;if(uploaded == questions.length) setUploadDone(true); }