react-router v4
Do you use react-router v4?
Do you use server side rendering?
Using react-rails or react-on-rails?
Can your server react environment not use fetch? (e.g. mini_racer, therubyracer)
Do your components expect server context from different places?
This component wraps the logic behind this scenario:
[[ Rails server ]]
-> @my_context = {blogPost: BlogPost.first}
-> react_component("App", {serverContext: @my_context}, {prerender: true})
-> [[ Rails JS environment, e.g. mini_racer, therubyracer ]]
-> @my_context given to react-router staticContext via context prop
-> assign @my_context to window.serverContext
-> render with staticContext data
-> [[ Browser environment ]]
-> no staticContext, so look for context on window.serverContext
-> render with serverContext from window
So, this component allows you to deal with server context in your component with one variable, serverContext
.
Not to worry, just wrap that component or HOC with this component, and have that component expect a alreadyResolved
prop so it doesn't do extra work. Just have it function normally if alreadyResolved == false
.
e.g.
interface MyResolveOrLoaderComponentProps {
alreadyResolved: boolean;
}
Function signature
function withContext<S,P>(contextKey: keyof S, routeKey: keyof P & keyof S[keyof S])
Function description
const ComponentWithContext = withContext<MyServerContextInterface, MyComponentRouterParamsInterface>(myKeyOfServerContext, myKeyOfServerContextObject)(MyComponent)
- Define server context interface for the object that will be passed into router context prop
- Define params interface for your component's
props.match.params
- Have your component pull the context into its state
this.state = {blogPost: this.props.serverContext.blogPost};
- Wrap your component with this component
Full example
export interface BlogPost {
title: string;
body: string;
}
export interface ServerContext {
blogPost?: BlogPost;
}
interface BlogPostParams {
title: string;
}
interface BlogPostProps {
blogPost: BlogPost;
}
class BlogPostComponent extends React.Component<BlogPostProps, BlogPostState> {
constructor(props: BlogPostProps) {
super(props);
this.state = {blogPost: this.props.serverContext.blogPost};
}
componentWillMount() {
// handle browser side navigation to blog posts
if (!this.props.serverContext.blogPost) {
this.getBlogPost();
}
}
getBlogPost() {
console.log('performing an ajax call and setting state here...');
}
render() {
return <div className="blog-post">
<h1>{this.state.blogPost.title}</h1>
<p>{this.state.blogPost.body}</p>
</div>
}
}
export const BlogPostWithContext = withContext<ServerContext, BlogPostParams>(
'blogPost', 'title'
)(BlogPostComponent)
☑️ move logic out of render()
☑️ support generic router interface