Republic is a library for React that gives you productive routing, controllers, and forms. It currently is built to work with Express + Next.js but has been built in mind that this may not always be the stack of choice.
If you miss the days of Rails forms and controllers, you'll love Republic.
- Start Here - for those who haven't used Republic before
- Concepts - the reasons why you should use Republic
- Documentation - for a reference of all the parts to Republic
We have a lovely document for creating an application from scratch using Express + Next.js + Republic. The guide is a complete walkthrough that's ideal for first time users. Read it to get started.
There are three concepts that make Republic compelling to use.
- Routing
- Controllers
- Forms
Republic tries to fuse the productivity provided by web frameworks like Rails with the new world of React. Unfortunately React requires quite a lot of boilerplate when making forms, Redux isn't straight forward when dealing with data, and Next.js does not have any inbuilt routing. Republic aims to solve all of these problems.
Republic provides routing functionality like Rails. You can build a URL or <a href>
just by referencing the controller and action, perhaps providing some parameters.
You define your routes in your Republic application. Your Republic application is universal, that is, it can be used both on the server and on the client side.
import Republic, { route } from 'republic/next'
export default new Republic(
route.page('/blog', 'blog#index'),
route.page('/blog/:slug', 'blog#show')
)
You can use express style routing, so for example parameters can be passed specified like :slug
.
You can construct URLs given an action like 'blog#index'
, and optionally params like { slug: 'awesome-post' }
.
import app from '../../app'
app.url('blog#index') // => '/blog'
app.url('blog#show', { slug: 'awesome-post' }) // => '/blog/awesome-post'
Because your Republic application is universal, you can build URLs both on the server and a client.
You can build links much like Next.js, but you do not need to hardcode URLs, instead you can specify an action and optionally params.
import React from 'react'
import { Link } from 'republic/react'
export default () =>
<div>
<Link action='blog#index'>
<a>View posts</a>
</Link>
<Link action='blog#show' params={{ slug: 'awesome-post' }}>
<a>Read awesome post</a>
</Link>
</div>
Republic provides the ability to define controller action handlers for your routes. These are similar to Express route handlers, however they are universal, they can run both on the server and client. This makes it really easy to handle form submissions in the client if JavaScript is available, or submit to the server if not.
You define your action handlers beside your routes, much like Express.
import Republic, { route } from 'republic/next'
import blog from './src/blog/'
export default new Republic(
route.page('/blog', 'blog#index', () => {
return { posts: blog.FetchLatestBlogPosts() }
}),
route.page('/blog/:slug', 'blog#show', ({ params }) => {
return { post: blog.FetchPost(params.slug) }
})
)
As you can see, you return an object with data in it. This data will be accessible within your React page by passing the object into your page component's props.
To illustrate, the access of the data returns from the action handler above, here is the page for 'blog#show'
. You can see we are accessing the post data returned from the action handler.
import React from 'react'
import app from '../../app'
export default app.page(({ post }) =>
<article>
<h1>{post.title}</h1>
{post.content}
</article>
)
Republic provides universal forms that handle state management for you. No longer do you need to define your own onChange handlers, a huge bug bear for those coming from Rails. Also, because Republic provides universal forms, the form can be handled both server side if JavaScript is not available, or in the client if it is.
Before we build our form, we first want to define our action handler that will be called on submit. The process is similar to defining a page route.
import Republic, { route } from 'republic/next'
import blog from './src/blog/'
export default new Republic(
route.page('/blog', 'blog#index', () => {
return { posts: blog.FetchLatestBlogPosts() }
}),
route.POST('/blog/subscribe', 'blog#subscribe', async ({ params, redirectTo }) => {
await blog.Subscribe(params.email)
redirectTo('blog#index')
})
)
This action will handle a new subscription to the blog's mailing list. Once the subscription has been created, we then redirect the user back to the blog index page.
Now we have defined our action handler, we can use it in a form. Within our 'blog#index'
page we can define our form and pass our action into it.
import React from 'react'
import { Form, Input } from 'republic/react'
import app from '../../app'
import PostList from '../../components/PostList'
export default app.page(({ posts, subscribe }) =>
<Form action={subscribe}>
<Input type='email' name='subscription[email]' />
<Input type='checkbox' name='subscription[include_spam]' value='yes' />
<button>Subscribe</button>
</Form>
)
Because 'blog#index'
is in the same controller as 'blog#subscribe'
the subscribe action handler is automatically passed into the props of the page. You can see this handler is then passed into the <Form>
's action
prop. The handler will be called onSubmit with the form data, or handled on the server if JavaScript is not available.
- Installation - how to install Republic
new Republic(...routes)
- how to define your Republic applicationapp.page(Component)
- how to define a Republic pageapp.url(action, params = {})
- how to build a URL<Link>
- how to link to other pages<Form>
- how to create a universal form<Input />
- how to add fields to your universal form<Textarea />
- how to add textarea fields to your universal form<Select>
- how to add select fields to your universal form
Use Next.js's example of custom server using Express to setup your app ready to install Republic.
Install republic first with:
npm install --save republic
Now add two middleware to your Express setup. The following code adds parsing for POST data.
// Handle POST data
import bodyParser from 'body-parser'
server.use(bodyParser.urlencoded({ extended: true }))
And then we add republic into the mix:
// Hook up upcoming + express + next
import { asExpressMiddleware, nextHandler } from 'republic/express'
import app from './app'
server.use(asExpressMiddleware(app, nextHandler(nextApp)))
Make sure the path to your app file is correct.
- Form state handling
- Connect form state to action handling
- Form handling receives nested object rather than flattened string keys
- Allow forms to have default state set
- Pass actions in as functions rather than strings so they can be used with custom forms, and on other input events like click
Nice to haves:
- Update route builders to accept middleware as arguments, array as single argument, or mix of both like Express
- Pass rails-like params into handlers
- Universal redirect
- Provide all common form inputs
- Convention for express + next (helper wrappers)
- Form error handling
- Abstract pages away from Next.js
- Allow Form to have custom onSubmit
- Upcoming middleware: how to, make sure API is easy to understand, etc.