Skip to content

Commit

Permalink
refactor: Remove old react lifecycle methods (outline#1480)
Browse files Browse the repository at this point in the history
* refactor: Remove deprecated APIs

* bump mobx-react for hooks support

* inject -> useStores
https://mobx-react.js.org/recipes-migration\#hooks-to-the-rescue

* chore: React rules of hooks lint
  • Loading branch information
tommoor authored Aug 23, 2020
1 parent 179176c commit ec38f5d
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 176 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"react-app",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:flowtype/recommended"
"plugin:flowtype/recommended",
"plugin:react-hooks/recommended"
],
"plugins": [
"prettier",
Expand Down
2 changes: 1 addition & 1 deletion app/components/DelayedMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function DelayedMount({ delay = 250, children }: Props) {
return () => {
clearTimeout(timeout);
};
}, []);
}, [delay]);

if (!isShowing) {
return null;
Expand Down
16 changes: 10 additions & 6 deletions app/components/HoverPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ type Props = {
onClose: () => void,
};

function HoverPreview({ node, documents, onClose, event }: Props) {
// previews only work for internal doc links for now
if (!isInternalUrl(node.href)) {
return null;
}

function HoverPreviewInternal({ node, documents, onClose, event }: Props) {
const slug = parseDocumentSlugFromUrl(node.href);

const [isVisible, setVisible] = React.useState(false);
Expand Down Expand Up @@ -131,6 +126,15 @@ function HoverPreview({ node, documents, onClose, event }: Props) {
);
}

function HoverPreview({ node, ...rest }: Props) {
// previews only work for internal doc links for now
if (!isInternalUrl(node.href)) {
return null;
}

return <HoverPreviewInternal {...rest} node={node} />;
}

const Animate = styled.div`
animation: ${fadeAndSlideIn} 150ms ease;
Expand Down
11 changes: 6 additions & 5 deletions app/components/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,22 @@ class Layout extends React.Component<Props> {
@observable redirectTo: ?string;
@observable keyboardShortcutsOpen: boolean = false;

componentWillMount() {
this.updateBackground();
constructor(props) {
super();
this.updateBackground(props);
}

componentDidUpdate() {
this.updateBackground();
this.updateBackground(this.props);

if (this.redirectTo) {
this.redirectTo = undefined;
}
}

updateBackground() {
updateBackground(props) {
// ensure the wider page color always matches the theme
window.document.body.style.background = this.props.theme.background;
window.document.body.style.background = props.theme.background;
}

@keydown("shift+/")
Expand Down
3 changes: 2 additions & 1 deletion app/components/Mask.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class Mask extends React.Component<Props> {
return false;
}

componentWillMount() {
constructor() {
super();
this.width = randomInteger(75, 100);
}

Expand Down
75 changes: 35 additions & 40 deletions app/components/Sidebar/Sidebar.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,60 @@
// @flow
import { observer, inject } from "mobx-react";
import { observer } from "mobx-react";
import { CloseIcon, MenuIcon } from "outline-icons";
import * as React from "react";
import { withRouter } from "react-router-dom";
import type { Location } from "react-router-dom";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import UiStore from "stores/UiStore";
import Fade from "components/Fade";
import Flex from "components/Flex";
import usePrevious from "hooks/usePrevious";
import useStores from "hooks/useStores";

let firstRender = true;

type Props = {
children: React.Node,
location: Location,
ui: UiStore,
};

@observer
class Sidebar extends React.Component<Props> {
componentWillReceiveProps = (nextProps: Props) => {
if (this.props.location !== nextProps.location) {
this.props.ui.hideMobileSidebar();
}
};
function Sidebar({ location, children }: Props) {
const { ui } = useStores();
const previousLocation = usePrevious(location);

toggleSidebar = () => {
this.props.ui.toggleMobileSidebar();
};
React.useEffect(() => {
if (location !== previousLocation) {
ui.hideMobileSidebar();
}
}, [ui, location]);

render() {
const { children, ui } = this.props;
const content = (
<Container
editMode={ui.editMode}
const content = (
<Container
editMode={ui.editMode}
mobileSidebarVisible={ui.mobileSidebarVisible}
column
>
<Toggle
onClick={ui.toggleMobileSidebar}
mobileSidebarVisible={ui.mobileSidebarVisible}
column
>
<Toggle
onClick={this.toggleSidebar}
mobileSidebarVisible={ui.mobileSidebarVisible}
>
{ui.mobileSidebarVisible ? (
<CloseIcon size={32} />
) : (
<MenuIcon size={32} />
)}
</Toggle>
{children}
</Container>
);
{ui.mobileSidebarVisible ? (
<CloseIcon size={32} />
) : (
<MenuIcon size={32} />
)}
</Toggle>
{children}
</Container>
);

// Fade in the sidebar on first render after page load
if (firstRender) {
firstRender = false;
return <Fade>{content}</Fade>;
}

return content;
// Fade in the sidebar on first render after page load
if (firstRender) {
firstRender = false;
return <Fade>{content}</Fade>;
}

return content;
}

const Container = styled(Flex)`
Expand Down Expand Up @@ -117,4 +112,4 @@ const Toggle = styled.a`
`};
`;

export default withRouter(inject("ui")(Sidebar));
export default withRouter(observer(Sidebar));
142 changes: 71 additions & 71 deletions app/components/Sidebar/components/SidebarLink.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @flow
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import { CollapsedIcon } from "outline-icons";
import * as React from "react";
Expand All @@ -25,79 +24,80 @@ type Props = {
depth?: number,
};

@observer
class SidebarLink extends React.Component<Props> {
@observable expanded: ?boolean = this.props.expanded;

style = {
paddingLeft: `${(this.props.depth || 0) * 16 + 16}px`,
};
function SidebarLink({
icon,
children,
onClick,
to,
label,
active,
menu,
menuOpen,
hideDisclosure,
theme,
exact,
href,
depth,
...rest
}: Props) {
const [expanded, setExpanded] = React.useState(rest.expanded);

const style = React.useMemo(() => {
return {
paddingLeft: `${(depth || 0) * 16 + 16}px`,
};
}, [depth]);

componentWillReceiveProps(nextProps: Props) {
if (nextProps.expanded !== undefined) {
this.expanded = nextProps.expanded;
React.useEffect(() => {
if (rest.expanded) {
setExpanded(rest.expanded);
}
}

@action
handleClick = (ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();

this.expanded = !this.expanded;
}, [rest.expanded]);

const handleClick = React.useCallback(
(ev: SyntheticEvent<>) => {
ev.preventDefault();
ev.stopPropagation();
setExpanded(!expanded);
},
[expanded]
);

const handleExpand = React.useCallback(() => {
setExpanded(true);
}, []);

const showDisclosure = !!children && !hideDisclosure;
const activeStyle = {
color: theme.text,
background: theme.sidebarItemBackground,
fontWeight: 600,
...style,
};

@action
handleExpand = () => {
this.expanded = true;
};

render() {
const {
icon,
children,
onClick,
to,
label,
active,
menu,
menuOpen,
hideDisclosure,
exact,
href,
} = this.props;
const showDisclosure = !!children && !hideDisclosure;
const activeStyle = {
color: this.props.theme.text,
background: this.props.theme.sidebarItemBackground,
fontWeight: 600,
...this.style,
};

return (
<Wrapper column>
<StyledNavLink
activeStyle={activeStyle}
style={active ? activeStyle : this.style}
onClick={onClick}
exact={exact !== false}
to={to}
as={to ? undefined : href ? "a" : "div"}
href={href}
>
{icon && <IconWrapper>{icon}</IconWrapper>}
<Label onClick={this.handleExpand}>
{showDisclosure && (
<Disclosure expanded={this.expanded} onClick={this.handleClick} />
)}
{label}
</Label>
{menu && <Action menuOpen={menuOpen}>{menu}</Action>}
</StyledNavLink>
{this.expanded && children}
</Wrapper>
);
}
return (
<Wrapper column>
<StyledNavLink
activeStyle={activeStyle}
style={active ? activeStyle : style}
onClick={onClick}
exact={exact !== false}
to={to}
as={to ? undefined : href ? "a" : "div"}
href={href}
>
{icon && <IconWrapper>{icon}</IconWrapper>}
<Label onClick={handleExpand}>
{showDisclosure && (
<Disclosure expanded={expanded} onClick={handleClick} />
)}
{label}
</Label>
{menu && <Action menuOpen={menuOpen}>{menu}</Action>}
</StyledNavLink>
{expanded && children}
</Wrapper>
);
}

// accounts for whitespace around icon
Expand Down Expand Up @@ -171,4 +171,4 @@ const Disclosure = styled(CollapsedIcon)`
${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
`;

export default withRouter(withTheme(SidebarLink));
export default withRouter(withTheme(observer(SidebarLink)));
10 changes: 10 additions & 0 deletions app/hooks/usePrevious.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow
import * as React from "react";

export default function usePrevious(value: any) {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
8 changes: 8 additions & 0 deletions app/hooks/useStores.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow
import { MobXProviderContext } from "mobx-react";
import * as React from "react";
import RootStore from "stores";

export default function useStores(): typeof RootStore {
return React.useContext(MobXProviderContext);
}
Loading

0 comments on commit ec38f5d

Please sign in to comment.