Skip to main content
  1. /classes/
  2. Classes, Fall 2025/
  3. CS 4140 Fall 2025: Course Site/

cs4140 Notes: 09-12 LiveView Story

·389 words·2 mins·

Do A Real Deploy

  • Deploy the latest main on shard.homework.quest.
  • Try to log in.

Form -> LiveView Demo

(demo was started with)

mix phx.new lvdemo --database sqlite3
cd lvdemo
mix ecto.setup

Let’s look at the demo app from the scratch repo.

Part 1: A Form

In router:

  • Path “/form” has a route for both get and post.
  • We submit a form, the server can re-render the page.

Part 2: A GenServer

  • CommentServer
  • Note that it needs to get started from Application
  • Demo it with iex -S mix

Part 3: A Channel + JS

  • chan.html.heex
  • comment_channel.js (user socket, app.js, etc)

Part 4: A LiveView

  • Template, similar to a “dead view”.
  • Associated elixir module with callbacks.
  • There’s an implicit channel (which is a GenServer) here.
    • Page on client is kept in sync with state on server.
  • We’re using a .form / .input setup, which would be typical for a deadview too (e.g. it handles _csrf_token).

title: “cs4140 Notes: 19 React” date: “2024-10-04”
#

Let’s set up React and a React component for PartyAnimal

cd assets
corepack enable pnpm
pnpm add react
pnpm add flowbite-react

Update tailwind config to look in jsx files:

module.exports = {
  content: [
    "./js/**/*.js",
    "./js/**/*.jsx",  // Here

In app.js:

import "./invites/main";

In invites/main.jsx:

import React from 'react';
import { createRoot } from 'react-dom/client';

import Invites from './invites';

const root_div = document.getElementById('invites-main');
if (root_div) {
  const root = createRoot(root_div);
  root.render(<Invites />);
}

In invites/invites.jsx:

import React from 'react';
import { createRoot } from 'react-dom/client';

import { Card } from 'flowbite-react';

export default function Invites(props) {
  return (
    <div className="flex items-center justify-center">
      <Card className="max-w-sm">
        <p>Invites go here</p>
      </Card>
    </div>
  );
}

In …/page_html/home.html.heex:

  <div id="invites-main">
    React component loading...
  </div>

In invites/api.js:

const base = "/ajax/invites";

export async function list_invites() {
  let resp = await fetch(base, {
    headers: {
      'Accept': 'application/json',
    }
  });
  return await resp.json();
}

And now update invites.jsx:

import { list_invites } from './api';

export default function Invites(props) {
  const [invites, setInvites] = useState([]);

  useEffect(() => {
    list_invites().then((xs) => setInvites(xs));
  }, []);

  console.log(invites);
  let invite_items = invites.map((item, ii) => (
    <li key={ii}>
      { item.name }
    </li>
  ));

  return (
    <div className="flex items-center justify-center">
      <Card className="max-w-sm">
        <ul>
          { invite_items }
        </ul>
      </Card>
    </div>
  );
}

Digression: Phoenix UI
#

We don’t need to push to React just to get already styled UI components:

Nat Tuck
Author
Nat Tuck