Skip to content

Commit

Permalink
Add local-component example (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
masenf committed Jun 20, 2024
1 parent c840c93 commit bf00173
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 0 deletions.
5 changes: 5 additions & 0 deletions local-component/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.db
*.py[cod]
.web
__pycache__/
assets/external
7 changes: 7 additions & 0 deletions local-component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Local Component

This example shows how to create a React component in a JS file along with a
Reflex wrapper in python that allows its use in a Reflex app.

The custom component is `local_component/hello.js` and is a standard React
component that displays a greeting with some additional functionality.
Binary file added local-component/assets/favicon.ico
Binary file not shown.
Empty file.
34 changes: 34 additions & 0 deletions local-component/local_component/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* This is an example inline React component that demonstrates how to pass a subset of props
* to the top-level `div` element.
*
* This component also demonstrates how to use `useMemo` to memoize the message
* string, and an event handler that updates a state variable when the user
* right-clicks on the component.
*/
import React, { useMemo, useState } from 'react';

// React.forwardRef is used to allow the parent component to pass a ref to this component.
export const Hello = React.forwardRef((props, ref) => {
// Extract the `name` prop that we care about, and pass any other props directly to the top `div`.
const {name, ...divProps} = props;

// This state variable controls whether the message will be all caps.
const [cap, setCap] = useState(false);

// Memoize the message to avoid re-calculating it on every render. Only re-calculate
// when `cap` or `name` changes.
const message = useMemo(() => {
const message = `Hello${name ? ` ${name}` : ""}!`;
return cap ? message.toUpperCase() : message;
}, [cap, name])

return (
// Pass ref and remaining props to the top-level `div`.
<div ref={ref} {...divProps}>
<h1 onContextMenu={(e) => {e.preventDefault(); setCap((cap) => !cap)}}>
{message}
</h1>
</div>
)
})
23 changes: 23 additions & 0 deletions local-component/local_component/hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""This component wraps the local `hello.js` which is supplied as an external asset."""
import reflex as rx

# Because the top-level element in Hello is a `<div>` and all other props are passed
# directly through, we can subclass `Div` to get the base behavior automatically.
from reflex.components.el.elements.typography import Div

# Defining the asset with a module-relative path will result in copying the
# file into a subdir of the .web/public directory
component_asset = rx._x.asset("hello.js")


class Hello(Div):
# To access the JS as an import, use an absolute path (rooted in `.web`)
# through `/public` where the assets exist in the frontend build.
library = f"/public/{component_asset}"

tag = "Hello"

name: rx.Var[str]


hello = Hello.create
76 changes: 76 additions & 0 deletions local-component/local_component/local_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import reflex as rx

from .hello import hello


class State(rx.State):
open: bool = False
who: str = "world"
saved_value: str = ""

def handle_open_change(self, open):
if open:
self.saved_value = self.who
else:
self.who = self.saved_value
self.open = open

def handle_submit(self, form_data):
who = form_data.get("who")
self.saved_value = who or "world"
self.handle_open_change(open=False)


def popover_editor(trigger):
return rx.popover.root(
rx.popover.trigger(rx.box(trigger)),
rx.popover.content(
rx.form.root(
rx.input(
placeholder="Who are you?",
name="who",
on_change=State.set_who,
),
rx.popover.close(
rx.button(display="none"),
),
on_submit=State.handle_submit,
),
),
open=State.open,
on_open_change=State.handle_open_change,
)


def index() -> rx.Component:
return rx.container(
rx.color_mode.button(position="top-right"),
rx.vstack(
rx.heading("Local Component Example", size="9"),
popover_editor(
hello(
name=State.who,
id="greeting", # React.forwardRef lets us handle refs
title="Click to change name. Right-click to toggle caps.",
on_context_menu=rx.console_log("Yes we pass events through").prevent_default,
background_color=rx.color_mode_cond(
light="papayawhip",
dark="rebeccapurple",
),
),
),
spacing="5",
justify="center",
min_height="85vh",
),
rx.logo(),
rx.button(
"Scroll to Greeting",
on_click=rx.scroll_to("greeting"),
margin_top="150vh",
),
)


app = rx.App()
app.add_page(index)
1 change: 1 addition & 0 deletions local-component/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reflex>=0.5.4
5 changes: 5 additions & 0 deletions local-component/rxconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import reflex as rx

config = rx.Config(
app_name="local_component",
)

0 comments on commit bf00173

Please sign in to comment.