Skip to content

Commit ef242db

Browse files
committed
Only Once JS prototype
1 parent 464b4e2 commit ef242db

File tree

5 files changed

+66
-6
lines changed

5 files changed

+66
-6
lines changed

src/js/src/components.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DjangoFormProps } from "./types";
1+
import { DjangoFormProps, OnlyOnceProps } from "./types";
22
import React from "react";
33
import ReactDOM from "react-dom";
44
/**
@@ -62,3 +62,42 @@ export function DjangoForm({
6262

6363
return null;
6464
}
65+
66+
export function OnlyOnceJS({ jsPath, autoRemove }: OnlyOnceProps): null {
67+
React.useEffect(() => {
68+
// Check if the script element already exists
69+
let el = document.head.querySelector(
70+
"script.reactpy-staticfile[src='" + jsPath + "']",
71+
);
72+
73+
// Create a new script element, if needed
74+
if (el === null) {
75+
el = document.createElement("script");
76+
el.className = "reactpy-staticfile";
77+
if (jsPath) {
78+
el.setAttribute("src", jsPath);
79+
}
80+
document.head.appendChild(el);
81+
}
82+
83+
// If requested, auto remove the script when it is no longer needed
84+
if (autoRemove) {
85+
// Keep track of the number of ReactPy components that are dependent on this script
86+
let count = Number(el.getAttribute("data-count"));
87+
count += 1;
88+
el.setAttribute("data-count", count.toString());
89+
90+
// Remove the script element when the last dependent component is unmounted
91+
return () => {
92+
count -= 1;
93+
if (count === 0) {
94+
el.remove();
95+
} else {
96+
el.setAttribute("data-count", count.toString());
97+
}
98+
};
99+
}
100+
}, []);
101+
102+
return null;
103+
}

src/js/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { DjangoForm, bind } from "./components";
1+
export { OnlyOnceJS, DjangoForm, bind } from "./components";
22
export { mountComponent } from "./mount";

src/js/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ export interface DjangoFormProps {
2323
onSubmitCallback: (data: Object) => void;
2424
formId: string;
2525
}
26+
27+
export interface OnlyOnceProps {
28+
jsPath: string;
29+
autoRemove: boolean;
30+
}

src/reactpy_django/components.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from __future__ import annotations
44

55
import json
6+
from pathlib import Path
67
from typing import TYPE_CHECKING, Any, Callable, Union, cast
78
from urllib.parse import urlencode
89

910
from django.http import HttpRequest
11+
from django.templatetags.static import static
1012
from django.urls import reverse
11-
from reactpy import component, hooks, html, utils
13+
from reactpy import component, hooks, html, utils, web
1214
from reactpy.types import ComponentType, Key, VdomDict
1315

1416
from reactpy_django.exceptions import ViewNotRegisteredError
@@ -25,6 +27,12 @@
2527
from reactpy_django.types import AsyncFormEvent, SyncFormEvent, ViewToComponentConstructor, ViewToIframeConstructor
2628

2729

30+
DjangoJS = web.export(
31+
web.module_from_file("reactpy-django", file=Path(__file__).parent / "static" / "reactpy_django" / "client.js"),
32+
("OnlyOnceJS"),
33+
)
34+
35+
2836
def view_to_component(
2937
view: Callable | View | str,
3038
transforms: Sequence[Callable[[VdomDict], Any]] = (),
@@ -97,7 +105,9 @@ def django_css(static_path: str, key: Key | None = None) -> ComponentType:
97105
return _django_css(static_path=static_path, key=key)
98106

99107

100-
def django_js(static_path: str, key: Key | None = None) -> ComponentType:
108+
def django_js(
109+
static_path: str, only_once: bool = False, only_once_auto_remove: bool = False, key: Key | None = None
110+
) -> ComponentType:
101111
"""Fetches a JS static file for use within ReactPy. This allows for deferred JS loading.
102112
103113
Args:
@@ -107,7 +117,9 @@ def django_js(static_path: str, key: Key | None = None) -> ComponentType:
107117
immediate siblings
108118
"""
109119

110-
return _django_js(static_path=static_path, key=key)
120+
return _django_js(
121+
static_path=static_path, only_once=only_once, only_once_auto_remove=only_once_auto_remove, key=key
122+
)
111123

112124

113125
def django_form(
@@ -278,5 +290,8 @@ def _django_css(static_path: str):
278290

279291

280292
@component
281-
def _django_js(static_path: str):
293+
def _django_js(static_path: str, only_once: bool, only_once_auto_remove: bool):
294+
if only_once:
295+
return DjangoJS({"jsPath": static(static_path), "autoRemove": only_once_auto_remove})
296+
282297
return html.script(cached_static_file(static_path))

src/reactpy_django/utils.py

+1
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ def wrapper(*args, **kwargs):
492492

493493

494494
def cached_static_file(static_path: str) -> str:
495+
"""Fetches a static file from Django and caches it for future use."""
495496
from reactpy_django.config import REACTPY_CACHE
496497

497498
# Try to find the file within Django's static files

0 commit comments

Comments
 (0)