Using the context API inside the contructor is possible, you just need to pass it as a parameter inside both the constructor and the super method as below:
export class Child extends React.Component {
static contextTypes = { registerComponent: PropTypes.func };
constructor(props, context) {
super(props, context);
context.registerComponent(props.id);
}
render() {
return <div id={this.props.id} />;
}
}
update:
so, the problem is related to the first render on the server that doesn't support any interractivity,
this approche will prevent any setState
to be considered on the server, in your example the <Provider />
wouldn't give as any hand, so we need a workaround that will be a hack in any case.
the solution i got is having a new window.ids
with every tracked component render(using the Head provided from nextjs):
export class Child extends React.Component {
registerComponent = (id) => `
if (window.ids) {
window.ids = [...window.ids, ${id}];
} else window.ids = [${id}];
`;
render() {
return (
<Fragment>
<Head>
<script
className="track"
dangerouslySetInnerHTML={{
__html: `${this.registerComponent(this.props.id)}`,
}}
/>
</Head>
<div id={this.props.id} />
</Fragment>
);
}
}
so the window.ids
variable will be available before the <App /> renders
.
here is a repos link to test it.
one other solution could be using a global variable on the server, then in each tracked component we could mutate
that global varibale using the componentWillMount life cycle hook since it will be executed on the server only, then
inject those ids on the html template <head />
, but this is possible if we are executing the renderToString method.
second solution: using the pages/_document instead of pages/_app so we get access to the server before it renders to string!
:
this is the repo branch: origin/workaround-using-document
-Child component:
export class Child extends React.Component {
render() {
return (
<Fragment>
<Head>
<meta className="track" id={this.props.id} />
</Head>
<div id={this.props.id} />
</Fragment>
);
}
}
-Document Component(replacing the App):
export default class MyDocument extends Document {
render() {
const { head } = this.props;
const idsFromHead = head.reduce(
(acc, { props }) =>
(props.className.includes('track') && [...acc, props.id]) || acc,
[]
);
return (
<html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `window.ids=[${idsFromHead}]`,
}}
/>
</Head>
<body className="custom_class">
<h3>{idsFromHead}</h3>
<Main />
<NextScript />
</body>
</html>
);
}
}
this approche works 100% on the server since we could capture the tracked ids before server rendrering occurs.
but the <NextScript />
is generating a warning(don't know why, could be a bug from nextjs):
React.createContext
) don't work at all in SSR. Discussed in github.com/zeit/next.js/issues/4182 / github.com/zeit/next.js/issues/4194 – MillstreamcomponentDidMount
only runs clientside. I need this to be done serverside. – Millstream