This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Save Data to the Server with fetch

2:30 React lesson by

We’ll cover posting new data to the server using fetch and the POST http method. We’ll also update the UI with a success message once the save has completed successfully.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

We’ll cover posting new data to the server using fetch and the POST http method. We’ll also update the UI with a success message once the save has completed successfully.

Avatar
Frederik

Isn't it strange you update the state already, before the backend has acknowledged the data was saved successfully.
If there was an issue with saving the data, the frontend is out of sync with the backend. (Just for the sake of 'quick and slick UI-behaviour'?

In reply to egghead.io
Avatar
Andrew Van Slaars

Frederik,

It's a trade-off. You can do UI updates in an optimistic or pessimistic manner. For this video, I chose optimistic. The trade-off is that in the event of an issue on the server, you'd need to handle that reset and some kind of notification in the UI. In the event that your services and connection are reliable, that would be an edge case. It's a choice you have to make for your project depending on the nature of the data, the business requirements, the expectations of the customer, the network reliability and so on.

There are quite a few very successful applications in the wild that take the optimistic approach. They have far more sophisticated synching mechanisms to handle network interruptions than I could have worked into the scope of this course, but it's not an uncommon scenario.

I went back and forth on which direction I would go for this course and chose the optimistic update. You could very easily refactor this code to only update the UI after the server has responded.

I hope this answers your question.

In reply to Frederik
Avatar
Maria

Hi Andrew,

When I added .then(() => console.log('todo added')) to createTodo(newTodo) in App.js, this is what I got in the browser console:

localhost/:1 Uncaught (in promise) TypeError: Failed to fetch

And this is what I got in the iTerm2 console when I ran npm test:

(node:40332) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Network request failed
(node:40332) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I was able to add a new todo, but it did not persist since my network request failed.

And BTW, I am on node 7.9.0.

Oh and also, when I ran npm test, no error showed up in the actual code. Only the deprecation warning error:

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.886s, estimated 1s

What's interesting tho, before adding fetch, all the tests passed and showed up in the iTerm console as passing. Now only one test passes. But no errors are showing up.

Avatar
Andrew Van Slaars

Maria,

Are you running the json-server? It was covered in one of the videos, but the readme on the course repo has some instructions for setting it up.

This project relies on a local mocked server since building a true API layer with data persistence would have been far too out of scope and would have likely tripled the length of the course.

What you're seeing with the tests is likely related to the way Jest caches tests. It will try to run tests only where code has changed. When Jest is in watch mode, you can hit a on your keyboard in the terminal to get it to run all of the tests. The code with the fetch calls wasn't included in the tests, so it shouldn't be a factor there.

Hope this helps, and if you run into issues after setting up the server and getting a full test run, let me know and I'll try to help.

In reply to Maria
Avatar
Maria

Thanks Andrew! It didn't work initially when I tried to use both npm start in one window and json-server in the other, but now I am using 7.10.0 and it worked when I tried it again. Not saying one had to do with the other, but it has been rectified. Thanks!

In reply to Andrew Van Slaars

Our application is set up to loadTodos from a todos endpoint provided by json-server. Let's update this so we can save new todos to the server as well. In todoService.js, I'm going to export a new function which we'll call createTodo. This is going to accept a new todo. Then we'll use fetch to 'POST' that to the server.

Just like in the loadTodos function above, we're going to call fetch with our baseUrl and return the resulting promise. I'm start with a return statement and a call to fetch passing in baseUrl. By default, fetch will issue a GET request. In order to 'POST' to the server, we'll need to pass in some options.

todoService.js

export const createTodo = (todo) => {
    return fetch(baseUrl, {

    })
}

After baseUrl, I'm going to put in a second argument here that's going to be an object with all of our options. We'll start by defining the method, which is going to be 'POST'. Then we're going to need a couple of headers. I'm going to paste those in. We have an 'Accept' header for 'application/json' and also a 'Content-Type' header.

export const createTodo = (todo) => {
    return fetch(baseUrl, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
    })
}

Finally, we have to define the body of our 'POST' or the content that we want saved to the server. We'll define a body property here. We need to stringify our todo object. We're going to call JSON.stringify and pass in our todo. Like we did above, I'm going to call .then, take the response, and call the .json method on it. Now I can save that.

export const createTodo = (todo) => {
    return fetch(baseUrl, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(todo)
    }).then(res => res.json())
}

Now we move back into our App.js.
I'm going to update my import to also include the createTodo function that we created. Now that we have that, I'm going to come down to the handleSubmit method. We're adding our todo and updating our state. Now, I want to call createTodo. I want to pass that newTodo to the server. So we can confirm that this works, I'm going to add .then.
When I get a response back, I'm going log out to the console "Todo added".

App.js

handleSubmit = (evt) => {
    evt.preventDefault()
    const newId = generateId()
    const newTodo = {id: newId, name: this.state.currentTodo, isComplete: false}
    const updatedTodos = addTodo(this.state.todos, newTodo)
    this.setState({
        todos: updatedTodos,
        currentTodo: '',
        errorMessage: ''
    })
    createTodo(newTodo)
        .then(() => console.log('Todo added'))
}

I'll save this. Our browser will reload. I'll open up DevTools. Now, when I add a new todo, we'll see that our log shows Todo added.

If I do a full page reload, it will fetch our todos from the server, and it will include that new item that was just added. If we look at db.json, we'll see that we have this new item added with our generated "id", our "name" of "New Todo". Our default "isComplete" value.

db.json

{
    "id": 81718,
    "name": "New Todo",
    "isComplete": false
}
HEY, QUICK QUESTION!
Joel's Head
Why are we asking?