Creating Content Dividers With the Wavy Divider WordPress Plugin

Kevin Batdorf, the Project Lead Developer at Extendify, released the Wavy Divider plugin earlier this week. It is described as “a colorful, fun, lightweight divider block to energize your website with character and pizazz.”

As someone who believes every Friday should be officially titled “Fun Friday,” how could I not install and activate it?

I have not seen many block plugins employ a random design option before, and Wavy Divider may be the first. Its foremost setting is a “shuffle waves” control. When a user clicks it, the divider is regenerated with new locations for its peaks and valleys.

Wavy Divider in the WordPress block editor, a colorful section for breaking up content.
Adding the first Wavy Divider.

I have mixed feelings about the shuffling option. One side of me wants to know what I am getting before I hit the button, and the other is pleasantly enjoying the randomness of it all. Users who prefer tight control over each point where each wave rises or slopes down might want to look elsewhere.

The plugin is not entirely a jumble of unpredictability. It offers a handful of other options for users to exert some control over its output. There are toggles for the divider’s smoothness and direction. The former switches between the default wavy style and its straight-lined rigid counterpart. The latter moves the wave flow from the bottom to the top and vice versa.

Users can set the divider’s height, points, and opacity. There is also a color option.

If you really want to get creative, your best option is mixing and matching it with the core Group and Cover blocks. The plugin’s documentation walks users through creating a mirrored gradient effect by using two dividers inside a Group. I followed the directions and built a gradient river that flowed across the screen:

An orange, pink, purple, and blue wavy gradient flowing across the screen.
Two dividers inside of a Group block with a gradient.

In the right hands, it is entirely possible to buff out some of the roughness, putting a professional sheen on top of it all.

I threw together an intro section for a fitness or adventure blog. The goal was to create the appearance of a site owner with personality. However, better designers will use it to piece together something unique to their brands.

Section with sunset gradient background on top of a mountain-shaped black divider.
Adventure blog intro section with bottom divider.

Half the fun of this plugin is trying out-of-the-ordinary combinations with other blocks. The other half is discovering new shapes via the shuffle option.

You can also use the plugin for your next low-budget horror film’s cover art (this was absolutely an experiment gone weird when testing this plugin, but I figured I would share anyway ):

A portrait-oriented box with the woods and moon faded into the background.  Covering it is a green slime running from top to bottom.

There is a little something for anyone with an imagination or enough time to repeatedly click the “shuffle waves” button until they land on that perfect wave shape. As I said, it is Friday, and we should all let loose and have a little fun.

I tend to bookmark projects with well-structured code for my own edification, and Wavy Divider fits that mold. For developers who want relatively simple examples of block development to study, it would be hard to go wrong using it as a starting point. The code is available on GitHub.

Android Native – How to use Composables in the View system

Introduction

If you are working on a native Android app using the View system, then you might have come across a situation where you would need to add a Composable (androidx.compose.runtime.Composable) into your View hierarchy. In this tutorial, we will learn how to add a Composable into an existing View system.

The Composables that we will use in this tutorial will come from the Material 3 library.

Goals

At the end of the tutorial, you would have learned:

  1. How to add a Composable into a View system.
Tools Required
  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1.
Prerequisite Knowledge
  1. Intermediate Android.
  2. Basic Jetpack Compose.
Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. Remove the default Hello World! TextView from activity_main.xml.

  3. Inside of your module build.gradle, upgrade your modules Material dependency to version 1.5.0.

     implementation 'com.google.android.material:material:1.5.0'
  4. Add the variable below to hold the compose version.

     def composeVersion = '1.0.5'
  5. Add these options to your android build options (the android{} block)

     buildFeatures {
         compose true
     }
    
     composeOptions {
         kotlinCompilerExtensionVersion = composeVersion
     }
  6. Add the Compose dependencies below into your dependencies{} block.

     //Compose
     implementation "androidx.compose.runtime:runtime:$composeVersion"
     implementation "androidx.compose.ui:ui:$composeVersion"
     implementation "androidx.compose.ui:ui-tooling:$composeVersion"
     implementation "androidx.compose.foundation:foundation:$composeVersion"
     implementation "androidx.compose.foundation:foundation-layout:$composeVersion"
  7. Add Compose support for Material 3.

     //Material 3 Compose Support
     implementation 'androidx.compose.material3:material3:1.0.0-alpha04'
  8. For simplicity, in the project build.gradle file, downgrade your Android Kotlin plugin version to 1.5.31.

     id 'org.jetbrains.kotlin.android' version '1.5.31' apply false
ComposeView

To bridge the gap between the Compose world and the View world, Android provides a couple of Interop APIs. ComposeView (androidx.compose.ui.platform.ComposeView) is one of those APIs that we can use in scenarios where we need to insert a Composable into an existing View hierarchy.

ComposeView is actually a View itself, so we will be able to add it via XML. To add it to activity_main.xml, copy and paste the code below inside of ConstraintLayout.

    <androidx.compose.ui.platform.ComposeView
       android:id="@+id/compose_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
Insert Composable into View hierarchy

For this tutorial, we can just add a simple Composable that will print the String "Hello" 100 times.

  1. In MainActivity.kt, add the top-level Composable below.

     @Composable
     private fun Hellos(hellos: Array<String> = Array(100) {"Hello $it"} ) {
        LazyColumn {
            items(items = hellos) { hello ->
                Text(text = hello)
            }
        }
     }
  2. Next, we will obtain the reference to the <ComposeView>. Inside of onCreate(), after the setContent() call, add the line of code below.

     val composeView = findViewById<ComposeView>(R.id.compose_view)
  3. To make use of composeView, we have two options: call setContent() directly from the composeView variable or use the Kotlin scope function apply(). We will be using apply() in this case because it has the advantage of calling multiple functions on composeView rather than just a singular setContent(). Append the code snippet below into onCreate().

     composeView.apply {
        setContent {
            Hellos()
        }
     }
ViewCompositionStrategy

We are almost done, but there is another important characteristic of ComposeView that needs discussion.

ComposeView also subclasses AbstractComposeView(androidx.compose.ui.platform.AbstractComposeView). AbstractComposeView includes an interesting function that is setViewCompositionStrategy(). Because we are mixing two different UI systems together, we will need to be aware of the Activity (or Fragment), View, and Composable lifecycles. With setViewCompositionStrategy(), we can pass in a ViewCompositionStrategy object to configure how the composition should be destroyed. There are three premade ViewCompositionStrategy available for us to use.

  1. ViewCompositionStrategy.DisposeOnDetachedFromWindow: disposes the composition whenever the view becomes detached from a window. This is the default behavior. This is a singleton object.
  2. ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed: disposes the composition when the ViewTreeLifecycleOwner of the next window the view is attached to is destroyed. This is also a singleton object.
  3. ViewCompositionStrategy.DisposeOnLifecycleDestroyed: disposes the composition when the lifecycle is destroyed. Similar to DisposeOnViewTreeLifecycleDestroyed, but this is a class with constructors that allows you to specify a specific lifecycle.

To set the ViewCompositionStrategy, you can add it to the apply() like below.

composeView.apply {
   //Default option
   //setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)

   //Explicit lifeCycle
   //setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(lifecycle))

   setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
   setContent {
       Hellos()
   }
}
Run the App

It is now time to run our App to see if it works correctly.

Compose_in_View.gif

We can see that it displays the Composable LazyList inside of our ConstraintLayout View successfully.

Solution Code

MainActivity.kt

package com.codelab.daniwebcomposeinviews

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       val composeView = findViewById<ComposeView>(R.id.compose_view)

/*        composeView.setContent {
           Hellos()
       }*/

       composeView.apply {
           //Default option
           //setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)

           //Explicit lifeCycle
           //setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(lifecycle))

           setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
           setContent {
               Hellos()
           }
       }
   }
}

@Composable
private fun Hellos(hellos: Array<String> = Array(100) {"Hello $it"} ) {
   LazyColumn {
       items(items = hellos) { hello ->
           Text(text = hello)
       }
   }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <androidx.compose.ui.platform.ComposeView
       android:id="@+id/compose_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Project build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
   id 'com.android.application' version '7.1.0' apply false
   id 'com.android.library' version '7.1.0' apply false
   id 'org.jetbrains.kotlin.android' version '1.5.31' apply false
}

task clean(type: Delete) {
   delete rootProject.buildDir
}

Module build.gradle

plugins {
   id 'com.android.application'
   id 'org.jetbrains.kotlin.android'
}

def composeVersion = '1.0.5'

android {
   compileSdk 31

   defaultConfig {
       applicationId "com.codelab.daniwebcomposeinviews"
       minSdk 21
       targetSdk 31
       versionCode 1
       versionName "1.0"

       testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }

   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
   }
   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
   kotlinOptions {
       jvmTarget = '1.8'
   }

   buildFeatures {
       compose true
   }

   composeOptions {
       kotlinCompilerExtensionVersion = composeVersion
   }
}

dependencies {
   //Compose
   implementation "androidx.compose.runtime:runtime:$composeVersion"
   implementation "androidx.compose.ui:ui:$composeVersion"
   implementation "androidx.compose.ui:ui-tooling:$composeVersion"
   implementation "androidx.compose.foundation:foundation:$composeVersion"
   implementation "androidx.compose.foundation:foundation-layout:$composeVersion"

   //Material 3 Compose Support
   implementation 'androidx.compose.material3:material3:1.0.0-alpha04'

   implementation 'com.google.android.material:material:1.5.0'
   implementation 'androidx.core:core-ktx:1.7.0'
   implementation 'androidx.appcompat:appcompat:1.4.1'
   implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
   testImplementation 'junit:junit:4.13.2'
   androidTestImplementation 'androidx.test.ext:junit:1.1.3'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Summary

We have learned how to add Composables into a View hierarchy using ComposeView. The full project code can be found at https://github.com/dmitrilc/DaniwebComposeInViews.

Kotlin – How to create property delegates

Introduction

When working on Android with Kotlin, you might have ran into property delegates on a few occasions, such as activityViewModels() from androidx.fragment.app or remember() from androidx.compose.runtime. In this tutorial, we will learn what property delegates are and how to create them ourselves.

Goals

At the end of the tutorial, you would have learned:

  1. How to create final delegates.
  2. How to create non-final delegates.
Tools Required

A Kotlin IDE such as IntelliJ IDEA version 2021.3.2 (Community Edition).

  1. The Kotlin version used in this tutorial is 1.6.10.
Prerequisite Knowledge
  1. Basic Kotlin.
Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Kotlin project using Gradle as the build system.

  2. For the Project JDK, use JDK 17.

  3. Add the kotlin-reflect dependency below into your build.gradle.

     implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
Final Property Delegate

In Kotlin, to delegate a property means letting another class provide or set the value for that property. There is a special syntax to use a delegate, which is:

val/var propertyName: Type by expression

In real code, the explicit Type on the variable can be omitted because it can be inferred from the delegate getter.

If the reference is a val, the delegate must provide a getter with the syntax below.

operator fun getValue(thisRef: Any?, property: KProperty<*>): Type

The function signature Type must be of the same or a subtype of the variable declaration. thisRef will contain a reference to the enveloping class of the property. If the property is a top-level declaration, then thisRef will be null.

To see how this works, let us write some code.

  1. Open Main.kt. All of the code used in this tutorial can be written in this file.

  2. Import this class into the file.

     import kotlin.reflect.KProperty
  3. Create a delegate for the final-variable use case.

     class FinalDelegate {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
            if (thisRef === null) {
                "${property.name} is top-level"
            }
            else "${property.name} is inside $thisRef"
     }
  4. Add a top-level val property and delegate its value to FinalDelegate.

     val topLevelFinalProp: String by FinalDelegate()
  5. Add a class with an inner val property like below.

     class FinalPropContainer {
        val innerFinalProp: String by FinalDelegate()
     }
  6. In main(), call the code below.

     println(topLevelFinalProp)
     println(FinalPropContainer().innerFinalProp)
  7. Run the program. We can see that the output prints whether the property is top-level or an inner property of another class.

     topLevelFinalProp is top-level
     innerFinalProp is inside FinalPropContainer@73a8dfcc
Non-final Property Delegate

A delegate for a var reference is different to a delegate for a val because in addition to providing a getter using the same syntax above, it must also provide a setter with a special syntax.

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Type)

The Type must be of the same or a super type of the property. To see how this works, let us write some more code.

  1. Inside Maint.kt, create another delegate for non-final var references. This delegate is a little bit different from FinalDelegate because it is a stateful class (the delegate is responsible for storing its state).

     class NonFinalDelegate(private var num: Int = 1) {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = num
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
            num = value
        }
     }
  2. Add a var reference and delegate it to NonFinalDelegate.

     var nonFinalProp: Int by NonFinalDelegate()
  3. In main(), call the code below. The assignment expression = will invoke the setter.

     println(nonFinalProp)
     nonFinalProp = 2
     println(nonFinalProp)
  4. We can see that after invoking the setter, nonFinalProp now contains a new value. The output of the code above is:

     1
     2
(Optional) How does remember() work?

This section is mostly only relevant towards Android development.

If we look at the function signature of one of the remember() variant,

@Composable
inline fun <T : Any?> remember(calculation: (@DisallowComposableCalls () -> T)?): T

then we can see that it basically just returns T from the lambda argument. Usually remember() is used in conjunction with lambdas that return a State (androidx.compose.runtime) object, such as mutableStateOf(). State itself does not contain a getValue() function, so how can it be used as a delegate? The answer lies in one of its extension functions, getValue().

inline operator fun <T : Any?> State<T?>?.getValue(thisObj: Any?, property: KProperty<*>?): T

Even though State does not include a getValue() function, it can still be used as a delegate if this extension function is imported into your source file.

Solution Code

Main.kt

import kotlin.reflect.KProperty

fun main() {
//    println(topLevelFinalProp)
//    println(FinalPropContainer().innerFinalProp)
   println(nonFinalProp)
   nonFinalProp = 2
   println(nonFinalProp)
}

val topLevelFinalProp: String by FinalDelegate()

class FinalPropContainer {
   val innerFinalProp: String by FinalDelegate()
}

class FinalDelegate {
   operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
       if (thisRef === null) {
           "${property.name} is top-level"
       }
       else "${property.name} is inside $thisRef"
}

var nonFinalProp: Int by NonFinalDelegate()

class NonFinalDelegate(private var num: Int = 1) {
   operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = num
   operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
       num = value
   }
}

build.gradle

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
   kotlin("jvm") version "1.6.10"
   application
}

group = "me.dimitri"
version = "1.0-SNAPSHOT"

repositories {
   mavenCentral()
}

dependencies {
   implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
   testImplementation(kotlin("test"))
}

tasks.test {
   useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
   kotlinOptions.jvmTarget = "1.8"
}

application {
   mainClass.set("MainKt")
}
Summary

We have learned how to create property delegates in Kotlin in this tutorial. In some situations where you do not have to use extension functions, then you can also implement the ReadOnlyProperty or ReadWriteProperty interfaces to create delegates.

The full project code can be found at https://github.com/dmitrilc/DaniwebKotlinPropertyDelegate.

What Web Frameworks Solve: The Vanilla Alternative (Part 2)

Last week, we looked at the different benefits and costs of using frameworks, starting from the point of view of which core problems they’re trying to solve, focusing on declarative programming, data-binding, reactivity, lists and conditionals. Today, we’ll see whether an alternative can emerge from the web platform itself.

Roll Your Own Framework?

An outcome that might seem inevitable from exploring life without one of the frameworks, is to roll your own framework for reactive data-binding. Having tried this before, and seeing how costly it can be, I decided to work with a guideline in this exploration; not to roll my own framework, but instead to see if I can use the web platform directly in a way that makes frameworks less necessary. If you consider rolling your own framework, be aware that there is a set of costs not discussed in this article.

Vanilla Choices

The web platform already provides a declarative programming mechanism out of the box: HTML and CSS. This mechanism is mature, well tested, popular, widely used, and documented. However, it does not provide clear built-in concepts of data-binding, conditional rendering, and list synchronization, and reactivity is a subtle detail spread across multiple platform features.

When I skim through the documentation of popular frameworks, I find the features described in Part 1 straight away. When I read the web platform documentation (for example, on MDN), I find many confusing patterns of how to do things, without a conclusive representation of data-binding, list synchronization, or reactivity. I will try to draw some guidelines of how to approach these problems on the web platform, without requiring a framework (in other words, by going vanilla).

Reactivity With Stable DOM Tree and Cascading

Let’s go back to the error label example. In ReactJS and SolidJS, we create declarative code that translates to imperative code that adds the label to the DOM or removes it. In Svelte, that code is generated.

But what if we didn’t have that code at all, and instead we used CSS to hide and show the error label?

<style>
    label.error { display: none; }
    .app.has-error label.error {display: block; }
</style>
<label class="error">Message</label>

<script>
   app.classList.toggle('has-error', true);
</script>

The reactivity, in this case, is handled in the browser — the app’s change of class propagates to its descendants until the internal mechanism in the browser decides whether to render the label.

This technique has several advantages:

  • The bundle size is zero.
  • There are zero build steps.
  • Change propagation is optimized and well tested, in native browser code, and avoids unnecessary expensive DOM operations like append and remove.
  • The selectors are stable. In this case, you can count on the label element being there. You can apply animations to it without relying on complicated constructs such as “transition groups”. You can hold a reference to it in JavaScript.
  • If the label is shown or hidden, you can see the reason in the style panel of the developer tools, which shows you the entire cascade, the chain of rules that ended up in the label being visible (or hidden).

Even if you read this and choose to keep working with frameworks, the idea of keeping the DOM stable and changing state with CSS is powerful. Consider where this could be useful to you.

Form-Oriented “Data-Binding”

Before the era of JavaScript-heavy single-page applications (SPAs), forms were the major way to create web applications that include user input. Traditionally, the user would fill in the form and click a “Submit” button, and the server-side code would handle the response. Forms were the multi-page application version of data-binding and interactivity. No wonder that HTML elements with the basic names of input and output are form elements.

Because of their wide use and long history, the form APIs accumulated several hidden nuggets that make them useful for problems that are not traditionally thought of as being solved by forms.

Forms and Form Elements as Stable Selectors

Forms are accessible by name (using document.forms), and each form element is accessible by its name (using form.elements). In addition, the form associated with an element is accessible (using the form attribute). This includes not only input elements, but also other form elements such as output, textarea, and fieldset, which allows for nested access of elements in a tree.

In the error label example from the previous section, we showed how to reactively show and hide the error message. This is how we update the error message text in React (and similarly in SolidJS):

const [errorMessage, setErrorMessage] = useState(null);
return <label className="error">{errorMessage}</label>

When we have a stable DOM and stable tree forms and form elements, we can do the following:

<form name="contactForm">
  <fieldset name="email">
     <output name="error"></output>
  </fieldset>
</form>

<script>
  function setErrorMessage(message) {
  document.forms.contactForm.elements.email.elements.error.value = message;
  }
</script>

This looks quite verbose in its raw form, but it’s also very stable, direct, and extremely performant.

Forms for Input

Usually, when we build a SPA, we have some kind of JSON-like API that we work with to update our server, or whatever model we use.

This would be a familiar example (written in Typescript for readability):

interface Contact {
  id: string;
  name: string;
  email: string;
  subscriber: boolean;
}

function updateContact(contact: Contact) { … }

It’s common in framework code to generate this Contact object by selecting input elements and constructing the object piece by piece. With proper use of forms, there is a concise alternative:

<form name="contactForm">
  <input name="id" type="hidden" value="136" />
  <input name="email" type="email"/>
  <input name="name" type="string" />
  <input name="subscriber" type="checkbox" />
</form>

<script>
   updateContact(Object.fromEntries(
       new FormData(document.forms.contactForm));
</script>

By using hidden inputs and the useful FormData class, we can seamlessly transform values between DOM input and JavaScript functions.

Combining Forms and Reactivity

By combining the high-performance selector stability of forms and CSS reactivity, we can achieve more complex UI logic:

<form name="contactForm">
  <input name="showErrors" type="checkbox" hidden />
  <fieldset name="names">
     <input name="name" />
     <output name="error"></output>
  </fieldset>
  <fieldset name="emails">
     <input name="email" />
     <output name="error"></output>
  </fieldset>
</form>

<script>
  function setErrorMessage(section, message) {
  document.forms.contactForm.elements[section].elements.error.value = message;
  }
  function setShowErrors(show) {
  document.forms.contactForm.elements.showErrors.checked = show;
  }
</script>

<style>
   input[name="showErrors"]:not(:checked) ~ * output[name="error"] {
      display: none;
   }
</style>

Note in this example that there is no use of classes — we develop the behavior of the DOM and style from the data of the forms, rather than by manually changing element classes.

I am not fond of overusing CSS classes as JavaScript selectors. I think they should be used to group together similarly styled elements, not as a catch-all mechanism to change component styles.

Advantages of Forms

  • As with cascading, forms are built into the web platform, and most of their features are stable. That means much less JavaScript, many fewer framework version mismatches, and no “build”.
  • Forms are accessible by default. If your app uses forms properly, there is much less need for ARIA attributes, “accessibility plugins”, and last-minute audits. Forms lend themselves to keyboard navigation, screen readers, and other assistive technologies.
  • Forms come with built-in input-validation features: validation by regex pattern, reactivity to invalid and valid forms in CSS, handling of required versus optional, and more. You don’t need something to look like a form in order to enjoy these features.
  • The submit event of forms is extremely useful. For example, it allows an “Enter” key to be caught even when there is no submit button, and it allows multiple submit buttons to be differentiated by the submitter attribute (as we’ll see in the TODO example later).
  • Elements are associated with their containing form by default but can be associated with any other form in the document using the form attribute. This allows us to play around with form association without creating a dependency on the DOM tree.
  • Using the stable selectors helps with UI test automation: We can use the nested API as a stable way to hook into the DOM regardless of its layout and hierarchy. The form > (fieldsets) > element hierarchy can serve as the interactive skeleton of your document.

ChaCha and HTML Template

Frameworks provide their own way of expressing observable lists. Many developers today also rely on non-framework libraries that provide this kind of feature, such as MobX.

The main problem with general-purpose observable lists is that they are general purpose. This adds convenience with the cost of performance, and it also requires special developer tools to debug the complicated actions that those libraries do in the background.

Using those libraries and understanding what they do are OK, and they can be useful regardless of the choice of UI framework, but using the alternative might not be more complicated, and it might prevent some of the pitfalls that happen when you try to roll your own model.

Channel of Changes (or ChaCha)

The ChaCha — otherwise also known as Changes Channel — is a bidirectional stream whose purpose is to notify changes in the intent direction and the observe direction.

  • In the intent direction, the UI notifies the model of changes intended by the user.
  • In the observe direction, the model notifies the UI of changes that were made to the model and that need to be displayed to the user.

It’s perhaps a funny name, but it’s not a complicated or novel pattern. Bidirectional streams are used everywhere on the web and in software (for example, MessagePort). In this case, we are creating a bidirectional stream that has a particular purpose: to report actual model changes to the UI and intentions to the model.

The interface of ChaCha can usually be derived from the specification of the app, without any UI code.

For example, an app that allows you to add and remove contacts and that loads the initial list from a server (with an option to refresh) could have a ChaCha that looks like this:

interface Contact {
  id: string;
  name: string;
  email: string;
}
// "Observe" Direction
interface ContactListModelObserver {
  onAdd(contact: Contact);
  onRemove(contact: Contact);
  onUpdate(contact: Contact);
}
// "Intent" Direction
interface ContactListModel {
  add(contact: Contact);
  remove(contact: Contact);
  reloadFromServer();  
}

Note that all of the functions in the two interfaces are void and only receive plain objects. This is intentional. ChaCha is built like a channel with two ports to send messages, which allows it to work in an EventSource, an HTML MessageChannel, a service worker, or any other protocol.

The nice thing about ChaChas is that they’re easy to test: You send actions and expect specific calls to the observer in return.

The HTML Template Element for List Items

HTML templates are special elements that are present in the DOM but don’t get displayed. Their purpose is to generate dynamic elements.

When we use a template element, we can avoid all of the boilerplate code of creating elements and populating them in JavaScript.

The following will add a name to a list using a template:

<ul id="names">
  <template>
   <li><label class="name" /></li>
  </template>
</ul>
<script>
  function addName(name) {
    const list = document.querySelector('#names');
    const item = list.querySelector('template').content.cloneNode(true).firstElementChild;
    item.querySelector('label').innerText = name;
    list.appendChild(item);
  }
</script>

By using the template element for list items, we can see the list item in our original HTML — it’s not “rendered” using JSX or some other language. Your HTML file now contains all of the HTML of the app — the static parts are part of the rendered DOM, and the dynamic parts are expressed in templates, ready to be cloned and appended to the document when the time comes.

Putting It All Together: TodoMVC

TodoMVC is an app specification of a TODO list that has been used to showcase the different frameworks. The TodoMVC template comes with ready-made HTML and CSS to help you focus on the framework.

You can play with the result in the GitHub repository, and the full source code is available.

Start With a Specification-Derived ChaCha

We’ll start with the specification and use it to build the ChaCha interface:

interface Task {
   title: string;
   completed: boolean;
}

interface TaskModelObserver {
   onAdd(key: number, value: Task);
   onUpdate(key: number, value: Task);
   onRemove(key: number);
   onCountChange(count: {active: number, completed: number});
}

interface TaskModel {
   constructor(observer: TaskModelObserver);
   createTask(task: Task): void;
   updateTask(key: number, task: Task): void;
   deleteTask(key: number): void;
   clearCompleted(): void;
   markAll(completed: boolean): void;
}

The functions in the task model are derived directly from the specification and what the user can do (clear completed tasks, mark all as completed or active, get the active and completed counts).

Note that it follows the guidelines of ChaCha:

  • There are two interfaces, one acting and one observing.
  • All of the parameter types are primitives or plain objects (being easily translated to JSON).
  • All of the functions return void.

The implementation of TodoMVC uses localStorage as the back end.

The model is very simple and not very relevant to the discussion about the UI framework. It saves to localStorage when needed and fires change callbacks to the observer when something changes, either as a result of user action or when the model is loaded from localStorage for the first time.

Lean, Form-Oriented HTML

Next, I’ll take the TodoMVC template and modify it to be form-oriented — a hierarchy of forms, with input and output elements representing data that can be changed with JavaScript.

How do I know whether something needs to be a form element? As a rule of thumb, if it binds to data from the model, then it should be a form element.

The full HTML file is available, but here is its main part:

<section class="todoapp">
   <header class="header">
       <h1>todos</h1>
       <form name="newTask">
           <input name="title" type="text" placeholder="What needs to be done?" autofocus>
       </form>
   </header>

   <main>
       <form id="main"></form>
       <input type="hidden" name="filter" form="main" />
       <input type="hidden" name="completedCount" form="main" />
       <input type="hidden" name="totalCount" form="main" />
       <input name="toggleAll" type="checkbox" form="main" />

       <ul class="todo-list">
           <template>
               <form class="task">
                   <li>
                       <input name="completed" type="checkbox" checked>
                       <input name="title" readonly />
                       <input type="submit" hidden name="save" />
                       <button name="destroy">X</button>
                   </li>
               </form>
           </template>
       </ul>
   </main>

   <footer>
       <output form="main" name="activeCount">0</output>
       <nav>
           <a name="/" href="#/">All</a>
           <a name="/active" href="#/active">Active</a>
           <a name="/completed" href="#/completed">Completed</a>
       </nav>
       <input form="main" type="button" name="clearCompleted" value="Clear completed" />
   </footer>
</section>

This HTML includes the following:

  • We have a main form, with all of the global inputs and buttons, and a new form for creating a new task. Note that we associate the elements to the form using the form attribute, to avoid nesting the elements in the form.
  • The template element represents a list item, and its root element is another form that represents the interactive data related to a particular task. This form would be repeated by cloning the template’s contents when tasks are added.
  • Hidden inputs represent data that is not directly shown but that is used for styling and selecting.

Note how this DOM is concise. It does not have classes sprinkled across its elements. It includes all of the elements needed for the app, arranged in a sensible hierarchy. Thanks to the hidden input elements, you can already get a good sense of what might change in the document later on.

This HTML does not know how it’s going to be styled or exactly what data it’s bound to. Let the CSS and JavaScript work for your HTML, rather than your HTML work for a particular styling mechanism. This would make it much easier to change designs as you go along.

Minimal Controller JavaScript

Now that we have most of the reactivity in CSS, and we have list-handling in the model, what’s left is the controller code — the duct tape that holds everything together. In this small application, the controller JavaScript is around 40 lines.

Here is a version, with an explanation for each part:

import TaskListModel from './model.js';

const model = new TaskListModel(new class {

Above, we create a new model.

onAdd(key, value) {
   const newItem = document.querySelector('.todo-list template').content.cloneNode(true).firstElementChild;
   newItem.name = `task-${key}`;
   const save = () => model.updateTask(key,  Object.fromEntries(new FormData(newItem)));
   newItem.elements.completed.addEventListener('change', save);
   newItem.addEventListener('submit', save);
   newItem.elements.title.addEventListener('dblclick', ({target}) => target.removeAttribute('readonly'));
   newItem.elements.title.addEventListener('blur', ({target}) => target.setAttribute('readonly', ''));
   newItem.elements.destroy.addEventListener('click', () => model.deleteTask(key));
   this.onUpdate(key, value, newItem);
   document.querySelector('.todo-list').appendChild(newItem);
}

When an item is added to the model, we create its corresponding list item in the UI.

Above, we clone the contents of the item template, assign the event listeners for a particular item, and add the new item to the list.

Note that this function, along with onUpdate, onRemove, and onCountChange, are callbacks that are going to be called from the model.

onUpdate(key, {title, completed}, form = document.forms[`task-${key}`]) {
   form.elements.completed.checked = !!completed;
   form.elements.title.value = title;
   form.elements.title.blur();
}

When an item is updated, we set its completed and title values, and then blur (to exit editing mode).

onRemove(key) { document.forms[`task-${key}`].remove(); }

When an item is removed from the model, we remove its corresponding list item from the view.

onCountChange({active, completed}) {
   document.forms.main.elements.completedCount.value = completed;
   document.forms.main.elements.toggleAll.checked = active === 0;
   document.forms.main.elements.totalCount.value = active + completed;
   document.forms.main.elements.activeCount.innerHTML = `<strong>${active}</strong> item${active === 1 ? '' : 's'} left`;
}

In the code above, when the number of completed or active items changes, we set the proper inputs to trigger the CSS reactions, and we format the output that displays the count.

const updateFilter = () => filter.value = location.hash.substr(2);
window.addEventListener('hashchange', updateFilter);
window.addEventListener('load', updateFilter);

And we update the filter from the hash fragment (and at startup). All we’re doing above is setting the value of a form element — CSS handles the rest.

document.querySelector('.todoapp').addEventListener('submit', e => e.preventDefault(), {capture: true});

Here, we ensure that we don’t reload the page when a form is submitted. This is the line that turns this app into a SPA.

document.forms.newTask.addEventListener('submit', ({target: {elements: {title}}}) =>   
    model.createTask({title: title.value}));
document.forms.main.elements.toggleAll.addEventListener('change', ({target: {checked}})=>
    model.markAll(checked));
document.forms.main.elements.clearCompleted.addEventListener('click', () =>
    model.clearCompleted());

And this handles the main actions (creating, marking all, clearing completed).

Reactivity With CSS

The full CSS file is available for you to view.

CSS handles a lot of the requirements of the specification (with some amendments to favor accessibility). Let’s look at some examples.

According to the specification, the “X” (destroy) button is shown only on hover. I’ve also added an accessibility bit to make it visible when the task is focused:

.task:not(:hover, :focus-within) button[name="destroy"] { opacity: 0 }

The filter link gets a red-ish border when it’s the current one:

.todoapp input[name="filter"][value=""] ~ footer a[href$="#/"],
nav a:target {
   border-color: #CE4646;
}

Note that we can use the href of the link element as a partial attribute selector — no need for JavaScript that checks the current filter and sets a selected class on the proper element.

We also use the :target selector, which frees us from having to worry about whether to add filters.

The view and edit style of the title input changes based on its read-only mode:

.task input[name="title"]:read-only {
…
}

.task input[name="title"]:not(:read-only) {
…
}

Filtering (i.e. showing only active and completed tasks) is done with a selector:

input[name="filter"][value="active"] ~ * .task
      :is(input[name="completed"]:checked, input[name="completed"]:checked ~ *),
input[name="filter"][value="completed"] ~ * .task
     :is(input[name="completed"]:not(:checked), input[name="completed"]:not(:checked) ~ *) {
   display: none;
}

The code above might seem a bit verbose, and it is probably easier to read with a CSS preprocessor such as Sass. But what it does is straightforward: If the filter is active and the completed checkbox is checked, or vice versa, then we hide the checkbox and its siblings.

I chose to implement this simple filter in CSS to show how far this can go, but if it starts to get hairy, then it would totally make sense to move it into the model instead.

Conclusion and Takeaways

I believe that frameworks provide convenient ways to achieve complicated tasks, and they have benefits beyond technical ones, such as aligning a group of developers to a particular style and pattern. The web platform offers many choices, and adopting a framework gets everyone at least partially on the same page for some of those choices. There’s value in that. Also, there is something to be said for the elegance of declarative programming, and the big feature of componentization is not something I’ve tackled in this article.

But remember that alternative patterns exist, often with less cost and not always needing less developer experience. Allow yourself to be curious with those patterns, even if you decide to pick and choose from them while using a framework.

Pattern Recap

  • Keep the DOM tree stable. It starts a chain reaction of making things easy.
  • Rely on CSS for reactivity instead of JavaScript, when you can.
  • Use form elements as the main way to represent interactive data.
  • Use the HTML template element instead of JavaScript-generated templates.
  • Use a bidirectional stream of changes as the interface to your model.

Special thanks to the following individuals for technical reviews: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal, Louis Lazaris

PHP Mysql database

Hi all, I hope someone can help me with the following. I have a script that reads a txt file and displays the correct values. I only want to add it to the database now, but I can't manage this. below the script

<?php 
include '../includes/connect.php';

    if(!$conn)
{
    die(mysqli_error());
}


    $fd = fopen ("import/8.txt", "r");

while (!feof ($fd))
{
 $buffer1 = fgets($fd, 4096);
 if (substr($buffer1,0,7)=='#EXTINF')
 {
  $buffer = fgets($fd, 4096);
  $buffer = str_replace("'","`",$buffer);
  $array=explode('\\',$buffer);
  $count = count($array);

  list($artiest,$titel) = explode(' - ',$array[$count-1]);

  if (strlen($titel)==0)
  {print("$artiest <br>");
   list($artiest,$titel) = explode('- ',$array[$count-1]);
   if (strlen($titel)==0)
   {print("$artiest <br>");
    list($artiest,$titel) = explode(' -',$array[$count-1]);
    if (strlen($titel)==0)
    {
     print("$artiest <br>");
     list($artiest,$titel) = explode('-',$array[$count-1]);
     if (strlen($titel)==0)
     {
      $buffer = str_replace("'","`",$buffer1);
      $pos=strpos($buffer,',');
      $tekst = substr($buffer,$pos+1);
      list($artiest,$titel) = explode(' - ',$tekst);
      if (strlen($titel)==0)
      {
       list($artiest,$titel) = explode('- ',$tekst);
       if (strlen($titel)==0)
       {
        list($artiest,$titel) = explode(' -',$tekst);
        if (strlen($titel)==0)
        {
         list($artiest,$titel) = explode('-',$tekst);
         if (strlen($titel)==0)
          print("$artiest <br>");

            $sql= "INSERT INTO mp3 (id_dj,artiest,titel) VALUES ('8','$artiest','$titel')";



    }



}

        }

       }
      }
     }

    }

   }

  }


echo "import mp3's klaar";







?>

The Real-time Web: Evolution of the User Experience

Over the last few years, companies have used real-time updates to add new experiences and features and increase their market share.

It's now standard to expect a page within an app or browser to update parts of itself without forcing it to refresh. For example, a news page of live sports scores updates with the latest goal scored, or an app shows a change as you track your taxi on a map.

How To Maintain Quality When Transitioning From Monolith to Microservices

Piece by piece, legacy monolith applications are being broken down and replaced by microservices. Organizations large and small are making the transition, but that doesn’t mean the transition is easy. Until recently, the challenge of transitioning well has held up many organizations, but there are some options to make this easier. In this post, we’ll look at some of the critical metrics to monitor when making the transition, along with helpful tools to get you from monolith to microservices.

The Why and the How

Organizations make the switch to microservices for several reasons. When an application is broken into small pieces, those pieces are easier to test and quicker to deploy. With this modularization also comes more clearly scoped responsibilities for developers and teams.

I’m From Ops, Should I Do Chaos!

Chaos Engineering is a useful technique to prepare the teams to respond to production issues and reduce application downtime.  In this article, an Ops team member who is involved in conducting chaos engineering puts forth his/her perspective, challenges, and expectations with respect to chaos engineering.  Ops in the context of this article are the team responsible for releasing software, provisioning infrastructure, managing production support, and monitoring applications in production.

I love stability.  DevOps experts tell me that those days of working in silos are gone and I should collaborate with the Dev team for all ‘continuous…’ culminating in continuous production deployment.  Agree, and totally.   

Introduction to Couchbase for Oracle Developers and Experts: Part 5: Statements and Features

SQL is the only 22nd century tool available in 21st century

Here are the previous articles comparing architecture, database objects, data types, and data modeling of Oracle with Couchbase. This will focus on SQL support.  

Oracle was the first and staunch supporter of SQL. Oracle's SQL implementation beat IBM to market by two years.  That changed the fortune of one or two people. :-) All of the modern relational databases implement SQL. So much so, the relational databases are sometimes called SQL databases, much to the chagrin of C. J. Date.  Nations are known by their languages... English, French, and American(!). It's not a stretch for a class of database systems to be known by their languages as well.  SQL has been so effective, many big data and non-relational systems have picked up SQL as the lingua franca. SQL is here to stay, even for NoSQL systems. 

Hi everyone, I’m christaelrod

Working as a senior graphic and web designer at Proglobalbusinesssolutions, passionate about creating unique user interfaces& multimedia design with strong skills. My expertise includes designing and developing front and back end web site designs using HTML5, CSS3, PHP, JS and more.

6+ Best Product Review Plugins for WordPress (2024)

Our readers often ask us if there are any WordPress product review plugins that we recommend.

During our 16+ years of blogging, we’ve reviewed and written about a lot of different products that we recommend to our readers. We have also collected and displayed different customer reviews for the plugins that we develop through our partner companies.

While we did these things completely manually in the past, plugins can speed up the process a lot. Plus, product review plugins can help you get more traffic from search engines, boost affiliate link clicks, add user-submitted reviews to your website to increase social proof, and more.

That’s why we have tested the most popular WordPress product review plugins on the market, while paying attention to features, user-friendliness, and different use cases.

In this article, we have hand-picked the best product review plugins for WordPress.

Best Product Review Plugins for WordPress

Why Use Product Review Plugins in WordPress?

What do you do when you want to buy something online? If you are like most people, then you probably check out product reviews to compare different options.

If you have an online store, then allowing your customers to submit reviews is a smart way to add social proof to your website and boost sales.

Even a niche review site where you are writing all the reviews yourself can use a product review plugin. The right plugin will help you optimize your reviews in search results so you can get more clicks and traffic.

WordPress makes it easy to add new blog posts and create important website pages, but writing review content or adding reviews to your website requires additional features offered by WordPress plugins. 

Product review plugins can help you make more sales, let users add reviews to your website, display reviews from third-party platforms, and more. 

That being said, let’s take a look at some of the best product review plugins you can use on your WordPress site. 

Why Trust WPBeginner?

At WPBeginner, our team has 16+ years of experience in blogging, affiliate marketing, and running WordPress websites. We also thoroughly test each of the plugins that we recommend on real websites.

For more details, see our editorial process.

1. WP Review Pro

WP Review Pro

WP Review Pro is the best WordPress product review plugin on the market. It gives you full control over your WordPress product reviews, makes it easy to create product comparisons, and much more. 

It includes a library of pre-designed templates you can customize to match the design of your website. There are different types of rating systems you can use, like percentage, star rating, points, and more. 

WP Review point rating example

Creating a custom review box lets you present all the important product information for your visitors, including a link to buy the product.

Review schema markup is automatically included, allowing you to show star ratings in the search results. When we tested the plugin, we realized that you can also let your users add their reviews to your products, rate features, and vote on other users’ comments. 

WP Review Pro user rating example

Pros

  • The plugin lets you display reviews from third-party sources like Facebook, Google Places, and Yelp, so you can easily add social proof to your WordPress website
  • WP Review allows you to create a pros and cons section for each product.
  • It lets you control who can review your website with its review user roles feature.
  • The plugin offers complete spam protection, import/exports reviews, and has a WooCommerce integration.

Cons

  • WP Review has a free plan, but most of the features can only be unlocked in the pro version.

Why we recommend WP Review Pro: Overall, WP Review Pro is the best product review plugin. It comes with lots of premade templates, makes it super easy to add reviews from other sources, and also offers complete spam protection.

2. All In One SEO

AIOSEO

All in One SEO (AIOSEO) is the best SEO plugin for WordPress on the market used by over 3 million websites. It lets you easily add product review schema to your review blog posts and optimize your content to get more traffic from search engines.

Schema markup doesn’t appear on your website for visitors to see, but it helps search engines better understand your content and display your product reviews properly.

Rich snippets will catch potential visitors’ attention and are more likely to get clicks from the search results.

Rich snippets product schema example

When you are writing your product reviews, AIOSEO will automatically detect the right type of schema based on what you are writing. 

You also have full control over the product review schema and can enter relevant product information for search engine bots to read.

AIOSEO product schema

Pros

  • With AIOSEO, you can add existing customer reviews to improve the search appearance. You will find this option below the review schema box in your post editor.
  • You can optimize your product images for SEO with the plugin.
  • It offers on-page SEO analysis, local SEO, social media integrations, and a broken link assistant.
  • When testing the plugin, we realized that AIOSEO can also act as a writing assistant and help you write better product descriptions.

Cons

  • The plugin does not offer any premade templates for product reviews and does not let you add product reviews from other sources.
  • There is a free version of the plugin available, but you’ll need the pro version to access the product review schema feature.

Why we recommend AIOSEO: If you want to optimize your product reviews for SEO, then All in One SEO is the best option. Its product review schema allows you to easily add product reviews and boost search engine rankings on your website.

For more details, you can see our complete All in One SEO review.

3. Smash Balloon Reviews Feed Pro

Smash Balloon Reviews Feed Pro

Smash Balloon Reviews Feed Pro is the best Google Reviews plugin. It also allows you to display reviews on your website from different sources like Yelp, Facebook, WordPress.org, Trustpilot, and TripAdvisor.

The plugin comes with an easy-to-use editor that lets you customize your product reviews feed according to your liking. You can change the feed’s layout, add different buttons, and create your own color scheme.

The Smash Balloon Review Feed Pro plugin

You can then add the product reviews feed to any page, post, or widget area on your website using Smash Balloon’s block or shortcode.

Pros

  • Reviews Feed Pro lets you create multiple product review feeds for different platforms.
  • It lets you filter reviews based on star ratings, keywords, or specific platforms.
  • You can configure the reviews feed to match your WordPress theme.
  • The plugin offers optimized images, local storage, and background caching for fast-loading review feeds. When we tested the plugin, it loaded the product review feed in no time.
  • Smash Balloon also offers other plugins that you can use to embed Facebook, Twitter, or Instagram feeds. You can also create a social wall with it.

Cons

  • The plugin’s free plan only lets you add Yelp or Google reviews. To add a feed for product reviews from other sources, you will need to upgrade to the pro plan.
  • It does not offer a feature for users to add product reviews directly on your WordPress site.

Why we recommend Smash Balloon Reviews Feed Pro: If you want to display a product reviews feed from another source like Yelp, Facebook, or Google, then Reviews Feed Pro is the ideal plugin for you.

For more information, you can see our complete Smash Balloon review.

4. WP Customer Reviews

WP Customer Reviews

WP Customer Reviews allows you to collect customer reviews and testimonials as well as create your own product reviews. 

For example, you can create a specific page on your website to showcase your testimonials and improve your social proof and conversions. 

You have full control over your review forms and the information you want to collect from your users. To display reviews, you can use shortcodes or the included block.

Plus, WP Customer Reviews allows you to configure which user roles can leave product reviews on your website.

Pros

  • You can check the reviews in your WordPress dashboard, so you have complete control over the ones that are displayed.
  • It has the option to turn any existing WordPress blog post into a product review.
  • WP Customer Reviews lets you add product review schema to improve search engine rankings.
  • The plugin allows you to add a pros and cons section for each product, add star ratings, and import/export reviews.

Cons

  • It offers limited features to filter and approve reviews based on different criteria.
  • The plugin offers basic layouts for reviews with limited customization.

Why we recommend WP Customer Reviews: The plugin is completely free and can also integrate with WooCommerce seamlessly. If you have an online store and are on a shoestring budget, then we recommend WP Customer Reviews to add product reviews to your website.

5. Site Reviews

Site Reviews plugin

Site Reviews is an easy-to-use review plugin that lets you collect customer reviews for your products, services, or a local business, similar to a site like TripAdvisor or Yelp.

You can even let users review certain parts of your website, like posts, products, pages, and more. 

It comes with a simple settings page that allows you to control how you want to gather reviews and how you want to display them on your website. You can customize the review form and display it anywhere on your site.

Pros

  • With Site Reviews, you can pin your best reviews to the top so your customers will see them first.
  • You can display your reviews using the included Gutenberg block or shortcode to show specific reviews. 

Cons

  • Site Reviews offers limited mobile responsiveness.
  • The plugin can potentially conflict with other plugins activated on your website.

Why we recommend Site Reviews: If you are looking for a free plugin to add a simple product reviews section, then you can use Site Reviews.

6. Customer Reviews for WooCommerce

Customer Reviews For WooCommerce

Customer Reviews for WooCommerce lets you easily add more detailed reviews to your store.

Your customers can attach pictures and vote on reviews, plus you can add a question and answer section so your product listings look the same as Amazon.

Overall, this plugin helps improve your social proof so that when visitors go to your product pages, they are more likely to make a purchase. 

Pros

  • It lets you send automated emails after a customer makes a purchase to help you generate more reviews. You can also send a coupon after a review is left on your product.
  • The plugin can integrate with Google Shopping, boost SEO, and support over 30 languages.
  • It allows you to approve, edit, or delete reviews, import/export reviews, and send review reminders on WhatsApp.

Cons

  • The plugin does not come with spam protection, so you may have to deal with spam, negative, or inappropriate reviews.
  • Users have had security concerns with the plugin in the past.

Why we recommend Customer Reviews for WooCommerce: This free plugin is a great option if you have a WooCommerce store due to its seamless integration and beginner-friendly interface.

Bonus Entries

7. OptinMonster

OptinMonster

OptinMonster is the best lead generation tool for WordPress and is used by over 1.2 million websites. You can easily create high-converting popup campaigns to grow your email list and get more sales for the products you review on your website.

It comes with a library of over 400 templates you can use to quickly create spin to win optins, alert bars, yes/no optins, and many other types of popup campaigns for your website. 

You can use these popups in creative ways. For example, you might create a popup that displays a coupon for the affiliate product you are reviewing in an article. 

OptinMonster affiliate popup example

This is a common way to increase affiliate income and earn more revenue. That’s why many of the most successful WordPress blogs use this technique in their product review articles.

Pros

  • You can target different users for various product reviews based on behavior, interests, or location.
  • The tool comes with a drag-and-drop builder.
  • You can optimize your opt-in forms with A/B testing.
  • The plugin can help you capture leads and generate revenue.

Cons

  • OptinMonster does not have a free plan, but its connecting plugin is free.
  • It does not have a dedicated product or customer reviews feature.

Why we recommend OptinMonster: If you are looking for a tool that can encourage users to buy products from your product reviews, then OptinMonster is a great option. You can use it to create popups that will offer discounts or coupons to users on product review pages.

For more information, see our OptinMonster review.

8. Uncanny Automator

Uncanny Automator website

Uncanny Automator is the best WordPress automation plugin. It lets you automate the process of collecting reviews from customers after they purchase a product.

You can also use the plugin to automatically post product reviews on Facebook or other social media platforms every time a user leaves feedback on your website. This can help boost your social media following and engagement.

Plus, Uncanny Automator helps you create other automated workflows on your site without any code, making it easier to manage your website and business.

Pros

  • The plugin acts like Zapier for WordPress and can connect with 150+ WordPress plugins and tools like Twilio, Instagram, Google Sheets, and Zoom.
  • It has a free plan, is super easy to use, and provides unlimited recipes and actions.
  • It can connect your product reviews with email marketing tools or CRM software.

Cons

  • Some of its features can only be unlocked in the pro version.
  • The plugin does not offer dedicated product review features.

Why we recommend Uncanny Automator: If you want to add your product reviews on platforms other than your WordPress site, then you can use Uncanny Automator to create an automated workflow.

For more details, you may like to see our Uncanny Automator review.

9. RafflePress

RafflePress

RafflePress is the best WordPress giveaway plugin on the market that allows you to get more customer reviews in exchange for entries into your giveaway/competition.

You can use it like a product review plugin by asking users to leave feedback on your website for your products if they want to enter your free giveaway.

RafflePress can also integrate with Trustpilot, Capterra, and G2 if you want users to leave product reviews on those platforms instead. All you will have to do is simply add these platforms as actions in the giveaway.

Once you do that, click on the block to open its settings in the block panel, where you can make the action mandatory. Now, if users want to enter a giveaway, then they must leave a product review first.

Product review giveaways

We have also used RafflePress on our own website to get more email subscribers. For details, you can see our insider look at how WPBeginner ran a successful viral giveaway with RafflePress.

Pros

  • The plugin comes with a drag-and-drop builder, premade giveaway templates, and complete fraud protection.
  • You can also ask users to share your products on their social media handles to enter the giveaway.
  • It can help capture leads and choose real-time winners.

Cons

  • It has a free plan, but you will need the pro version to unlock the product review actions.
  • RafflePress is not a dedicated product review plugin.

Why we recommend RafflePress: If you want to host giveaways or contests to boost engagement and get product reviews at the same time, then this is the plugin for you.

For more information, see our RafflePress review.

Which Is the Best WordPress Product Reviews Plugin?

In our expert opinion, WP Review Pro is the best WordPress product review plugin on the market. It comes with plenty of premade review templates, complete spam protection, and lets you add different rating systems for reviews.

However, if you want to boost search rankings with your product reviews, then we recommend All in One SEO for WordPress due to its amazing product reviews schema.

Similarly, if you want to add product reviews from another platform like Yelp, Google, TripAdvisor, or Facebook, then Smash Balloon Reviews Feed Pro is a good choice.

Alternatively, if you want to use a free plugin, then WP Customer Reviews is also a great option.

Frequently Asked Questions About WordPress Product Review Plugins

Here are some questions that are frequently asked by our readers about product review plugins for WordPress.

Do product review plugins help with SEO?

Product review plugins can help boost SEO if they have a schema markup feature. The plugins also add new content to your site, which can further improve search engine rankings.

Additionally, reviews left by customers usually contain relevant keywords that can help with your site’s SEO. If you want to use product reviews to improve your website’s ranking, then AIOSEO is the ideal choice.

Can I use product review plugins to encourage customers to leave reviews?

Most product review plugins do not contain any specific feature that can encourage users to leave reviews.

However, you can use a plugin like OptinMonster to generate attractive popups that will offer discount offers to customers if they visit the product review pages on your website.

If you want, you can also offer free giveaways to customers if they leave product reviews on your website or external review platforms.

How can I use product reviews to gather customer feedback and insights?

Popular plugins like WP Customer Reviews allow you to create product review forms where you can add different fields to gather feedback and insights from customers.

However, we recommend using a dedicated user feedback plugin to gather customer insights instead.

For example, you can use UserFeedback to add a customer survey prompt on your website.

UserFeedback Survey Preview

You can even use WPForms to create polls and surveys to collect feedback. For details, see our tutorial on how to create post purchase surveys in WooCommerce.

Best WordPress Guides for Product Reviews

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post 6+ Best Product Review Plugins for WordPress (2024) first appeared on WPBeginner.