Feeding data into universal rendering components
See original GitHub issueI’ve been looking at how to compile data from the server and make it available for the new universal React routing & rendering capability that was recently added to 0.4.x.
That data should obviously include user data, relevant lists, etc… but we don’t want to pass it explicitly through the component view hierarchy. We haven’t discussed the topic in-depth, but the community seems to be converging around Redux and it’s rapidly expanding ecosystem of developer tools. There’s a really great tutorial course by @gaearon.
Currently, it appears we take a bit of a wild west approach to bootstrapping the client page with data from the server, with no consistent user-extensible mechanism that I can see to determine what data is intended to be delivered to the client.
It also seems that there are bits and pieces of it being written by different page views (e.g., via viewLocals
and arbitrary data being shoved directly into render()
calls). Additionally, our current React admin session store doesn’t appear to be designed for universal rendering and the Node request/response cycle. This stuff hasn’t been a huge issue so far because users define their own views and can essentially stuff any data they want anywhere they want, but that approach won’t work with baked in support for universal rendering.
Thankfully, both Express and React provide mechanisms that may be an effective path forward.
First, Express has a res.locals
environment that is intended to be a safe way to gather data for the current request/response cycle, only. This is a good place to store things like authentication data, current user preferences, CSRF, “view locals”, and so on. We should use it for its intended purpose, and create a special key that users of Keystone can easily extend with whatever custom data they need to generate on the server for delivery to the client.
Second, React has a context
mechanism that seems to be a good candidate for this stuff. It’s a way of implicitly passing data down through the entire React component hierarchy as opposed to explicitly passing everything from parent to children in props
.
Receiving context is opt-in, and a React component must opt-in to the particular context keys that they’re interested in. Additionally, Redux & React-Redux offer a simple way to pass store context into the app.
I’m thinking that we should create a special res.locals.context
key that Keystone users can add to in their own middleware, and then we should ensure that it gets added to a Redux store and made available in the client bootstrap (currently we set a bunch of variables directly on a Keystone
object on the client page… we can do something similar, or keep doing that).
I need a working mechanism to pass state into universal routes for both server and client rendering right now. I’m using it for language preferences and user data in the short-term. I’d love to hear your feedback.
Issue Analytics
- State:
- Created 8 years ago
- Comments:9 (6 by maintainers)
Top GitHub Comments
Data Injection RDD
Here is my proposal to add Redux & data injection capabilities to the Universal Routing and Rendering support.
Universal Route & Render with React & Redux
For Universal JavaScript projects, simply pass a few options into your
Keystone.init()
. For example, if you have the following smart component you’d like to use as a route:You’ll need reducers defined for those props (
reducers/index.js
):Add the component to your routes in
routes/react-routes
:You could can use it by importing it into your
Keystone.js
file, along with your Redux reducers:Unit Testing Smart Components
If you’d like to unit test your smart components, you’ll need to create a store. To facilitate that, there’s a new
keystone.createStore()
utility that takesinitialState
,reducers
, andreduxMiddleware
parameters. You’ll need to pass the store into your component props to get the rendered values:Building Your Client App
In general, you’re free to do whatever you want with your client app. Here’s how to bootstrap it with the server-rendered data:
Redux Middleware
You may want to pass Redux middleware into the store. Here’s how to do it on the server:
And in the client app, simply add it to the call to
universal()
:Injecting Data from the Server
Data is made available to React components via the
react-redux
context. To add to it on the server, simply add data keys tores.locals.context
. Data inres.locals.context
will be assigned toinitialState
, overriding whatever defaults exist in your reducers.Note: This bit needs discussion and fleshing out. I won’t implement it in the first pass.
Automatic Context Data
Some data will be made available to your app’s stores automatically:
Keystone 4 is going in maintenance mode. Expect no major change. see #4913 for details. Keystone v5 is using GraphQL extensively and one of the few examples are based on SSR with Next.js for React.