A key rule of thumb for high-performance code in general, and graphics in particular, is to batch large amounts of work together rather than doing it in small chunks. In your case, rather than sending one quad's worth of data at a time and drawing one quad at a time, it will be far more efficient to send many quads' data in a large batch, and draw many quads together in a single draw call.
A good way to accomplish both is instancing. You would have a 4-point vertex buffer for the corners of a quad, and a second vertex buffer for the values that are per-quad (what you're putting in your uniform buffer now). This second buffer would be large enough to hold the data for all the quads at once, not just one quad. To populate the vertex buffer, you can map the whole thing, write all the data, and unmap. Then do a single instanced draw call to render all the quads at once.
However, if instancing can't be used for some reason (for instance if each quad needs a different texture, and it's not possible or desirable to put them together in an atlas), you can still upload all the data in one batch. Create a uniform buffer large enough to hold an array of all the quads' data; map/write/unmap it all in one big batch as before; then before each draw call, use
glBindBufferRange to select one quad's data from that large buffer.
Batching can also be used when you don't know the number of quads in advance. Just pick a "reasonably large" batch size (say, 1024 quads) and size your buffers for that number. Accumulate quads into the buffer until you reach the size limit, then draw them all, and start over (using the
GL_MAP_INVALIDATE_BUFFER_BIT flag with
glMapBufferRange to get a fresh copy of the buffer). You'll also need to do a partial draw at the end of this process to handle any left-over quads that didn't reach the size limit.