-
Notifications
You must be signed in to change notification settings - Fork 26.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow pages to be render functions (allows persistent layout with no magic) #3461
Conversation
Nice and reasonable workaround! @jdeal Thank you! |
@jdeal wow this is pretty neat. I can only take this PR, if this is the only the way. We can make this backward compatible and warn users to follow this approach. But before we do that, we need to know how @rauchg @nkzawa @sergiodxa and @timneutkens thinks about this. |
This is super interesting @jdeal. Thanks for sharing. Are there any downsides to always applying this new behavior without checking for the name? |
Thanks @arunoda and @rauchg for taking a look! I can happily switch to always treating plain functions as render functions and detecting React classes for backward compatibility. The one downside I can imagine would be if someone is currently depending on that behavior. In other words, if they have a stateless function component at the top and then some stateful components underneath, those child components are currently unmounting/remounting. If that's actually desirable in their specific case, or if they've done other workarounds to "fix" that behavior, then moving to a render function would suddenly surprise them and possibly break their app. |
This is a good point. |
@@ -83,7 +83,11 @@ class Container extends Component { | |||
// https://github.com/gaearon/react-hot-loader/issues/442 | |||
return ( | |||
<AppContainer warnings={false} errorReporter={ErrorDebug}> | |||
<Component {...props} url={url} /> | |||
{ | |||
Component.name === 'render' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On other idea to wrap the Component
inside a function if it's a React component class.
And do not rely on the name.
🤔 I'm not sure, this is not idiomatic React code, most people don't know you can just call a React stateless component as simple functions passing props as arguments neither expect it. So we should add warning and teach people on how this works. And as @arunoda said we're going to have a new API to render a page, this means more documentation needed and more possible errors (specially due people don't expect that to work). |
Seeing as how this is such a desired feature (and has been for a while), I think adding a bit of documentation for this functionality is well worth it. If something more idiomatic comes along, you can just release a new major version and refer people to the changelog. :\ |
@rauchg what about #3471 ? is not a breaking change, and React components will be still be used as usual. BTW I don't care about documenting more Next.js features, my concern is with this we need to document another way React components can be used, it's not a Next.js feature, is an undocumented way to use React. |
@sergiodxa If I were designing the Next.js from scratch. I'd go with this option. Like this: const Page = (props) => (
<Layout>
...
</Layout>
)
Page.getInitialProps = () => ({
name: 'Hello'
})
export default Page And we don't support exporting React classes. But now, it's pretty hard to do this change since we've made the pages API super stable. That's why I like #3471. |
@arunoda @sergiodxa I had thought of a similar approach to #3471, but I went with the render function instead because it's more flexible. What if you want your Layout component to provide a render callback instead of passing it a child component? What if you want to pass different props to your Layout component from different pages? What if you don't want a Layout component at all, but instead you want to make some kind of dynamic determination of what to render? Having the An important side effect of this is that the static Layout component approach simply pushes the problem further down the tree. The page component ( What about deprecating the current behavior in v5 with a warning and allowing opt-in in a config to the new behavior? Then v6 could switch to the new behavior as a default. Some other options for providing a render function without the special // Provide a Page class that creates a wrapped render function.
import Page from 'next/page'
export class MyPage extends Page {
render() {
return <Layout>...</Layout>;
}
} // Provide a createPage helper that wraps a render function.
import createPage from 'next/page'
export createPage(() => <Layout>...</Layout>); Neither of these is substantially different from the You could also compromise with something like this: export default class AboutPage extends React.Component {
static renderPage = ({Component, ...props}) => <Layout><Component {...props}/></Layout>
render () {
return (
<div>
about
</div>
)
}
} This way, technically, you keep the same top-level API, but there's an escape hatch that lets take over rendering. Effectively, that means you could do this: export default class AboutPage extends React.Component {
static renderPage = () => <Layout><div>about</div></Layout>
} That's fine, but maybe just a little weird in the sense that we're just hijacking the React.Component class to hold a render function. |
I like the idea of "createPage" because that way getInitialProps can go together with a pure render function:
|
If I can put my two cents again, I completely agree with:
So, I think #3471 is not a complete solution while the approach described by @jdeal makes things flexible. The exact API (either a "render" function, or "renderPage", or "Page" class) needs more thought, indeed. I don't like the magic "render" function naming just because it doesn't say much to those who just inspect the code without reading Next.js documentation. I would prefer a more explicit way of declaring your intent. "Page" class and "createPage" expands the API surface, and also make things confusing given two sets of APIs (React.Component/stateless component [native React way] and Page/createPage [Next.js way]) are expected. The compromise of static |
Let's close this in favor of #3552 |
I'm clone your fork canary branch. This example does not work as expected. I'm do not understand how it may work. Is a new layout created each time? In #3552 |
@apapacy Here it is working for me: From the It will not create a new |
Yes. It work. You update lib/app.js. I'm not detect this fact. |
Closing in favor of #4129 |
By adding the ability to define a page as a function instead of a component, you can easily create persistent layouts by the natural way that React diffs the tree.
The proposal here is that if the exported function is named
render
, then it's treated as a render function rather than a component. In that case, the return value of that function is rendered rather than creating an element from the component. Any matching root components stay mounted across page transitions. Because you're in control of what is rendered at the root, you can have multiple possible roots that mount/unmount as desired. For example, you could have pagesa
andb
useLayoutFoo
andc
andd
useLayoutBar
.LayoutFoo
would stay mounted while ona
andb
, but it would be unmounted when going toc
andd
. In this way, this PR is more generic than #3288.Only a slight tweak to lib/app.js is required to make this possible. The rest of this PR is a new example to demonstrate a persistent layout with render function pages.