Skip to content

Commit

Permalink
New Navigate component (#34)
Browse files Browse the repository at this point in the history
- Create `navigate` component
- Better key value identity of route components
- Remove initial URL handling from the `History` component due to reactpy rendering bugs
   - This is now handled by a new `FirstLoad` element.
- Fix docs publishing workflow
- Add arg descriptions to all public functions
- Better styling for autodocs.
- Support Python 3.12
  • Loading branch information
Archmonger authored Oct 17, 2024
1 parent 0c04073 commit 2f9be01
Show file tree
Hide file tree
Showing 22 changed files with 486 additions and 162 deletions.
1 change: 0 additions & 1 deletion .github/workflows/test-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
pip install -r requirements/build-docs.txt
pip install -r requirements/check-types.txt
pip install -r requirements/check-style.txt
pip install -e .
- name: Check docs build
run: |
linkcheckMarkdown docs/ -v -r
Expand Down
70 changes: 35 additions & 35 deletions .github/workflows/test-src.yaml
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
name: Test

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "0 0 * * *"
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "0 0 * * *"

jobs:
source:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Python Dependencies
run: pip install -r requirements/test-run.txt
- name: Run Tests
run: nox -t test
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Latest Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install Python Dependencies
run: pip install -r requirements/test-run.txt
- name: Run Tests
run: nox -t test -- --coverage
source:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Python Dependencies
run: pip install -r requirements/test-run.txt
- name: Run Tests
run: nox -t test
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Latest Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install Python Dependencies
run: pip install -r requirements/test-run.txt
- name: Run Tests
run: nox -t test -- --coverage
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ Using the following categories, list your changes in this order:
- Rename `CONVERSION_TYPES` to `CONVERTERS`.
- Change "Match Any" syntax from a star `*` to `{name:any}`.
- Rewrite `reactpy_router.link` to be a server-side component.
- Simplified top-level exports within `reactpy_router`.
- Simplified top-level exports that are available within `reactpy_router.*`.

### Added

- New error for ReactPy router elements being used outside router context.
- Configurable/inheritable `Resolver` base class.
- Add debug log message for when there are no router matches.
- Add slug as a supported type.
- Add `reactpy_router.navigate` component that will force the client to navigate to a new URL (when rendered).
- New error for ReactPy router elements being used outside router context.
- Configurable/inheritable `Resolver` base class.

### Fixed

Expand All @@ -58,6 +59,7 @@ Using the following categories, list your changes in this order:
- Fix bug where `link` elements could not have `@component` type children.
- Fix bug where the ReactPy would not detect the current URL after a reconnection.
- Fix bug where `ctrl` + `click` on a `link` element would not open in a new tab.
- Fix test suite on Windows machines.

## [0.1.1] - 2023-12-13

Expand Down
1 change: 1 addition & 0 deletions docs/examples/python/basic-routing-more-routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from reactpy import component, html, run

from reactpy_router import browser_router, route


Expand Down
1 change: 1 addition & 0 deletions docs/examples/python/basic-routing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from reactpy import component, html, run

from reactpy_router import browser_router, route


Expand Down
17 changes: 15 additions & 2 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ nav:
- Hooks: learn/hooks.md
- Creating a Custom Router 🚧: learn/custom-router.md
- Reference:
- Router Components: reference/router.md
- Routers: reference/routers.md
- Components: reference/components.md
- Hooks: reference/hooks.md
- Types: reference/types.md
Expand Down Expand Up @@ -96,8 +96,21 @@ plugins:
- https://reactpy.dev/docs/objects.inv
- https://installer.readthedocs.io/en/stable/objects.inv
options:
show_bases: false
signature_crossrefs: true
scoped_crossrefs: true
relative_crossrefs: true
modernize_annotations: true
unwrap_annotated: true
find_stubs_package: true
show_root_members_full_path: true
show_bases: false
show_source: false
show_root_toc_entry: false
show_labels: false
show_symbol_type_toc: true
show_symbol_type_heading: true
show_object_full_path: true
heading_level: 3
extra:
generator: false
version:
Expand Down
1 change: 1 addition & 0 deletions docs/src/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ misconfiguration
misconfigurations
backhaul
sublicense
contravariant
2 changes: 1 addition & 1 deletion docs/src/reference/components.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
::: reactpy_router

options:
members: ["route", "link"]
members: ["route", "link", "navigate"]
File renamed without changes.
4 changes: 4 additions & 0 deletions docs/src/reference/types.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
::: reactpy_router.types

options:
summary: true
docstring_section_style: "list"
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ line-length = 120

[tool.pytest.ini_options]
testpaths = "tests"
asyncio_mode = "auto"
2 changes: 2 additions & 0 deletions requirements/build-docs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ mkdocs-minify-plugin
mkdocs-section-index
mike
mkdocstrings[python]
black # for mkdocstrings automatic code formatting
.
2 changes: 1 addition & 1 deletion requirements/test-env.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
twine
pytest
pytest-asyncio
anyio
pytest-cov
reactpy[testing,starlette]
nodejs-bin==18.4.0a4
106 changes: 88 additions & 18 deletions src/js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ export function bind(node) {
};
}

export function History({ onHistoryChange }) {
// Capture browser "history go back" action and tell the server about it
// Note: Browsers do not allow us to detect "history go forward" actions.
/**
* History component that captures browser "history go back" actions and notifies the server.
*
* @param {Object} props - The properties object.
* @param {Function} props.onHistoryChangeCallback - Callback function to notify the server about history changes.
* @returns {null} This component does not render any visible output.
* @description
* This component uses the `popstate` event to detect when the user navigates back in the browser history.
* It then calls the `onHistoryChangeCallback` with the current pathname and search parameters.
* Note: Browsers do not allow detection of "history go forward" actions.
* @see https://github.com/reactive-python/reactpy/pull/1224
*/
export function History({ onHistoryChangeCallback }) {
React.useEffect(() => {
// Register a listener for the "popstate" event and send data back to the server using the `onHistoryChange` callback.
const listener = () => {
onHistoryChange({
onHistoryChangeCallback({
pathname: window.location.pathname,
search: window.location.search,
});
Expand All @@ -32,31 +42,42 @@ export function History({ onHistoryChange }) {
});

// Tell the server about the URL during the initial page load
// FIXME: This currently runs every time any component is mounted due to a ReactPy core rendering bug.
// FIXME: This code is commented out since it currently runs every time any component
// is mounted due to a ReactPy core rendering bug. `FirstLoad` component is used instead.
// https://github.com/reactive-python/reactpy/pull/1224
React.useEffect(() => {
onHistoryChange({
pathname: window.location.pathname,
search: window.location.search,
});
return () => {};
}, []);

// React.useEffect(() => {
// onHistoryChange({
// pathname: window.location.pathname,
// search: window.location.search,
// });
// return () => {};
// }, []);
return null;
}

// FIXME: The Link component is unused due to a ReactPy core rendering bug
// which causes duplicate rendering (and thus duplicate event listeners).
// https://github.com/reactive-python/reactpy/pull/1224
export function Link({ onClick, linkClass }) {
/**
* Link component that captures clicks on anchor links and notifies the server.
*
* @param {Object} props - The properties object.
* @param {Function} props.onClickCallback - Callback function to notify the server about link clicks.
* @param {string} props.linkClass - The class name of the anchor link.
* @returns {null} This component does not render any visible output.
*/
export function Link({ onClickCallback, linkClass }) {
// FIXME: This component is currently unused due to a ReactPy core rendering bug
// which causes duplicate rendering (and thus duplicate event listeners).
// https://github.com/reactive-python/reactpy/pull/1224

// This component is not the actual anchor link.
// It is an event listener for the link component created by ReactPy.
React.useEffect(() => {
// Event function that will tell the server about clicks
const handleClick = (event) => {
event.preventDefault();
let to = event.target.getAttribute("href");
window.history.pushState({}, to, new URL(to, window.location));
onClick({
window.history.pushState(null, "", new URL(to, window.location));
onClickCallback({
pathname: window.location.pathname,
search: window.location.search,
});
Expand All @@ -78,3 +99,52 @@ export function Link({ onClick, linkClass }) {
});
return null;
}

/**
* Client-side portion of the navigate component, that allows the server to command the client to change URLs.
*
* @param {Object} props - The properties object.
* @param {Function} props.onNavigateCallback - Callback function that transmits data to the server.
* @param {string} props.to - The target URL to navigate to.
* @param {boolean} props.replace - If true, replaces the current history entry instead of adding a new one.
* @returns {null} This component does not render anything.
*/
export function Navigate({ onNavigateCallback, to, replace }) {
React.useEffect(() => {
if (replace) {
window.history.replaceState(null, "", new URL(to, window.location));
} else {
window.history.pushState(null, "", new URL(to, window.location));
}
onNavigateCallback({
pathname: window.location.pathname,
search: window.location.search,
});
return () => {};
}, []);

return null;
}

/**
* FirstLoad component that captures the URL during the initial page load and notifies the server.
*
* @param {Object} props - The properties object.
* @param {Function} props.onFirstLoadCallback - Callback function to notify the server about the first load.
* @returns {null} This component does not render any visible output.
* @description
* This component sends the current URL to the server during the initial page load.
* @see https://github.com/reactive-python/reactpy/pull/1224
*/
export function FirstLoad({ onFirstLoadCallback }) {
// FIXME: This component only exists because of a ReactPy core rendering bug, and should be removed when the bug
// is fixed. Ideally all this logic would be handled by the `History` component.
React.useEffect(() => {
onFirstLoadCallback({
pathname: window.location.pathname,
search: window.location.search,
});
return () => {};
}, []);
return null;
}
3 changes: 2 additions & 1 deletion src/reactpy_router/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__version__ = "0.1.1"


from .components import link, route
from .components import link, navigate, route
from .hooks import use_params, use_search_params
from .routers import browser_router, create_router

Expand All @@ -13,4 +13,5 @@
"browser_router",
"use_params",
"use_search_params",
"navigate",
)
Loading

0 comments on commit 2f9be01

Please sign in to comment.