Richard Lindsey @Velveeta
const ResizeObserver = class extends React.Component {
_resizeObserver = new ResizeObserverPolyfill((entries) => {
const [entry] = entries;
this._updateFromDOMRect(entry.contentRect);
});
state = {
height: null,
ref: React.createRef(),
width: null,
};
componentDidMount() {
this._resizeObserver.observe(this.state.ref.current);
this._updateFromDOMRect(this.state.ref.current.getBoundingClientRect());
}
componentWillUnmount() {
this._resizeObserver.disconnect();
}
_updateFromDOMRect(rect) {
this.setState({
height: rect.height,
width: rect.width,
});
}
render() {
return this.props.children(this.state);
}
};
const MyResizeableComponent = () => (
<ResizeObserver>
{({ height, ref, width }) => (
<div ref={ref}>
My dimensions are {width}x{height} pixels!
</div>
)}
</ResizeObserver>
);
const UserDataContext = React.createContext();
const { Provider } = UserDataContext;
const UserDataProvider = class extends React.Component {
_poolingTimeoutId = null;
_queuedRequestIds = [];
state = {
users: {},
};
componentWillUnmount() {
if (this._poolingTimeoutId) {
clearTimeout(this._poolingTimeoutId);
}
}
_issueRequest() {
fetch('/users', {
userIds: this._queuedRequestIds,
})
.then(resp => resp.json())
.then(users => {
this.setState(state => ({
...state,
users: users.reduce((acc, user) => {
acc[user.userId] = user;
return acc;
}, { ...state.users }),
}))
});
this._queuedRequestIds = [];
}
_request = (userId) => {
if (!this.state.users[userId]) {
this._queuedRequestIds.push(userId);
if (!this._poolingTimeoutId) {
this._poolingTimeoutId = setTimeout(() => {
this._poolingTimeoutId = null;
this._issueRequest();
}, this.props.poolingTimeout);
}
}
};
render() {
const value = {
request: this._request,
users: this.state.users,
}
return <Provider value={value}>{this.props.children}</Provider>;
}
};const UserDataConsumer = class extends React.Component {
static contextType = UserDataContext;
componentDidMount() {
this.context.request(this.props.userId);
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.context.request(this.props.userId);
}
}
render() {
const user = this.context.users[this.props.userId];
const props = {
isLoading: !user,
user,
};
return this.props.children(props);
}
};
const UserInfo = ({ userId }) => (
<UserDataConsumer userId={userId}>
{({ isLoading, user }) => (
{isLoading
? <LoadingSpinner />
: <UserProfile user={user} />}
)}
</UserDataConsumer>
);
const UserList = ({ userIds }) => (
<React.Fragment>
{userIds.map(userId => (
<UserInfo userId={userId} />
))}
</React.Fragment>
);
const UserDataConsumer = ({ children, userId }) => {
const { request, users } = useContext(UserDataContext);
useEffect(() => {
request(userId);
}, [request, userId]);
const user = users[userId];
const props = {
isLoading: !user,
user,
};
return children(props);
};
const useUserData = (userId) => {
const { request, users } = useContext(UserRestfulResourceFetcherContext);
useEffect(() => {
request(userId);
}, [request, userId]);
return users[userId];
};
const UserDataConsumer = ({ children, userId }) => {
const user = useUserData(userId);
const props = {
isLoading: !user,
user,
};
return children(props);
};
import React from 'react';
import SomeService from 'services/some-service';
const withSomeFeature = Component => {
const ComponentWithSomeFeature = class extends React.Component {
static displayName = `SomeFeature(${Component.displayName})`;
state = {
someFeatureResult: null;
}
componentDidMount() {
this._computeSomeFeatureResult();
}
componentDidUpdate() {
this._computeSomeFeatureResult();
}
_computeSomeFeatureResult() {
this.setState({
someFeatureResult: SomeService.getResults(this.props),
});
}
render() {
return <Component someFeature={this.state.someFeatureResult} {...this.props} />;
}
};
return ComponentWithSomeFeature;
}
export default withSomeFeature;
import React from 'react';
import withSomeFeature from 'hocs/with-some-feature';
const MyComponent = ({ children, someFeature }) => {
if (!someFeature) {
return null;
}
return (
<div>
<div>Some feature is {someFeature}!</div>
{children}
</div>
};
export default withSomeFeature(MyComponent);
import React from 'react';
const MyStatefulComponent = class extends React.Component {
_ref = React.createRef();
state = {
isOpen: this.props.isOpen,
};
componentDidMount() {
this.setState({ isOpen: this.props.isOpen });
if (this.props.isOpen) {
document.addEventListener('click', this._documentClick);
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.isOpen !== this.props.isOpen) {
this.setState({ isOpen: this.props.isOpen });
}
if (prevState.isOpen !== this.state.isOpen) {
if (this.state.isOpen) {
document.addEventListener('click', this._documentClick);
} else {
document.removeEventListener('click', this._documentClick);
}
}
}
componentWillUnmount() {
if (this.state.isOpen) {
document.removeEventListener('click', this._documentClick);
}
}
_documentClick = (e) => {
if (!this._ref.current.contains(e.target)) {
this.setState({ isOpen: false });
}
};
_onSelect = (e) => {
this.props.onChange(e.target.dataset.value);
this.setState({ isOpen: false });
};
render() {
const {
options,
value,
} = this.props;
return (
<div>
<div className="dropdown-value">{value}</div>
{isOpen && (
<div className="dropdown-list" ref={this._ref}>
{options.map(option => {
<div data-value={option.value} key={option.value} onClick={this._onSelect}>
{option.display}
</div>
})}
</div>
)}
</div>
);
}
}
import React, { useCallback, useEffect, useRef, useState } from 'react';
const MyStatefulFunctionalComponent = ({
isOpen: isOpenProp,
onChange,
options,
value,
}) => {
const ref = useRef();
const [isOpen, setIsOpen] = useState(isOpenProp);
useEffect(() => {
if (isOpen) {
const documentClick = (e) => {
if (!ref.current.contains(e.target)) {
setIsOpen(false);
}
};
document.addEventListener('click', documentClick);
return () => {
document.removeEventListener('click', documentClick);
};
}
}, [isOpen]);
useEffect(() => {
setIsOpen(isOpenProp);
}, [isOpenProp]);
const onSelect = useCallback((e) => {
onChange(e.target.dataset.value);
setIsOpen(false);
}, [onChange]);
return (
<div>
<div className="dropdown-value">{value}</div>
{isOpen && (
<div className="dropdown-list" ref={ref}>
{options.map(option => {
<div data-value={option.value} key={option.value} onClick={onSelect}>
{option.display}
</div>
})}
</div>
)}
</div>
);
};
import React, { lazy, Suspense } from 'react';
import LoadingSpinner from '../components/loading-spinner';
import Modal from '../components/modal';
import Router from '../router';
import TermsAndServicesLink from '../components/tos-link';
const TermsAndServices = lazy(() => import('../legal/tos'));
const Layout = () => {
const [showTos, setShowTos] = useState(false);
return (
<React.Fragment>
<main>
<Router />
</main>
<footer>
<TermsAndServicesLink onClick={() => setShowTos(true)} />
{showTos && (
<Modal onClose={() => setShowTos(false)}>
<Suspense fallback={<LoadingSpinner />}>
<TermsAndServices />
</Suspense>
</Modal>
)}
</footer>
</div>
);
};
export default Layout;
Day 2 complete, congratulations!
Richard Lindsey @Velveeta