next_on_rails 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +591 -0
- data/Rakefile +32 -0
- data/app/controllers/next_on_rails/application_controller.rb +21 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/concerns/current_resource_serializer.rb +15 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/confirmations_controller.rb +4 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/omniauth_callbacks_controller.rb +7 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/passwords_controller.rb +23 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/registrations_controller.rb +33 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/sessions_controller.rb +27 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/token_validations_controller.rb +13 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/unlocks_controller.rb +23 -0
- data/app/controllers/next_on_rails/merger_controller.rb +36 -0
- data/app/serializers/next_on_rails/active_model_errors_serializer.rb +45 -0
- data/app/serializers/next_on_rails/errors_serializer.rb +16 -0
- data/config/routes.rb +12 -0
- data/lib/generators/nor/backend/backend_generator.rb +129 -0
- data/lib/generators/nor/backend/templates/Procfile +2 -0
- data/lib/generators/nor/backend/templates/application_controller.rb +2 -0
- data/lib/generators/nor/backend/templates/cors.rb +23 -0
- data/lib/generators/nor/backend/templates/current_user_serializer.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user_migration.rb.tt +53 -0
- data/lib/generators/nor/backend/templates/user_serializer.rb.tt +11 -0
- data/lib/generators/nor/frontend/frontend_generator.rb +60 -0
- data/lib/generators/nor/frontend/templates/frontend/components/flash.js +29 -0
- data/lib/generators/nor/frontend/templates/frontend/components/inputs.js +69 -0
- data/lib/generators/nor/frontend/templates/frontend/components/login-form.js +23 -0
- data/lib/generators/nor/frontend/templates/frontend/components/registration-form.js +49 -0
- data/lib/generators/nor/frontend/templates/frontend/next-on-rails.config.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/next.config.js +20 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/_app.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/index.js +57 -0
- data/lib/generators/nor/frontend/templates/frontend/server.js +31 -0
- data/lib/generators/nor/frontend/templates/frontend/stylesheets/application.scss +2 -0
- data/lib/generators/nor/install/USAGE +30 -0
- data/lib/generators/nor/install/install_generator.rb +8 -0
- data/lib/generators/nor/scaffold/USAGE +10 -0
- data/lib/generators/nor/scaffold/scaffold_generator.rb +53 -0
- data/lib/generators/nor/scaffold/templates/controller.rb.tt +40 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/details.js.tt +20 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/form.js.tt +17 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/crud.js.tt +123 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/edit.js.tt +55 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/index.js.tt +88 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/new.js.tt +51 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/show.js.tt +43 -0
- data/lib/next_on_rails.rb +12 -0
- data/lib/next_on_rails/engine.rb +5 -0
- data/lib/next_on_rails/version.rb +3 -0
- data/lib/tasks/next_on_rails_tasks.rake +4 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 20645c6f6a59e3294c584a02c801b53efd4711b66b5b28e431bde613e6209e24
|
4
|
+
data.tar.gz: f42e5e7d240bbf70d949b7e99b4ee3e761f7ff16a17ad4fc76bb10c3928a7bf3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91001cd6fa66e07fb965caaf96e232ff678d20df93b2d19ab418ba000d7d36c5f91bea06d1a7745e69d347df31722d3ec059a9406c883f6cbe5475e404c5890b
|
7
|
+
data.tar.gz: 671d41b98f9ed825ed29b81e639b4c6909ff87af9fdd4c241e06afdf95c20914f990f4e1fb22b760ec39b0efa48afb40a659a0ad42ae74a92dbd5d52e94f3370
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 Enrico
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,591 @@
|
|
1
|
+
# Next On Rails
|
2
|
+
|
3
|
+
Next On Rails (aka NOR) is a Ruby gem and a NPM package that help developers
|
4
|
+
connecting [Rails](https://rubyonrails.org/) --api backend app to
|
5
|
+
[Next.js](https://nextjs.org/) frontend app.
|
6
|
+
|
7
|
+
The Ruby gem setup Rails api app to serve json in the [jsonapi
|
8
|
+
format](https://jsonapi.org/) using the awesome Netflix [fast
|
9
|
+
jsonapi](https://github.com/Netflix/fast_jsonapi) gem and configure
|
10
|
+
[Devise](https://github.com/plataformatec/devise) for you with [Devise Token
|
11
|
+
Auth](https://github.com/lynndylanhurley/devise_token_auth/tree/master/lib/generators/devise_token_auth)
|
12
|
+
for authentication.
|
13
|
+
|
14
|
+
The NPM package provides a set of [React
|
15
|
+
hooks](https://reactjs.org/docs/hooks-intro.html) and utility to interact with
|
16
|
+
backend. Basically it allows developers to:
|
17
|
+
|
18
|
+
- fetch data with [Next.js
|
19
|
+
`getInitialProps`](https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle)
|
20
|
+
async function in an easy way
|
21
|
+
- handle Rails resources as React states easily with hooks
|
22
|
+
([CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete))
|
23
|
+
|
24
|
+
The NPM package also provides a `requester` object that performs HTTP requests
|
25
|
+
to Rails backend taking care of Devise Token Auth authentication token
|
26
|
+
management.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Create a fresh Rails --api app:
|
31
|
+
|
32
|
+
rails new YOUR-APP --api
|
33
|
+
|
34
|
+
Add in the Gemfile Next On Rails
|
35
|
+
|
36
|
+
gem 'next_on_rails'
|
37
|
+
bundle
|
38
|
+
|
39
|
+
Install
|
40
|
+
|
41
|
+
rails g nor:install
|
42
|
+
|
43
|
+
This generator setup your backend and generate a new frontend app in Next.js
|
44
|
+
inside the `./frontend` directory.
|
45
|
+
|
46
|
+
**What's appened?**
|
47
|
+
|
48
|
+
1. Generation of the `User` model for authentication with Devise
|
49
|
+
2. Add `UserSerializer` in `app/serializers/user_serializer.rb` and `CurrentUserSerializer` in `app/serializers/current_user_serializer.rb`
|
50
|
+
3. Add Devise config file in `config/initializers/devise.rb`
|
51
|
+
4. Add Devise Token Auth config file in `config/initializers/devise_token_auth.rb`
|
52
|
+
5. Add CORS configuration in `config/initializers/cors.rb`
|
53
|
+
6. Add Devise routes in `config/routes.rb`
|
54
|
+
7. Install a new `ApplicationController` that extends from `NextOnRails::ApplicationController`
|
55
|
+
8. Add [Foreman gem](https://github.com/ddollar/foreman) gem and `Procfile`
|
56
|
+
9. Setup [`letter_opener` gem](https://github.com/ryanb/letter_opener) for development environment
|
57
|
+
10. Generate a Next.js frontend app in `./frontend` directory
|
58
|
+
|
59
|
+
Now you are ready to use Next On Rails! Let's generate something:
|
60
|
+
|
61
|
+
rails g nor:scaffold post title body:text public:boolean
|
62
|
+
rails db:migrate
|
63
|
+
|
64
|
+
And run your backend and fronend with `foreman start`. Rails backend runs on
|
65
|
+
port 3000, while Next.js frontend runs on port 3001 (with
|
66
|
+
[express.js](https://github.com/expressjs/express))
|
67
|
+
|
68
|
+
Now visit [http://localhost:3001](http://localhost:3001). You should see the
|
69
|
+
homepage with login and registration form. You can also visit the post CRUD
|
70
|
+
pages just generated at
|
71
|
+
[http://localhost:3001/posts](http://localhost:3001/posts).
|
72
|
+
|
73
|
+
## The Frontend App
|
74
|
+
|
75
|
+
The frontend app is a normal Next.js app with some enhancements:
|
76
|
+
|
77
|
+
- Configured to use [SASS](https://sass-lang.com/) (see `./frontend/next.config.js`)
|
78
|
+
- [Bootstrap](https://getbootstrap.com/) ready
|
79
|
+
- The `App` React component is not the default `import App from 'next/app'` but `import App from 'next-on-rails/app'` (some High Order Components have been applied)
|
80
|
+
- In the `./frontend/components` there are some usefull React components:
|
81
|
+
- `<Flash/>` (for display flash messages)
|
82
|
+
- `<LoginForm/>`
|
83
|
+
- `<RegistrationForm/>`
|
84
|
+
- `<Input/>`, `<CheckBox/>`, `<Select/>`, `<TextArea/>` forms
|
85
|
+
|
86
|
+
# next-on-rails NPM package
|
87
|
+
|
88
|
+
Let's say that we have the resources _posts_, in our backend we have the model
|
89
|
+
`./app/models/post.rb` and the controller
|
90
|
+
`./app/controllers/posts_controller.rb` with the usual crud actions _index_,
|
91
|
+
_show_, _create_, _update_ and _destroy_. Now lets see how we can handle this
|
92
|
+
resources from the Next.js frontend app using the next-on-rails NPM package.
|
93
|
+
|
94
|
+
First of all we need to create a `/posts` page with the list of the posts. So
|
95
|
+
we add the file `./frontend/pages/posts/index.js`:
|
96
|
+
|
97
|
+
const PostsIndex = props => {
|
98
|
+
return (
|
99
|
+
<div>
|
100
|
+
<h1>Posts</h1>
|
101
|
+
<!-- content here -->
|
102
|
+
</div>
|
103
|
+
)
|
104
|
+
}
|
105
|
+
|
106
|
+
export default PostsIndex
|
107
|
+
|
108
|
+
We have just created a simple React functional component. This component will
|
109
|
+
render the entire `/posts` HTML page. Now it would be nice if the component
|
110
|
+
was initialized with a property `posts`, an array with the posts to display in
|
111
|
+
this page. We need to request the posts to the Rails backend at
|
112
|
+
`http://localhost:3000/posts.json` (standard
|
113
|
+
[restful](https://en.wikipedia.org/wiki/Representational_state_transfer)
|
114
|
+
route).
|
115
|
+
|
116
|
+
### getInitialResources
|
117
|
+
|
118
|
+
Next On Rails can help us with the function `getInitialResources` that returns
|
119
|
+
an async function that returns the object
|
120
|
+
`{ <resourcesName>: arrayOfResources }`
|
121
|
+
that is the `getInitialProps` function that we need!! So we can change our
|
122
|
+
component:
|
123
|
+
|
124
|
+
import { getInitialResources } from 'next-on-rails/resources'
|
125
|
+
|
126
|
+
const PostsIndex = props => {
|
127
|
+
return (
|
128
|
+
<div>
|
129
|
+
<h1>Posts</h1>
|
130
|
+
<ul>
|
131
|
+
{props.posts.map(post => <li key={post.id}>{post.title}</li>)}
|
132
|
+
</ul>
|
133
|
+
</div>
|
134
|
+
)
|
135
|
+
}
|
136
|
+
|
137
|
+
PostsIndex.getInitialProps = getInitialResources('posts')
|
138
|
+
|
139
|
+
export default PostsIndex
|
140
|
+
|
141
|
+
Now the React component will be initialized with the property `posts` that is
|
142
|
+
the array with all posts returned by the Rails backend `posts#index` endpoint.
|
143
|
+
|
144
|
+
So `getInitialResources` call the index action of the controller associated at
|
145
|
+
the resources passed as first parameter (`'posts'` in this example). It has
|
146
|
+
also an optional second parameter: the request parameters used to make the
|
147
|
+
request to the backend.
|
148
|
+
|
149
|
+
### getInitialResource
|
150
|
+
|
151
|
+
We can do the same for the single post page. We need to create a `/posts/:id`
|
152
|
+
page. So we add the file `./frontend/pages/posts/show.js`. In this case we
|
153
|
+
have to add the route to _express_ (in `./frontend/server.js` file) cause the
|
154
|
+
route `/posts/:id` is [dynamic](https://github.com/zeit/next.js#with-link):
|
155
|
+
|
156
|
+
server.get('/posts/:id(\\d+)', (req, res) => {
|
157
|
+
app.render(req, res, '/posts/show', { id: req.params.id })
|
158
|
+
})
|
159
|
+
|
160
|
+
The React component for this page will be:
|
161
|
+
|
162
|
+
import { getInitialResource } from 'next-on-rails/resources'
|
163
|
+
|
164
|
+
const PostsShow = props => {
|
165
|
+
return (
|
166
|
+
<div>
|
167
|
+
<h1>Post #{props.post.id}</h1>
|
168
|
+
<h2>{props.post.title}</h2>
|
169
|
+
<p>
|
170
|
+
{props.post.body}
|
171
|
+
</p>
|
172
|
+
</div>
|
173
|
+
)
|
174
|
+
}
|
175
|
+
|
176
|
+
PostsShow.getInitialProps = getInitialResource('post')
|
177
|
+
|
178
|
+
export default PostsShow
|
179
|
+
|
180
|
+
In this case we used the function `getInitialResource` that is similar to
|
181
|
+
`getInitialResources` but it calls the endpoint `posts#show` passing the
|
182
|
+
parameter `id` retrieved from the current url. The property returned is no
|
183
|
+
more an array, but a js object with the post (
|
184
|
+
`{post: {id: 1, title: ..., body: ..., ...}}`).
|
185
|
+
|
186
|
+
**NB:** first, we have said that the Rails backend responds in jsonapi format,
|
187
|
+
but the post object is not in jsonapi format. Next On Rails normalize the
|
188
|
+
jsonapi format internally for us. So in our frontend, we have the data ready
|
189
|
+
to use!
|
190
|
+
|
191
|
+
### composeGetInitialResources
|
192
|
+
|
193
|
+
This function is like `getInitialResources` or `getInitialResource` but take a
|
194
|
+
list of resource names and combine the property to pass to the React
|
195
|
+
component. For example if we want to prefetch a post and a list of authors (in
|
196
|
+
an edit page we can edit the post author for example) we can use this function:
|
197
|
+
|
198
|
+
PostsEdit.getInitialProps = composeGetInitialResources('post', 'authors')
|
199
|
+
|
200
|
+
For this function the plurality of names are very important: singular names
|
201
|
+
will be request the `show` action, plural names the `index` action. With this
|
202
|
+
function is not possible to pass HTTP extra params. If you need to pass extra
|
203
|
+
HTTP params you should call `getInitialResources` or `getInitialResource` and
|
204
|
+
compose yourself the results.
|
205
|
+
|
206
|
+
`composeGetInitialResources` __performs a single HTTP request__.
|
207
|
+
|
208
|
+
### useResources
|
209
|
+
|
210
|
+
Now let's say we want update a post. We need to create a `/posts/:id/edit`
|
211
|
+
page. So we add the file `./frontend/pages/posts/edit.js` and add the route to _express_.
|
212
|
+
|
213
|
+
The React component for this page will be:
|
214
|
+
|
215
|
+
import { getInitialResource, useResources, useResourceForm } from 'next-on-rails/resources'
|
216
|
+
import { Input, TextArea, CheckBox, HiddenIdField } from '../inputs'
|
217
|
+
|
218
|
+
const PostsEdit = props => {
|
219
|
+
const [, post, { update }] = useResources('posts', [], props.post)
|
220
|
+
const [submit, getError] = useResourceForm(update)
|
221
|
+
|
222
|
+
return (
|
223
|
+
<div>
|
224
|
+
<h1>Edit Post #{post.id}</h1>
|
225
|
+
<form onSubmit={submit}>
|
226
|
+
<HiddenIdField id={post.id} />
|
227
|
+
<Input name="title" value={post.title} error={getError('title')} />
|
228
|
+
<TextArea name="body" value={post.body} error={getError('body')} />
|
229
|
+
<CheckBox name="public" value={post.public} error={getError('public')} />
|
230
|
+
<button type="submit" className="btn btn-primary">
|
231
|
+
Save
|
232
|
+
</button>
|
233
|
+
</form>
|
234
|
+
</div>
|
235
|
+
)
|
236
|
+
}
|
237
|
+
|
238
|
+
PostsEdit.getInitialProps = getInitialResource('post')
|
239
|
+
|
240
|
+
export default PostsEdit
|
241
|
+
|
242
|
+
Ok, we have a lot to say about this component. Don't worry!
|
243
|
+
Let's start from the React hook `useResources`.
|
244
|
+
|
245
|
+
The idea is that we have a state consisting of 2 elements. An array with a
|
246
|
+
collection of resources and an object with a single resource. For example the
|
247
|
+
array with all posts and an object with the current post. This 2 elements
|
248
|
+
should be bound together so if I change the resource also the element with
|
249
|
+
the same id in the resources array should change accordingly, and vice versa.
|
250
|
+
|
251
|
+
This hook returns an array with four elements:
|
252
|
+
`[arrayOfResources, currentResourceObject, { actions }, dispatch]`.
|
253
|
+
The first two elements are the state, the array of resources (first one) and a
|
254
|
+
single resource (second one). The third is an object with the five functions
|
255
|
+
to perform the CRUD operations: `index`, `show`, `create`, `update` and
|
256
|
+
`destroy`. The fourth is the dispatch function returned by the
|
257
|
+
[`useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer) React
|
258
|
+
hook that is used internally by the `useResources` hook.
|
259
|
+
|
260
|
+
The third element is an object with five function, let's see one by one:
|
261
|
+
|
262
|
+
- `function index(params)` has as its argument HTTP params and performs a
|
263
|
+
request to the `resources_controller#index` action of Rails backend. For
|
264
|
+
example you can call `index({ page: 2 })` to get array of posts.
|
265
|
+
Returns a promise that will be resolved with the array of resources or
|
266
|
+
rejected with the errors.
|
267
|
+
|
268
|
+
- `function show(id, params)` has as its first argument the ID of a resource
|
269
|
+
and as its second argument HTTP params. Performs a request to the
|
270
|
+
`resources_controller#show` action of Rails backend. Returns a promise that
|
271
|
+
will be resolved with the object of the resource with the corresponding ID
|
272
|
+
or rejected with the errors.
|
273
|
+
|
274
|
+
- `function create(params)` has as its argument the params to create a new
|
275
|
+
resource. For example to create a new post you can call
|
276
|
+
`create({ title: 'Spiderman is dead', body: '...', public: true })`.
|
277
|
+
Performs a request to the `resources_controller#create` action of Rails
|
278
|
+
backend. Returns a promise that will be resolved with the object just
|
279
|
+
created or rejected with the errors.
|
280
|
+
|
281
|
+
- `function update(id, params)` has as its first argument the ID of the
|
282
|
+
resource to update and as its second argument params to update. For example
|
283
|
+
to update a post you can call
|
284
|
+
`update(1, { title: 'Spiderman is alive', body: '...', public: true })`.
|
285
|
+
Performs a request to the `resources_controller#update` action of Rails
|
286
|
+
backend. Returns a promise that will be resolved with the object just
|
287
|
+
updated or rejected with the errors.
|
288
|
+
|
289
|
+
- `function destroy(id)` has as its argument the ID of the resource to delete.
|
290
|
+
Performs a request to the `resources_controller#destroy` action of Rails
|
291
|
+
backend. Returns a promise that will be resolved with no arguments or
|
292
|
+
rejected with the errors.
|
293
|
+
|
294
|
+
So now in our previous example we can understand the line
|
295
|
+
|
296
|
+
const [, post, { update }] = useResources('posts', [], props.post)
|
297
|
+
|
298
|
+
We want a state with a post (initialized with the property `post`) and a
|
299
|
+
function to update it! For this component we don't need posts array and any
|
300
|
+
other CRUD functions.
|
301
|
+
|
302
|
+
### useResourceForm
|
303
|
+
|
304
|
+
Now let's talk about the other very useful hook that NOR makes us available:
|
305
|
+
`useResourceForm`. This hook take a CRUD function which we talked about
|
306
|
+
earlier as first argument, a success callback as second argument and an error
|
307
|
+
callback as third argument.
|
308
|
+
|
309
|
+
It returns an array with two functions: `[submit, getError]`. The first one is
|
310
|
+
a function that is ready to pass as value of `onSubmit` form tag attribute. It
|
311
|
+
collects all form data with
|
312
|
+
[`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) web
|
313
|
+
API object and then call the CRUD function (passed as first parameter) with
|
314
|
+
this form data. The resource ID can be passed as hidden field tag.
|
315
|
+
|
316
|
+
The second function `getError` takes a resource field as string, like the post
|
317
|
+
attribute `'title'`, and returns a validation error message for this attribute
|
318
|
+
if there is one. The errors are modelled as a React state. So at the beginnig
|
319
|
+
there are no errors and the function will return always `null`. After a failed
|
320
|
+
submit the function can return the validation error message. So if we call
|
321
|
+
`getError('title')` we get `null` if there is no validation errors or for
|
322
|
+
example _"Title can't be blank"_ if there is.
|
323
|
+
|
324
|
+
So we can now understand the line in the previous example
|
325
|
+
|
326
|
+
const [submit, getError] = useResourceForm(update)
|
327
|
+
|
328
|
+
We get the `submit` function to update a post and the `getError` function to
|
329
|
+
display the validation error messages.
|
330
|
+
|
331
|
+
### useCurrentUser
|
332
|
+
|
333
|
+
Always in our React components we can use this hook:
|
334
|
+
|
335
|
+
|
336
|
+
import { useCurrentUser } from 'next-on-rails/current-user'
|
337
|
+
|
338
|
+
const HelloUser = () => {
|
339
|
+
const { currentUser } = useCurrentUser()
|
340
|
+
|
341
|
+
return (
|
342
|
+
<p>{ currentUser ? `Hello ${currentUser.username}` : 'You are not logged' }</p>
|
343
|
+
)
|
344
|
+
}
|
345
|
+
|
346
|
+
export default HelloUser
|
347
|
+
|
348
|
+
This hook return an object with two key. `currentUser` is the object with the
|
349
|
+
current user logged or `null`. `setCurrentUser` is a function to update the
|
350
|
+
current user.
|
351
|
+
|
352
|
+
const { currentUser, setCurrentUser } = useCurrentUser()
|
353
|
+
|
354
|
+
### useFlash
|
355
|
+
|
356
|
+
We can use flash messages also in our Next.js frontend app. Here the previous example with flash messages:
|
357
|
+
|
358
|
+
import { getInitialResource, useResources, useResourceForm } from 'next-on-rails/resources'
|
359
|
+
import { useFlash } from 'next-on-rails/utils'
|
360
|
+
import { Input, TextArea, CheckBox, HiddenIdField } from '../inputs'
|
361
|
+
import Flash from '../flash'
|
362
|
+
|
363
|
+
const PostsEdit = props => {
|
364
|
+
const { setFlash } = useFlash()
|
365
|
+
const [, post, { update }] = useResources('posts', [], props.post)
|
366
|
+
const [submit, getError] = useResourceForm(update, () => {
|
367
|
+
setFlash('notice', 'Post updated successfully')
|
368
|
+
})
|
369
|
+
|
370
|
+
return (
|
371
|
+
<div>
|
372
|
+
<Flash />
|
373
|
+
<h1>Edit Post #{post.id}</h1>
|
374
|
+
<form onSubmit={submit}>
|
375
|
+
<HiddenIdField id={post.id} />
|
376
|
+
<Input name="title" value={post.title} error={getError('title')} />
|
377
|
+
<TextArea name="body" value={post.body} error={getError('body')} />
|
378
|
+
<CheckBox name="public" value={post.public} error={getError('public')} />
|
379
|
+
<button type="submit" className="btn btn-primary">
|
380
|
+
Save
|
381
|
+
</button>
|
382
|
+
</form>
|
383
|
+
</div>
|
384
|
+
)
|
385
|
+
}
|
386
|
+
|
387
|
+
PostsEdit.getInitialProps = getInitialResource('post')
|
388
|
+
|
389
|
+
export default PostsEdit
|
390
|
+
|
391
|
+
|
392
|
+
We can use the hook `useFlash` from which we can get a function to set a flash
|
393
|
+
message and then we can display the component `Flash` that will show the
|
394
|
+
message. You can find the `Flash` component in the file
|
395
|
+
`./frontend/components/flash.js` where you can customize it as you want.
|
396
|
+
|
397
|
+
### useLoading
|
398
|
+
|
399
|
+
`useLoading` is another utility hook to know when the frontend is loading data from backend. Is quite simple:
|
400
|
+
|
401
|
+
import { useLoading } from 'next-on-rails/utils'
|
402
|
+
|
403
|
+
const Loader = props => {
|
404
|
+
const loading = useLoading()
|
405
|
+
|
406
|
+
if (loading) {
|
407
|
+
return <img src='loader.gif' alt="loading" />
|
408
|
+
} else {
|
409
|
+
return null
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
### Requester
|
414
|
+
|
415
|
+
To perform HTTP request to backend you should use the `requester`.
|
416
|
+
|
417
|
+
import requester from 'next-on-rails/requester'
|
418
|
+
|
419
|
+
requester.get('/posts')
|
420
|
+
.then(function (response) {
|
421
|
+
// handle success
|
422
|
+
console.log(response)
|
423
|
+
})
|
424
|
+
.catch(function (error) {
|
425
|
+
// handle error
|
426
|
+
console.log(error);
|
427
|
+
})
|
428
|
+
|
429
|
+
It is a wrapper over [Axios](https://github.com/axios/axios) and it works in
|
430
|
+
the same way. You can also access to axios object with `requester.axios`.
|
431
|
+
|
432
|
+
So why the `requester`? Because is handle for you the [Devise Token Auth
|
433
|
+
authentication
|
434
|
+
token](https://devise-token-auth.gitbook.io/devise-token-auth/conceptual),
|
435
|
+
adding the token in the request header and change it at every request based on
|
436
|
+
the previous response. The requester save the token on cookies.
|
437
|
+
|
438
|
+
It also normalize the jsonapi response. For example if reponse json is
|
439
|
+
|
440
|
+
{
|
441
|
+
"data": [
|
442
|
+
{
|
443
|
+
"id": "1",
|
444
|
+
"type": "post",
|
445
|
+
"attributes": {
|
446
|
+
"id": 1,
|
447
|
+
"title": "My first blog post",
|
448
|
+
"body": "Amazing",
|
449
|
+
"public": true
|
450
|
+
}
|
451
|
+
},
|
452
|
+
{
|
453
|
+
"id": "2",
|
454
|
+
"type": "post",
|
455
|
+
"attributes": {
|
456
|
+
"id": 2,
|
457
|
+
"title": "My second blog post",
|
458
|
+
"body": "Wow",
|
459
|
+
"public": false
|
460
|
+
}
|
461
|
+
}
|
462
|
+
]
|
463
|
+
}
|
464
|
+
|
465
|
+
it will be normalized in:
|
466
|
+
|
467
|
+
[
|
468
|
+
{
|
469
|
+
"id": 1,
|
470
|
+
"title": "My first blog post",
|
471
|
+
"body": "Amazing",
|
472
|
+
"public": true
|
473
|
+
},
|
474
|
+
{
|
475
|
+
"id": 2,
|
476
|
+
"title": "My second blog post",
|
477
|
+
"body": "Wow",
|
478
|
+
"public": false
|
479
|
+
}
|
480
|
+
]
|
481
|
+
|
482
|
+
### Config
|
483
|
+
|
484
|
+
NOR try to follow the Rails conventions, but you can customize something
|
485
|
+
editing the configuration file `./frontend/next-on-rails.config.js`. The default config is this:
|
486
|
+
|
487
|
+
{
|
488
|
+
baseURL: 'http://localhost:3000',
|
489
|
+
flashTimeout: 5000,
|
490
|
+
devisePaths: {
|
491
|
+
validateToken: '/auth/validate_token',
|
492
|
+
signIn: '/auth/sign_in',
|
493
|
+
signOut: '/auth/sign_out',
|
494
|
+
signUp: '/auth',
|
495
|
+
passwordReset: '/auth/password',
|
496
|
+
passwordChange: '/auth/password',
|
497
|
+
unlock: '/auth/unlock'
|
498
|
+
},
|
499
|
+
deviseFormInputNames: {
|
500
|
+
email: 'email',
|
501
|
+
password: 'password',
|
502
|
+
passwordConfirmation: 'password_confirmation'
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
You can customize the resource names and actions:
|
507
|
+
|
508
|
+
resources: {
|
509
|
+
posts: {
|
510
|
+
routes: {
|
511
|
+
index: { method: 'get', url: '/all_posts.json' }
|
512
|
+
},
|
513
|
+
name: {
|
514
|
+
singular: 'post',
|
515
|
+
plural: 'posts'
|
516
|
+
}
|
517
|
+
}
|
518
|
+
}
|
519
|
+
|
520
|
+
## Add custom action
|
521
|
+
|
522
|
+
Until now we have always talk about the standard CRUD action. But how to do if
|
523
|
+
we need manage a custom controller action?
|
524
|
+
|
525
|
+
We can add in our post controller a new action:
|
526
|
+
|
527
|
+
def like
|
528
|
+
@post = Post.find(params[:id])
|
529
|
+
@post.increment!(:like_counter)
|
530
|
+
render json: PostSerializer.new(@post)
|
531
|
+
end
|
532
|
+
|
533
|
+
And in `routes.rb`
|
534
|
+
|
535
|
+
resources :posts do
|
536
|
+
member do
|
537
|
+
patch :like
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
Our frontend page component should be:
|
542
|
+
|
543
|
+
import { getInitialResource, useResources } from 'next-on-rails/resources'
|
544
|
+
import requester from 'next-on-rails/requester'
|
545
|
+
|
546
|
+
const PostsShow = props => {
|
547
|
+
const [, post, , dispatch] = useResources('posts', [], props.post)
|
548
|
+
|
549
|
+
const like = (event) => {
|
550
|
+
requester.patch(`/posts/${post.id}/like`)
|
551
|
+
.then(response => {
|
552
|
+
dispatch({ type: 'set', resource: response.data })
|
553
|
+
})
|
554
|
+
.catch(console.log)
|
555
|
+
}
|
556
|
+
|
557
|
+
return (
|
558
|
+
<div className="container pt-4">
|
559
|
+
<h1 className="mb-5">Post #{post.id}</h1>
|
560
|
+
|
561
|
+
<p>{post.body}</p>
|
562
|
+
|
563
|
+
<div>
|
564
|
+
<button onClick={like}>Like</button>
|
565
|
+
{post.like_counter} people like it
|
566
|
+
</div>
|
567
|
+
</div>
|
568
|
+
)
|
569
|
+
}
|
570
|
+
|
571
|
+
PostsShow.getInitialProps = getInitialResource('post')
|
572
|
+
|
573
|
+
export default PostsShow
|
574
|
+
|
575
|
+
We have create a custom function `like` that performs the `posts#like` action
|
576
|
+
and on success we replace the current post state object with the new post
|
577
|
+
object returned from the HTTP request. In this way the `like_counter` will be
|
578
|
+
updated and React re-render the incremented counter.
|
579
|
+
|
580
|
+
## License
|
581
|
+
|
582
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
583
|
+
|
584
|
+
## About Uqido
|
585
|
+
|
586
|
+
[](http://uqido.com)
|
587
|
+
|
588
|
+
Next On Rails is maintained and funded by [Uqido](https://uqido.com).
|
589
|
+
The names and logos for Uqido are trademarks of Uqido s.r.l.
|
590
|
+
|
591
|
+
The [Uqido team](https://www.uqido.com/en/about-us/).
|