react_on_rails icon indicating copy to clipboard operation
react_on_rails copied to clipboard

Turbo forms with react_component not rendering React

Open uvera opened this issue 1 year ago • 4 comments

So I'm using turbo streams for re-rendering forms when there are errors.

where I replace the form

edit.turbo_stream.erb

<%= turbo_stream.replace dom_id(@car), target: '_top' do %>
  <%= render partial: 'form' %>
<% end %>
<%= turbo_stream.replace "flashes", partial: "/flash" %>

where form _form.html.erb

<%= form_with model: @car, id: dom_id(@car), builder: CustomFormBuilder, multipart: true, url: @car.new_record? ? admin_dashboard_cars_path : admin_dashboard_car_path(@car) do |f| %>
...
    <%= car_images_upload_input(@car) %>

    <%= signal_react_rerender %>
...

where controller actions

      def update
        @car = Car.find_by(id: params[:id])
        @car.assign_attributes(car_params)
        @car.release_date = Date.new(car_params[:release_date]&.to_i)

        if @car.save
          redirect_success
        else
          form_respond('edit')
        end
      end

      private

      def redirect_success
        redirect_to admin_dashboard_cars_path(format: :html), notice: I18n.t('flash.update.success')
      end

      def form_respond(template)
        respond_to do |format|
          format.html do
            flash[:error] = I18n.t('flash.update.error')
            render template
          end
          format.turbo_stream do
            flash.now[:error] = I18n.t('flash.update.error')
            render template
          end
        end
      end

The issue arises when I submit the form, react_component does not get re-rendered/hydrated even with

ReactOnRails.setOptions({ turbo: true });

The workaround I found is event listening for "turbo:before-stream-render"

const debouncedHandlerForNodes = debounce(() => {
  const nodes = document.querySelectorAll("[data-signal-react-rerender]");
  if (nodes.length) {
    ReactOnRails.reactOnRailsPageLoaded();

    nodes.forEach((each) => each.remove());
  }
}, 100);

document.addEventListener("turbo:load", turboReloadJs);
document.addEventListener("turbo:before-stream-render", function () {
  debouncedHandlerForNodes();
});

turboReloadJs is just for re-initializing flowbite/tailwindcss this selector [data-signal-react-rerender] is searching for span tag from signal_react_rerender

  def signal_react_rerender
    content_tag(:span, nil, data: { signal_react_rerender: true }, class: 'hidden')
  end

Another thing

Something I noticed is without debounced and delayed call to ReactOnRails.reactOnRailsPageLoaded(); console warns with Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.

This is caused because this event gets called twice and of course, it's happening before stream rendering.

Environment

  1. Ruby version: 3.1.2
  2. Rails version: 7.0.4
  3. Webpacker version: 6.5
  4. React on Rails version: 13.2

Expected behavior

ReactOnRails should re-render/hydrate components after turbo-stream form replacement where we have react_component helper called within the replaced form

Actual behavior

Components do not get re-rendered/hydrated after turbo-stream form replacement

Small, reproducible repo

https://github.com/uvera/React-on-rails-turbo-js-bug-reproduction

uvera avatar Jan 20 '23 11:01 uvera