Fetching in Tandem
Tags: react, database, tutorial
Published: 2022-08-31 21:36:48

To start off let me first explain what I mean by fetching in tandem. Fetching is a process to access the data from a database or API with a *fetch request*. More about fetching can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). To have this process run in duality is what I mean for it to be in tandem. That is if I make a fetch request that requires either the data from another fetch request or at least the process to have run. In this particular case, I wanted to have a fetch request nested inside another, so that it would run right after the other within a single motion. So it’s not exactly simultaneous but in my case I had it run in a single handler function, so I consider that to be a single motion and thus in tandem.

Now to explain what I had going on in this example and how I got to that point, I will first explain my setup. My database was set up with a many-to-many relationship pertaining to three tables. These tables were a Photos table, a Tags table, and a PhotoTags table. The PhotoTags table is the join-table connecting the other two, with a foreign key column for each of the other respective tables.

For some reference here is what my schema looks like:

###### db/schema.rb

```swift
create_table "photos", force: :cascade do |t|
  t.string "image"
  t.text "description"
  t.integer "user_id"
end

create_table "tags", force: :cascade do |t|
  t.string "name"
end

create_table "photo_tags", force: :cascade do |t|
  t.integer "photo_id"
  t.integer "tag_id"
end
```

Going forward I will explain what I had going on. To start with this example involves a form page set up to add photos to the database. This form has three inputs: Image, Description, and Tags. Image is for the image URL. Description and Tags are optional. Description is for if the *user* wants to add some descriptive text to the *photo*. Tags is for adding and selecting *tags* from the database in order to have them attached to the *photo*. Adding *tags* to the database is handled by an `onKeyDown` event handler function, but more importantly, attaching those *tags* is handled by an `onChange` event handler function for selecting and then a POST fetch request for attaching them to the *photo*.

###### client/AddPhoto.js: onChange

```swift
onChange={(event, newValue) => {
  tags.filter( tag => {
    newValue.length > 0 ? (
      newValue.filter( value => {
         if(value === tag.name) {
          setSelectedTags([...selectedTags, tag])
        }
      } )
    ) : (
      setSelectedTags([])
    )
  } )
}}
```

This is my `onChange` handler for the *tags'* form input. Simply put, I check to see if there are any *tags* in the input and add them to a state variable called `selectedTags`. I checked if there was a *tag* added by seeing if the `newValue` prop was empty or not. I then matched the `newValue` with the `tags` from the database. Both `tags` and `newValue` are arrays, but the `tags` array is from the database and thus has *id*'s where as the `newValue` array only has the *name* value of the *tag*. I match these two arrays by filtering through both. I do this in order to grab the *tag* from the database that has the same *name* as the *tag* in the `newValue` array. Basically, I'm grabbing the *tag* with its *id* from the array displayed on the DOM. If they do match the comparison I set them to the `selectedTags` array using `useState`. This way the `selectedTags` array matches what the user sees on the DOM but also contains the *id*'s to help later with adding the association to the photo via the `photo_tags` Join table.

The harder part is adding that association, taking the *tags* that have been selected and attaching them to the *photo*. To do this requires a **POST fetch request** to the `photo_tags` Join table, for each *tag* to be attached to the *photo*. But first, we need to have *photo* to add these to. That isn't really a problem, except that the *photo* needs to be in the database and have an *id*. This is because the association between the *photos* and *tags* in the Join table are reliant on *id*'s as its foreign keys (look to the schema for clarification on this). This isn't a problem either, because I just need to run a POST fetch request to add the *photo* to the database and then run the POST fetch request to the Join table to attach the *tags*. The problem here lies in the fact that this is one form with one submit button. So in order to have the *tags* associated with the *photo* as well as the *photo* to be added to the database upon clicking the single submit button I need to run both POST fetch requests almost simultaneously. I do this by running the two fetch requests right after each other inside the submit handler function. This way they both initiate upon the submit button click, but I also control them so that the *photo*'s fetch request is run and with the return of the data from its completion to then run the `photo_tag`'s fetch request with that data.

Here is what my submit handler function looks like:

###### client/AddPhoto.js: handleSubmit

```swift
function handleSubmit(event) {
 event.preventDefault()

 fetch("/photos", {
   method: "POST",
   headers: {
     "Content-Type": "application/json"
   },
   body: JSON.stringify(formData)
 })
 .then((r) => {
   if (r.ok) {
     r.json().then((photo) => {
       if(selectedTags.length > 0) {
         selectedTags.map( tag => {
           fetch("/photo_tags", {
             method: "POST",
             headers: {
               "Content-Type": "application/json"
             },
             body: JSON.stringify({ photo_id: photo.id, tag_id: tag.id })
           })
           .then((r) => {
             if (r.ok) {
               r.json().then((photoTag) => {
                 console.log(photoTag)
               })
             }
             else {
               r.json().then((err) => setErrors(err.errors))
             }
           })
         })
       }
     }).then(() => {
       history.push(`/users/${currentUser.id}`)
     })
   } else {
     r.json().then((err) => {
       setErrors(err.errors.map((error) => error.toLowerCase()))
     })
   }
 })
}
```

The formData here is an object that contains the URL of the *image* and the *description* for the *photo*. These are added via `useState` from the other form inputs. I have errors set from both fetch requests, but since the `photo_tag` fetch request can't run without the *photo*'s *id* or the *tags*' *id*'s it never gets to that point. I also console logged the data from the `photo_tag` fetch request to confirm everything's working properly. I set the `photo_tag` fetch request to run after the *photo* fetch request by nesting it inside of its response, and it also will only run if *tags* have been selected (since adding *tags* to a *photo* is optional). If there are `selectedTags` it will iterate through them and run the `photo_tag` fetch request for each *tag*. Once all of the fetch requests have run it will then redirect to the user's page I have set up with the *id* of the *user* that is logged in (aka. the `currentUser`). On a side note, the addition of *photos* and *tags* also requires a *user* to be logged in. The `photo` table also has a `user_id` foreign key, but that is handled in the controller, here:

###### app/controllers/photos_controller.rb: create

```swift
def create
  photo = @current_user.photos.create!(photo_params)
  render json: photo, status: :created
end
```

In conclusion, it is not that I have fetches running simultaneously, but that I have them run one after the other by nesting them and having them within a single action (submit handler initiated upon button click action).