@@ -18,30 +18,288 @@ cd reactpy-router
18
18
pip install -e . -r requirements.txt
19
19
```
20
20
21
- # Running the Tests
21
+ # Usage
22
22
23
- To run the tests you'll need to install [ Chrome] ( https://www.google.com/chrome/ ) . Then you
24
- can download the [ ChromeDriver] ( https://chromedriver.chromium.org/downloads ) and add it to
25
- your ` PATH ` . Once that's done, simply ` pip ` install the requirements:
23
+ Assuming you are familiar with the basics of [ ReactPy] ( https://reactpy.dev ) , you can
24
+ begin by using the simple built-in router implementation supplied by ` reactpy-router ` .
26
25
27
- ``` bash
28
- pip install -r requirements.txt
26
+ ``` python
27
+ from reactpy import component, html, run
28
+ from reactpy_router import route, simple
29
+
30
+ @component
31
+ def root ():
32
+ return simple.router(
33
+ route(" /" , html.h1(" Home Page 🏠" )),
34
+ route(" *" , html.h1(" Missing Link 🔗💥" )),
35
+ )
36
+
37
+ run(root)
29
38
```
30
39
31
- And run the tests with ` pytest ` :
40
+ When navigating to http://127.0.0.1:8000 you should see "Home Page 🏠". However, if you
41
+ go to any other route (e.g. http://127.0.0.1:8000/missing ) you will instead see the
42
+ "Missing Link 🔗💥" page.
32
43
33
- ``` bash
34
- pytest tests
44
+ With this foundation you can start adding more routes:
45
+
46
+ ``` python
47
+ from reactpy import component, html, run
48
+ from reactpy_router import route, simple
49
+
50
+ @component
51
+ def root ():
52
+ return simple.router(
53
+ route(" /" , html.h1(" Home Page 🏠" )),
54
+ route(" /messages" , html.h1(" Messages 💬" )),
55
+ route(" *" , html.h1(" Missing Link 🔗💥" )),
56
+ )
57
+
58
+ run(root)
59
+ ```
60
+
61
+ With this change you can now also go to ` /messages ` to see "Messages 💬" displayed.
62
+
63
+ # Route Links
64
+
65
+ Instead of using the standard ` <a> ` element to create links to different parts of your
66
+ application, use ` reactpy_router.link ` instead. When users click links constructed using
67
+ ` reactpy_router.link ` , instead of letting the browser navigate to the associated route,
68
+ ReactPy will more quickly handle the transition by avoiding the cost of a full page
69
+ load.
70
+
71
+ ``` python
72
+ from reactpy import component, html, run
73
+ from reactpy_router import link, route, simple
74
+
75
+ @component
76
+ def root ():
77
+ return simple.router(
78
+ route(" /" , home()),
79
+ route(" /messages" , html.h1(" Messages 💬" )),
80
+ route(" *" , html.h1(" Missing Link 🔗💥" )),
81
+ )
82
+
83
+ @component
84
+ def home ():
85
+ return html.div(
86
+ html.h1(" Home Page 🏠" ),
87
+ link(" Messages" , to = " /messages" ),
88
+ )
89
+
90
+ run(root)
91
+ ```
92
+
93
+ Now, when you go to the home page, you can click the link to go to ` /messages ` .
94
+
95
+ ## Nested Routes
96
+
97
+ Routes can be nested in order to construct more complicated application structures:
98
+
99
+ ``` python
100
+ from reactpy import component, html, run
101
+ from reactpy_router import route, simple, link
102
+
103
+ message_data = [
104
+ {" id" : 1 , " with" : [" Alice" ], " from" : None , " message" : " Hello!" },
105
+ {" id" : 2 , " with" : [" Alice" ], " from" : " Alice" , " message" : " How's it going?" },
106
+ {" id" : 3 , " with" : [" Alice" ], " from" : None , " message" : " Good, you?" },
107
+ {" id" : 4 , " with" : [" Alice" ], " from" : " Alice" , " message" : " Good, thanks!" },
108
+ {" id" : 5 , " with" : [" Alice" , " Bob" ], " from" : None , " message" : " We meeting now?" },
109
+ {" id" : 6 , " with" : [" Alice" , " Bob" ], " from" : " Alice" , " message" : " Not sure." },
110
+ {" id" : 7 , " with" : [" Alice" , " Bob" ], " from" : " Bob" , " message" : " I'm here!" },
111
+ {" id" : 8 , " with" : [" Alice" , " Bob" ], " from" : None , " message" : " Great!" },
112
+ ]
113
+
114
+ @component
115
+ def root ():
116
+ return simple.router(
117
+ route(" /" , home()),
118
+ route(
119
+ " /messages" ,
120
+ all_messages(),
121
+ # we'll improve upon these manually created routes in the next section...
122
+ route(" /with/Alice" , messages_with(" Alice" )),
123
+ route(" /with/Alice-Bob" , messages_with(" Alice" , " Bob" )),
124
+ ),
125
+ route(" *" , html.h1(" Missing Link 🔗💥" )),
126
+ )
127
+
128
+ @component
129
+ def home ():
130
+ return html.div(
131
+ html.h1(" Home Page 🏠" ),
132
+ link(" Messages" , to = " /messages" ),
133
+ )
134
+
135
+ @component
136
+ def all_messages ():
137
+ last_messages = {
138
+ " , " .join(msg[" with" ]): msg
139
+ for msg in sorted (message_data, key = lambda m : m[" id" ])
140
+ }
141
+ return html.div(
142
+ html.h1(" All Messages 💬" ),
143
+ html.ul(
144
+ [
145
+ html.li(
146
+ {" key" : msg[" id" ]},
147
+ html.p(
148
+ link(
149
+ f " Conversation with: { ' , ' .join(msg[' with' ])} " ,
150
+ to = f " /messages/with/ { ' -' .join(msg[' with' ])} " ,
151
+ ),
152
+ ),
153
+ f " { ' ' if msg[' from' ] is None else ' 🔴' } { msg[' message' ]} " ,
154
+ )
155
+ for msg in last_messages.values()
156
+ ]
157
+ ),
158
+ )
159
+
160
+ @component
161
+ def messages_with (* names ):
162
+ names = set (names)
163
+ messages = [msg for msg in message_data if set (msg[" with" ]) == names]
164
+ return html.div(
165
+ html.h1(f " Messages with { ' , ' .join(names)} 💬 " ),
166
+ html.ul(
167
+ [
168
+ html.li(
169
+ {" key" : msg[" id" ]},
170
+ f " { msg[' from' ] or ' You' } : { msg[' message' ]} " ,
171
+ )
172
+ for msg in messages
173
+ ]
174
+ ),
175
+ )
176
+
177
+ run(root)
178
+ ```
179
+
180
+ ## Route Parameters
181
+
182
+ In the example above we had to manually create a ` messages_with(...) ` component for each
183
+ conversation. This would be better accomplished by defining a single route that declares
184
+ a "route parameters" instead. With the ` simple.router ` route parameters are declared
185
+ using the following syntax:
186
+
187
+ ```
188
+ /my/route/{param}
189
+ /my/route/{param:type}
190
+ ```
191
+
192
+ In this case, ` param ` is the name of the route parameter and the optionally declared
193
+ ` type ` specifies what kind of parameter it is. The available parameter types and what
194
+ patterns they match are are:
195
+
196
+ - str (default) - ` [^/]+ `
197
+ - int - ` \d+ `
198
+ - float - ` \d+(\.\d+)? `
199
+ - uuid - ` [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} `
200
+ - path - ` .+ `
201
+
202
+ Any parameters that have matched in the currently displayed route can then be consumed
203
+ with the ` use_params ` hook which returns a dictionary mapping the parameter names to
204
+ their values. Note that parameters with a declared type will be converted to is in the
205
+ parameters dictionary. So for example ` /my/route/{my_param:float} ` would match
206
+ ` /my/route/3.14 ` and have a parameter dictionary of ` {"my_param": 3.14} ` .
207
+
208
+ If we take this information and apply it to our growing example application we'd
209
+ substitute the manually constructed ` /messages/with ` routes with a single
210
+ ` /messages/with/{names} ` route:
211
+
212
+ ``` python
213
+ from reactpy import component, html, run
214
+ from reactpy_router import route, simple, link
215
+ from reactpy_router.core import use_params
216
+
217
+ message_data = [
218
+ {" id" : 1 , " with" : [" Alice" ], " from" : None , " message" : " Hello!" },
219
+ {" id" : 2 , " with" : [" Alice" ], " from" : " Alice" , " message" : " How's it going?" },
220
+ {" id" : 3 , " with" : [" Alice" ], " from" : None , " message" : " Good, you?" },
221
+ {" id" : 4 , " with" : [" Alice" ], " from" : " Alice" , " message" : " Good, thanks!" },
222
+ {" id" : 5 , " with" : [" Alice" , " Bob" ], " from" : None , " message" : " We meeting now?" },
223
+ {" id" : 6 , " with" : [" Alice" , " Bob" ], " from" : " Alice" , " message" : " Not sure." },
224
+ {" id" : 7 , " with" : [" Alice" , " Bob" ], " from" : " Bob" , " message" : " I'm here!" },
225
+ {" id" : 8 , " with" : [" Alice" , " Bob" ], " from" : None , " message" : " Great!" },
226
+ ]
227
+
228
+ @component
229
+ def root ():
230
+ return simple.router(
231
+ route(" /" , home()),
232
+ route(
233
+ " /messages" ,
234
+ all_messages(),
235
+ route(" /with/{names} " , messages_with()), # note the path param
236
+ ),
237
+ route(" *" , html.h1(" Missing Link 🔗💥" )),
238
+ )
239
+
240
+ @component
241
+ def home ():
242
+ return html.div(
243
+ html.h1(" Home Page 🏠" ),
244
+ link(" Messages" , to = " /messages" ),
245
+ )
246
+
247
+ @component
248
+ def all_messages ():
249
+ last_messages = {
250
+ " , " .join(msg[" with" ]): msg
251
+ for msg in sorted (message_data, key = lambda m : m[" id" ])
252
+ }
253
+ return html.div(
254
+ html.h1(" All Messages 💬" ),
255
+ html.ul(
256
+ [
257
+ html.li(
258
+ {" key" : msg[" id" ]},
259
+ html.p(
260
+ link(
261
+ f " Conversation with: { ' , ' .join(msg[' with' ])} " ,
262
+ to = f " /messages/with/ { ' -' .join(msg[' with' ])} " ,
263
+ ),
264
+ ),
265
+ f " { ' ' if msg[' from' ] is None else ' 🔴' } { msg[' message' ]} " ,
266
+ )
267
+ for msg in last_messages.values()
268
+ ]
269
+ ),
270
+ )
271
+
272
+ @component
273
+ def messages_with ():
274
+ names = set (use_params()[" names" ].split(" -" )) # and here we use the path param
275
+ messages = [msg for msg in message_data if set (msg[" with" ]) == names]
276
+ return html.div(
277
+ html.h1(f " Messages with { ' , ' .join(names)} 💬 " ),
278
+ html.ul(
279
+ [
280
+ html.li(
281
+ {" key" : msg[" id" ]},
282
+ f " { msg[' from' ] or ' You' } : { msg[' message' ]} " ,
283
+ )
284
+ for msg in messages
285
+ ]
286
+ ),
287
+ )
288
+
289
+ run(root)
35
290
```
36
291
37
- You can run the tests in headless mode (i.e. without opening the browser):
292
+ # Running the Tests
38
293
39
294
``` bash
40
- pytest tests
295
+ nox -s test
41
296
```
42
297
43
- You'll need to run in headless mode to execute the suite in continuous integration systems
44
- like GitHub Actions.
298
+ You can run the tests with a headed browser.
299
+
300
+ ``` bash
301
+ nox -s test -- --headed
302
+ ```
45
303
46
304
# Releasing This Package
47
305
0 commit comments