Phoenix LiveView 'render/2 is undefined or private' error

I am working on the youtube tutorial from Alchemist Camp - Simple Phoenix LiveView App. It was created using early versions of LiveView. I am trying to complete it using v. 0.16.4. I’ve made it partway through, but I encountered an issue I cannot overcome.

I’m in the process of creating live views. The root route (not a live view) works, but when I try “/users”, I get this:

Request: GET /users
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function ReactorWeb.UserView.render/2 is undefined or private

Here is the scope from router.ex:

scope "/", ReactorWeb do
    pipe_through :browser
    live "/users", UserLive.Index
    live "/users/new", UserLive.New
    live "/users/:id", UserLive.Show
    live "/users/:id/edit", UserLive.Edit

    resources "/comments", CommentController
    resources "/podcast", PodcastController

    get "/", PageController, :index
  end

This is the UserLive.Index module:

defmodule ReactorWeb.UserLive.Index do
  use Phoenix.LiveView
  alias Reactor.Accounts
  alias ReactorWeb.UserView

  **def render(assigns), do: UserView.render("index.html", assigns)** <- this line flagged by VS Code

  def mount(_params, _session, socket) do
 ...
end

T

his is the file *user_view.ex*:
**defmodule ReactorWeb.UserView do** -< also flagged by VS Code
  use ReactorWeb, :live_view

  def template_not_found(template, _assigns) do
    Phoenix.Controller.status_message_from_template(template)
  end
end

VS Code shows this message regarding the first line: render/1 was not implemented for ReactorWeb.UserView.

I have a template template/user/index.html.leex.

I went through the installation section of the LIveView docs to make the recommended changes in config.exs, *_web.ex, router.ex, and endpoint.ex.

I suspect I am missing a use, alias, or import somewhere, but I can’t determine where.

Thanks!

According to this post, this is the way to delegate to a Phoenix.View

defmodule MyAppWeb.SampleLive do
  use MyAppWeb, :live_view
  def render(assigns) do
    Phoenix.View.render(MyAppWeb.PageView, "page.html", assigns)
  end
end

Here you will have your template file placed inside lib/my_app_web/templates/page/page.html.heex and Phoenix view at lib/my_app_web/views/page_view.ex

I tried that, but the error function ReactorWeb.UserView.render/2 is undefined or private still comes up when I go to the “/users” route. However, the errors related to the render function in VS Code are gone!

To be clear, this is what I changed in the UserLive module:

defmodule ReactorWeb.UserLive.Index do
  use Phoenix.LiveView
  alias Reactor.Accounts
  alias ReactorWeb.UserView

  **def render(assigns), do: Phoenix.View.render(UserView, "index.html", assigns)**
  ...

So I still don’t know where the error is coming from.

defmodule ReactorWeb.UserView do
  use ReactorWeb, :live_view

should be

defmodule ReactorWeb.UserView do
  use ReactorWeb, :view

use ReactorWeb, :view will call use Phoenix.View, root: "lib/reactor_web/templates", namespace: ReactorWeb, which defines render/2 for you

Have a look at the difference between def view and def live_view in your lib/reactor_web.ex

Phoenix.View.render/3 calls the render/2 function in the module passed in as its first argument. In your case that’s UserView, which is why you have to use ReactorWeb, :view so that it can be created.

I confused because I’m not calling render/2. The line in question is:
def render(assigns), do: Phoenix.View.render(UserView, "index.html", assigns)
which is calling the render/3 function from Phoenix.View.

When I change to using :view, now there is an error lib/reactor_web/views/user_view.ex:4: def render/1 conflicts with defaults from render/2.

I think a lot of my problems with this project have to do with trying to take code from LiveView 0.5.1 and getting it to work in 0.16.4. I don’t have a good guide as to how to render live views, from controller to the template. For example, that same line calling Phoenix.View.render/3 is in the reactor_web/views/user_view.ex and reactor_web/live/user_live/index.ex files. Putting live view files into a /live folder is how the project is organized. I have not see any information in the LiveView docs or any other tutorial I can find that shows how a v. 0.16 project should be organized.

Yes unfortunately trying to upgrade code from an old implementation to new can be tricky.

Sounds like user_view.ex is defining its own render/1 function on line 4?

Perhaps it would be a good exercise to generate a fresh mix phx.new project and experiment with creating the same schema/context for a Phoenix view and a Phoenix LiveView and study the differencece.

$ mix phx.gen.html Accounts User users name:string
$ mix phx.gen.live Accounts User users name:string --no-context --no-schema

While you’re at it, you could upgrade to the latest v1.6.6 by first executing mix archive.install hex phx_new latest

After looking at the LiveView docs again, and checking out some example projects, I see a big problem with my code. I have 2 LiveView modules, each with a render function! Following the video for the project, it follows the earlier format with a view file in reactor_web/views and a template in reactor_web/templatest, along with a controller. I assumed that the reactor_web/live//users/index.ex file (the ReactorWeb.UserLive.Index module) was substituting as a controller. It’s actually a LiveView module.

Based on the docs, I am going to co-locate the LiveView module and template in the live folder, which is recommended. If those files are co-located, then *render’ doesn’t have to be called, and no files in the views and templates folders are needed. Much simpler.

2 Likes

Glad you managed to understand and figure out your system needs.

I feel at this point in time having a separate standard view is good for separation of concerns if you have a bunch of view logic and templates that you want to share across a number of pages.

However the general strategy for tutorials and initial app development it seems good enough to shove everything directly in the live view logic and co-located templates, and then worry about refactoring to separate views as needed in the future.

Another reason is also if you’re rendering a lot of your page statically, and only have sections of it as live view. But at the moment I’m using full live views so I hardly touch standard views.