-
Notifications
You must be signed in to change notification settings - Fork 1
Add <ClientSideOnly /> component #20
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { createElement, Component } from 'react'; | ||
import { func, node } from 'prop-types'; | ||
|
||
export default class ClientSideOnly extends Component { | ||
static propTypes = { | ||
onServerRender: func, | ||
children: node | ||
}; | ||
|
||
static defaultProps = { | ||
onServerRender: () => null | ||
}; | ||
|
||
static isSSR() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a Other alternatives include:
I chose the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The static method works fine. If we need to change it at build time, it's a small change, and it allows for a lot of runtime flexibility. Of course, prototypal inheritance allows us to make it an instance method and still affect all instances at once... ClientSideOnly.prototype.isSSR = () => true; But I think that confuses most people, so your way is better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, can be in the proto chain. I tend to only put things on the prototype that rely on instance-specific context, and other related things end up as static properties. Of course, that's purely a (personal) stylistic thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do the same thing. Leave it static! |
||
return process.env.NODE_ENV === 'server'; | ||
} | ||
|
||
render() { | ||
return ClientSideOnly.isSSR() | ||
? this.props.onServerRender() | ||
: this.props.children; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not the most voracious consumer of React components, so this is a stylistic issue that most people might not have. Paging @jimbo for a second opinion. But I think it might be confusing to take a function-as-prop for server content, but regular old children for client content. If this was an intentional choice to encourage writing client-only content rather than server-only content, I dig it...but I'm still not sure how readable it is. What if maybe: import OnlyRender from '..';
// [...]
test('Returns value from invoking serverSide prop in SSR mode', () => {
jest.spyOn(OnlyRender, 'isSSR').mockImplementation(() => true);
const wrapper = shallow(
<OnlyRender
serverSide={() => <div>Server Content</div>}
clientSide={() => <span>Client Content</span>}
/>
);
expect(wrapper.text()).toEqual('Server Content');
OnlyRender.isSSR.mockReset();
});
test('Returns default value (null) when serverSide prop is not provided', () => {
jest.spyOn(OnlyRender, 'isSSR').mockImplementation(() => true);
const wrapper = shallow(<OnlyRender clientSide={() => <div>Client Content</div>} />);
expect(wrapper.text()).toEqual('');
OnlyRender.isSSR.mockReset();
});
test('Returns value from invoking clientSide prop in non-SSR mode', () => {
jest.spyOn(OnlyRender, 'isSSR').mockImplementation(() => false);
const wrapper = shallow(
<OnlyRender
serverSide={() => <div>Server Content</div>}
clientSide={() => <span>Client Content</span>}
/>
);
expect(wrapper.text()).toEqual('Client Content');
OnlyRender.isSSR.mockReset();
}); Or what about just letting people specify server-only and client-only content independently? import OnlyRender from '..';
// [...]
test('Renders children of ServerSide component only in SSR mode', () => {
jest.spyOn(OnlyRender, 'isSSR').mockImplementation(() => true);
// OnlyRender itself is a pass-thru for a convenient top-level node
const wrapper = shallow(
<OnlyRender>
<OnlyRender.ServerSide><div>Server Content</div></OnlyRender.ServerSide>
<OnlyRender.ClientSide><div>Client Content</div></OnlyRender.ClientSide>
</OnlyRender>
);
expect(wrapper.text()).toEqual('Server Content');
OnlyRender.isSSR.mockReset();
});
test('Renders children of ClientSide component only in non-SSR mode', () => {
jest.spyOn(OnlyRender, 'isSSR').mockImplementation(() => false);
const wrapper = shallow(
<OnlyRender>
<OnlyRender.ServerSide><div>Server Content</div></OnlyRender.ServerSide>
<OnlyRender.ClientSide><div>Client Content</div></OnlyRender.ClientSide>
</OnlyRender>
);
expect(wrapper.text()).toEqual('Client Content');
OnlyRender.isSSR.mockReset();
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I don't feel especially strongly about this, so if neither of those suggestions are compelling, feel free to brush them off--your API is just fine, and I'm only trying to think about the newbie.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tbh, the current API is the way it is just because it was the first idea that came to mind. I'm not a huge fan of the I'll go with whichever one of the ideas @jimbo ends up preferring There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good discussion. I think @DrewML has the right idea with the original implementation here. This is a pass-through component (render signature of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 10-4, sounds like your current API will be great. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import ClientSideOnly from '..'; | ||
import { createElement } from 'react'; | ||
import { configure, shallow } from 'enzyme'; | ||
import Adapter from 'enzyme-adapter-react-16'; | ||
|
||
configure({ adapter: new Adapter() }); | ||
|
||
test('Returns value from invoking onServerRender prop in SSR mode', () => { | ||
jest.spyOn(ClientSideOnly, 'isSSR').mockImplementation(() => true); | ||
const wrapper = shallow( | ||
<ClientSideOnly onServerRender={() => <div>Server Content</div>}> | ||
Client Content | ||
</ClientSideOnly> | ||
); | ||
expect(wrapper.text()).toEqual('Server Content'); | ||
ClientSideOnly.isSSR.mockReset(); | ||
}); | ||
|
||
test('Returns default value (null) when onServerRender is not provided', () => { | ||
jest.spyOn(ClientSideOnly, 'isSSR').mockImplementation(() => true); | ||
const wrapper = shallow(<ClientSideOnly>Client Content</ClientSideOnly>); | ||
expect(wrapper.text()).toEqual(''); | ||
ClientSideOnly.isSSR.mockReset(); | ||
}); | ||
|
||
test('Returns children in non-SSR mode', () => { | ||
jest.spyOn(ClientSideOnly, 'isSSR').mockImplementation(() => false); | ||
const wrapper = shallow( | ||
<ClientSideOnly onServerRender={() => <div>Server Content</div>}> | ||
Client Content | ||
</ClientSideOnly> | ||
); | ||
expect(wrapper.text()).toEqual('Client Content'); | ||
ClientSideOnly.isSSR.mockReset(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './ClientSideOnly'; |
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.
onServerRender
is optional for the cases where you really just don't want anything rendered