DEV Community

Cover image for Fetching in Tandem
Julie Evans
Julie Evans

Posted on

Fetching in Tandem

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. 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
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
Enter fullscreen mode Exit fullscreen mode

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
onChange={(event, newValue) => {
  tags.filter( tag => {
    newValue.length > 0 ? (
      newValue.filter( value => {
         if(value === tag.name) {
          setSelectedTags([...selectedTags, tag])
        }
      } )
    ) : (
      setSelectedTags([])
    )
  } )
}}
Enter fullscreen mode Exit fullscreen mode

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
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()))
     })
   }
 })
}
Enter fullscreen mode Exit fullscreen mode

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
def create
  photo = @current_user.photos.create!(photo_params)
  render json: photo, status: :created
end
Enter fullscreen mode Exit fullscreen mode

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).

Top comments (0)