Series::Building Drag and Drop Dynamic App

Iryna Stein
10 min readOct 6, 2021

BLOG#1::App Architecture Strategies

DREAM-CREATE-RECEIVE (water element component)

After getting some experience in coding I began to realize that my passion lays within user driven and highly interactive apps. I built a couple of games in the past, my first one was a take on a famous card game Black Jack. And mostly recent app was small and cute tamagotchi game where users can create and take care of their virtual pets (GitHub repo: frontend || backend)

So this time I wanted to take interactive application one step further and built something that can shape shift depending on what user does on the screen. And that is how an idea of a customizable vision board was born. Along this project I also decided to create a series of blog posts that will walk readers through some of the most challenging coding moments, thought process and new tricks I had to learn to make this app possible. I hope you enjoy this journey with me and lets dive in!

The full series consists of a total of 4 blogs which support development process of a full stack vision board application using React/Redux and Ruby on Rails (GitHub repo: frontend || backend). In short, this app allows a user to create a custom vision board using some pre-loaded elements (like stickers and inspirational quotes), custom uploaded elements (like user photos) and write affirmations. All elements can be dynamically positioned on the board using drag and drop functionality. At the end of the session user can either save the board and come back to it later or download it as a JPEG to share with the world.

This is the first blog in the series and it is focused more on theoretical decision making process and things to consider when choosing your tools and deciding on the architecture of your application.

I would like to start this discussion with the backend, as it is how I usually tend to layout my app: back to front. One of super helpful tools I like to use when planning out my Active Record models, tables and relationships is the dbdiagram.io. When your app is growing and becoming more complex it is very important to be able to keep track of any possible scenarios in mind and potential relationships between models. Sometimes it’s hard to do so until you have a diagram in front of you. While creating relationships and tables make sure to ask yourself a lot of questions: what element and models are you planning for in your app and how they will interact with each other; which relationships they will have with a user and a board itself; do the elements rely on each others existence; which elements are required and which ones are going to be flexible etc.?

Here are a few questions you should absolutely ask yourself before creating your database:

  • If my element is draggable (movable, re-shapable etc) do I have a place in my table to store these values? Coordinates, size, text color and any other little attributes that make that element so special to our users.
  • If you are pre-seeding some elements into your app (stickers, quotes etc) do you have a way of identifying them as initial references? This is a very important moment where I want you to pause and take a look at your model behavior. When you modify properties of an existing element make sure you do so on a separate “copy” that belongs specifically to the board (canvas, card, workbench or gallery) otherwise if you have multiple users and you don’t initialize a new instance of a class each time your user creates a new element from a “reference” you will end up modifying the attributes of an original instance which will become a problem when another user would want to reach for that element in your database. Take advantage of many_through relationships and join tables for such situations.
stickers = Sticker.where("init=?", true).where('category=?', board_params[:category])
board_stickers = stickers.map {|sticker| Sticker.create(name: sticker[:name], category: sticker[:category], coordinates: "x: 0, y: 0", image_url: sticker[:image_url], init: false)}

(When initiating a new board I also created new instances of a Sticker Class)

  • If you are planning on using uploadable images feature, will you allow users to utilize the use of the same collection across different boards (canvases) or do they have to re-upload images every time they initialize a new instance of your “board” class… The reason you should give this a thought is because this feature will dictate how you process and store your Active Storage data on the backend. There are several ways you can approach this: you could either store image in a separate table, or if you are planning on modifying original images you will need to work with the Active Storage auto generated “active_storage_blobs” table, ActiveRecordAttachments class and perhaps variants via image-processing gem (I will talk a little later about all these things).
  • And one of the last questions is the structure of your serializers and response rendering. Which particular things you would want your backend to send every time a request comes in. Will your database responses contain ALL of the information and your entire element collection within initial load or will these elements be returned on specific events and via separate calls?

You might think that the above questions are very basic and obvious, but trust me — these were the biggest gotchas once I started going through the process and I would like for you to be better prepared on your new journey than I was.

Just as you probably already noticed — I won’t be walking your step by step through an entire app set up and I also expect you have a good working knowledge of Ruby on Rails and React already. But I do want to bring attention to the hidden difficulties you might encounter as a developer.

Okay, now that we covered our basis, let’s review some of the potential challenges.

  1. Active Storage attachments, blobs and variants

As mentioned before — there are two ways you could potentially use active storage attachments.

In the first approach, you could use them as references for the Picture class. Technically the basic idea here is that when a user uploads an image it becomes part of a “collection” and the user can “call” any element of the collection and turn it into a Picture class instance which is editable, easily manipulated, duplicated, resized and stored without any extra efforts. This also allows us to keep our referenced images unmodified and repurposed for future references. I find it useful too when thinking about going back to an original state of a picture as well — we are making no changes to the originals. However, this will probably take up more storage in a long run.

In the other approach, you will have to work directly with Active Storage “active_storage_blobs” and “active_storage_variant_records” — the first one being an auto generated table created after running rails active_storage:install and the second is brought to you by the image-processing gem. Here is an article that I think gives an amazing explanation of the use of blob and variants (note: example is a full stack Rails App).

One last thing I wanted to add, if you are planning on uploading multiple images via Active Storage and do not want them to over-write the ones you have uploaded earlier, make sure to add this line of code to your environment files (development.rb, production.rb and test.rb):

config.active_storage.replace_on_assign_to_many = false
  1. Active Record strong params and custom methods

Another challenge that I faced in creating the app had to do with dynamic updates sent to the backend. When creating games or interactive apps you might have to make API calls a lot more frequent. Sometimes you probably even set your “save/update” calls on a timer or attach them to event listeners that might not look like an API call in your UI. For this reason, you probably have to plan for a very flexible “update” method. You also might be facing complex and nested data structures coming into your backend: arrays of objects each containing many attributes probably being the most common. So my advice here is to really pay attention to the strong params structure. If you are sending an array of user post objects each of which will have updated attributes, make sure to specify your permitted params in greater detail to avoid unwanted and unexpected rejections.

An example of what your permitted params might look like:

def board_params
params.permit(:name, :category, :user_id, :quote_id, :id,
:frames => [:filename, :byte_size, :id, :url, :coordinates, :old_id],
:posts => [:id, :paragraph, :category, :coordinates],
quote: {},
:stickers => [:id, :name, :category, :image_url, :coordinates, :init],
images: [])

And here is a nice quick read about accepting nested params as well.

Lastly, as you can imagine the built-in update or create macros might simply not be sufficient for when initiating a new layout for the vision board (canvas) or when making dynamic changes. This is a good time to take advantage of a build macro and conditional updates via custom methods. Custom methods can be easily set up inside your models which helps keep controllers clean and manageable. I find the conditional statements in combination with .nil? method the most efficient for performing custom updates.

So this pretty much wraps up the backend pointers for someone who is attempting to develop an app similar to this custom vision board. Lets take a look at the front-end now.

3. Single source of truth and global state management

Some of the first and most important things you think about when designing any React application are of course your state and the component structure. Typically I start by drawing very detailed wire frames where I specify not only the transitions and behaviors but also API calls, state handlers and so on. You can draw frames on a piece of paper or use Figma website which is becoming more and more popular in software engineering community. My detailed approach was very true until I just recently discovered Redux and started getting more relaxed about the component hierarchy. As any new developer would agree, when you learn more efficient tools that make your life easier you just move on and do not look back at your old ways… until it’s time to go back to basics in order to solve some complicated problems. And that is exactly what happened to me and I would like to take this opportunity to emphasize the importance of designing a mindful architecture for your components regardless of the global state management tools you use (I know you will have to use them in some ways, otherwise it will be an utter prop drilling nightmare). Whether you are choosing useContext or Redux as your poison, I would like to encourage you to spend some good time thinking about components re-rendering every time your state changes. Especially if you are using async functions or Thunks. Are all essential components protected against data loss when it comes to re-rendering? Think about the hard refresh of your browser — what’s being cleared, which undefined values are going to effect your front end behaviors?

One of the most essential points I would like to make is when working with state, and ESPECIALLY globally, always put a SINGLE SOURCE OF TRUTH concepts as an absolute priority and a Guiding Star of your journey.

4. Component structure — small manageable pieces.

Another thing that would be potentially very helpful when designing a complex and dynamic app is keeping your components small. You probably already heard that it’s a great practice to keep your React Components short and they should generally be no longer than 50 lines. While it’s easy to get carried away — think of your components as functions and follow the same principles you would when writing a good manageable function. I often look back at Robert Martin’s classic Clean Code highlights:

  • Small
  • Does one thing
  • One level of abstraction
  • Less than three arguments
  • Descriptive names

5. And that technically brings me to the last point I wanted to make in this App pre-work blog — the usage of custom hooks.

The idea of writing custom hooks could be extremely intimidating but ONLY if you have never written one. Once you realize what custom hooks are you won’t stop. I am personally a huge proponent of re-using, re-cycling and re-purposing in real life and in coding as well. Custom hooks are technically functions that help you incapsulate hook related logic and make it shareable across all your app components. As a matter of fact, you have already probably written a custom hook without knowing it — you just didn’t call it a hook and treated it a simple function or a helper component. I know I have done it in a past. And once I realized the easy and clean power of custom hooks I went back and re-wrote my old apps as well. With that said, when working with a dynamic app like a vision board or an e-card maker you might run into a logic that is clearly repetitive across some components, let’s say — calculating and parsing coordinates for different elements. So why not just make that a useCoordinates hook?

I hope I was able to give you some food for thought and general sense of direction if you are pondering an idea of a creative customizable app with drag and drop functionality. See you in the next chapter. In my next blog we will take a deep dive into an actual drag and drop implementation and will discuss how to use @use-gesture/react with Redux toolkit.

DREAM-CREATE-RECEIVE (earth element component)

--

--