diff --git a/.eslintrc.js b/.eslintrc.js
index 1af7ec0b4..8c696b251 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -41,6 +41,7 @@ module.exports = {
jsx: "never",
mjs: "never",
ts: "never",
+ json: "always",
},
],
diff --git a/package.json b/package.json
index 778cc65b9..52979aa9b 100644
--- a/package.json
+++ b/package.json
@@ -49,12 +49,15 @@
"passport-google-oauth20": "^2.0.0",
"passport-local": "^1.0.0",
"pg": "^8.9.0",
+ "preact": "^10.15.1",
+ "preact-render-to-string": "^6.1.0",
+ "preact-ssr-prepass": "^1.2.0",
"pretty-error": "^3.0.4",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
+ "react": "npm:@preact/compat@*",
"react-autosuggest": "^10.0.2",
"react-bootstrap": "^2.7.0",
- "react-dom": "^18.2.0",
+ "react-dom": "npm:@preact/compat@*",
"react-flip-toolkit": "^7.0.17",
"react-geosuggest": "^2.14.1",
"react-icons": "^4.7.1",
diff --git a/src/client.tsx b/src/client.tsx
index d1bcc7a46..d2e45dd46 100644
--- a/src/client.tsx
+++ b/src/client.tsx
@@ -7,8 +7,8 @@
* LICENSE.txt file in the root directory of this source tree.
*/
+import { hydrate, render } from "preact";
import React, { useEffect } from "react";
-import { createRoot, hydrateRoot } from "react-dom/client";
import { Action, createPath, Location } from "history";
import App from "./components/App";
import configureStore from "./store/configureStore";
@@ -91,8 +91,6 @@ if (subdomain) {
const router = routerCreator(routes);
-const root = createRoot(container!);
-
// Re-render the app when window.location changes
const onLocationChange = async ({
action,
@@ -197,9 +195,9 @@ const onLocationChange = async ({
};
if (isInitialRender) {
- hydrateRoot(container!, );
+ hydrate(, container!);
} else {
- root.render();
+ render(, container!);
}
} catch (error) {
if (__DEV__) {
diff --git a/src/components/AddUserForm/AddUserForm.tsx b/src/components/AddUserForm/AddUserForm.tsx
index 42c212d0e..d7ebbd407 100644
--- a/src/components/AddUserForm/AddUserForm.tsx
+++ b/src/components/AddUserForm/AddUserForm.tsx
@@ -1,4 +1,4 @@
-import React, { ChangeEvent, Component, FormEvent } from "react";
+import React, { ChangeEvent, Component, TargetedEvent } from "react";
import Button from "react-bootstrap/Button";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
@@ -38,9 +38,9 @@ class AddUserForm extends Component {
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) =>
- this.setState({ [field]: event.target.value });
+ this.setState({ [field]: event.currentTarget.value });
- handleSubmit = (event: FormEvent) => {
+ handleSubmit = (event: TargetedEvent) => {
event.preventDefault();
this.props.addUserToTeam(this.state);
this.setState({ ...AddUserForm.defaultState });
diff --git a/src/components/App.tsx b/src/components/App.tsx
index cba74cd8d..5ed4cab62 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -9,7 +9,8 @@
import StyleContext, { InsertCSS } from "isomorphic-style-loader/StyleContext";
import PropTypes from "prop-types";
-import React, { Children, ReactNode } from "react";
+import React from "react";
+import { ComponentChildren } from "preact";
import { Provider as ReduxProvider } from "react-redux";
import { Libraries, Loader } from "@googlemaps/js-api-loader";
import { AppContext } from "../interfaces";
@@ -26,7 +27,7 @@ const ContextType = {
};
interface AppProps {
- children: ReactNode;
+ children?: ComponentChildren;
context: AppContext;
}
@@ -63,6 +64,10 @@ class App extends React.PureComponent {
static childContextTypes = ContextType;
+ static defaultProps = {
+ children: null,
+ };
+
constructor(props: AppProps) {
super(props);
@@ -88,7 +93,7 @@ class App extends React.PureComponent {
- {Children.only(this.props.children)}
+ {this.props.children}
diff --git a/src/components/ChangeTeamURLModal/ChangeTeamURLModal.tsx b/src/components/ChangeTeamURLModal/ChangeTeamURLModal.tsx
index 400bcbc55..d75641904 100644
--- a/src/components/ChangeTeamURLModal/ChangeTeamURLModal.tsx
+++ b/src/components/ChangeTeamURLModal/ChangeTeamURLModal.tsx
@@ -40,9 +40,9 @@ class ChangeTeamURLModal extends Component<
handleChange =
(field: keyof ChangeTeamURLModalState) =>
- (event: ChangeEvent) =>
+ (event: ChangeEvent) =>
this.setState({
- [field]: event.target.value,
+ [field]: event.currentTarget.value,
});
handleSubmit = () => {
diff --git a/src/components/ConfirmModal/ConfirmModal.tsx b/src/components/ConfirmModal/ConfirmModal.tsx
index 370d1ff79..b657e7f89 100644
--- a/src/components/ConfirmModal/ConfirmModal.tsx
+++ b/src/components/ConfirmModal/ConfirmModal.tsx
@@ -1,4 +1,5 @@
-import React, { ReactNode } from "react";
+import React from "react";
+import { ComponentChildren } from "preact";
import Modal from "react-bootstrap/Modal";
import ModalBody from "react-bootstrap/ModalBody";
import ModalFooter from "react-bootstrap/ModalFooter";
@@ -8,7 +9,7 @@ export interface ConfirmModalProps {
actionLabel: string;
shown: boolean;
hideModal: () => void;
- body: ReactNode;
+ body: ComponentChildren;
handleSubmit: () => void;
}
diff --git a/src/components/DeleteTeamModal/DeleteTeamModal.tsx b/src/components/DeleteTeamModal/DeleteTeamModal.tsx
index bfddad417..24f79d92f 100644
--- a/src/components/DeleteTeamModal/DeleteTeamModal.tsx
+++ b/src/components/DeleteTeamModal/DeleteTeamModal.tsx
@@ -36,9 +36,11 @@ class DeleteTeamModal extends Component<
};
}
- handleChange = (event: ChangeEvent) => {
+ handleChange = (
+ event: ChangeEvent
+ ) => {
this.setState({
- confirmSlug: event.target.value,
+ confirmSlug: event.currentTarget.value,
});
};
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
index 7904e00f3..1b94ae575 100644
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -7,7 +7,8 @@
* LICENSE.txt file in the root directory of this source tree.
*/
-import React, { Component, ReactNode } from "react";
+import React, { Component } from "react";
+import { VNode } from "preact";
import PropTypes from "prop-types";
import { InsertCSS } from "isomorphic-style-loader/StyleContext";
// eslint-disable-next-line css-modules/no-unused-class, no-unused-vars
@@ -22,7 +23,7 @@ import s from "./Layout.scss";
const emptyFunction = () => undefined;
export interface LayoutProps {
- children: ReactNode;
+ children: VNode;
isHome?: boolean;
path: string;
shouldScrollToTop: boolean;
diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx
index 185b43c2d..11844d62a 100644
--- a/src/components/Link/Link.tsx
+++ b/src/components/Link/Link.tsx
@@ -7,7 +7,8 @@
* LICENSE.txt file in the root directory of this source tree.
*/
-import React, { Component, MouseEvent, ReactNode } from "react";
+import React, { Component, HTMLAttributes } from "react";
+import { ComponentChildren } from "preact";
import history from "../../history";
function isLeftClickEvent(event: MouseEvent) {
@@ -18,9 +19,9 @@ function isModifiedEvent(event: MouseEvent) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
-interface LinkProps extends React.HTMLProps {
+interface LinkProps extends HTMLAttributes {
to: string;
- children: ReactNode;
+ children: ComponentChildren;
onClick?: (event: MouseEvent) => void;
}
diff --git a/src/components/NameFilterForm/NameFilterForm.tsx b/src/components/NameFilterForm/NameFilterForm.tsx
index 1e86b568e..07f94a812 100644
--- a/src/components/NameFilterForm/NameFilterForm.tsx
+++ b/src/components/NameFilterForm/NameFilterForm.tsx
@@ -58,7 +58,7 @@ class NameFilterForm extends Component<
};
setNameFilterValue = (event: ChangeEvent) => {
- this.props.setNameFilter(event.target.value);
+ this.props.setNameFilter(event.currentTarget.value);
};
hideForm = () => {
diff --git a/src/components/NotificationList/NotificationList.tsx b/src/components/NotificationList/NotificationList.tsx
index 414ac2cba..482706d27 100644
--- a/src/components/NotificationList/NotificationList.tsx
+++ b/src/components/NotificationList/NotificationList.tsx
@@ -12,6 +12,7 @@ interface NotificationListProps {
const NotificationList = ({ notifications }: NotificationListProps) => (
+ {/* @ts-expect-error: expects component instead of children */}
{notifications.map((notification) => (
) =>
this.setState({
- daysAgo: Number(event.target.value),
+ daysAgo: Number(event.currentTarget.value),
});
handleSubmit = () => {
diff --git a/src/components/RestaurantAddForm/RestaurantAddForm.tsx b/src/components/RestaurantAddForm/RestaurantAddForm.tsx
index 197f62742..69142cded 100644
--- a/src/components/RestaurantAddForm/RestaurantAddForm.tsx
+++ b/src/components/RestaurantAddForm/RestaurantAddForm.tsx
@@ -74,7 +74,7 @@ class RestaurantAddForm extends Component {
return (