Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to React 18 #4992

Merged
merged 14 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .swcrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "https://swc.rs/schema.json",
"sourceMaps": true,
"jsc": {
"parser": {
Expand All @@ -7,7 +8,7 @@
},
"transform": {
"react": {
"runtime": "classic",
"runtime": "automatic",
"refresh": true
}
}
Expand Down
6 changes: 3 additions & 3 deletions jsapp/js/account/organizations/requireOrgOwner.component.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {ReactElement, Suspense, useContext, useEffect} from 'react';
import {RouteObject, useNavigate} from 'react-router-dom';
import React, {Suspense, useContext, useEffect} from 'react';
import {useNavigate} from 'react-router-dom';
import {OrganizationContext} from 'js/account/organizations/useOrganization.hook';
import LoadingSpinner from 'js/components/common/loadingSpinner';
import {ACCOUNT_ROUTES} from 'js/account/routes.constants';

interface Props {
children: RouteObject[] | undefined | ReactElement;
children: React.ReactNode;
redirect?: boolean;
}

Expand Down
4 changes: 2 additions & 2 deletions jsapp/js/account/plans/billingButton.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import cx from 'classnames';
import type {ButtonProps} from 'js/components/common/button';
import Button from 'js/components/common/button';
import React from 'react';
import {button} from './billingButton.module.scss';
import styles from './billingButton.module.scss';

/**
* The base button component that's used on the Plans/Add-ons pages.
Expand All @@ -16,7 +16,7 @@ export default function BillingButton(props: Partial<ButtonProps>) {
color='blue'
size='l'
{...props}
className={cx([button, props.className])}
className={cx([styles.button, props.className])}
isFullWidth
/>
);
Expand Down
4 changes: 2 additions & 2 deletions jsapp/js/account/plans/plan.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export default function Plan(props: PlanProps) {
const getSubscribedProduct = useCallback(getSubscriptionsForProductId, []);

const isSubscribedProduct = useCallback(
(product: SinglePricedProduct, quantity = null) => {
(product: SinglePricedProduct, quantity: number | undefined) => {
if (!product.price?.unit_amount && !hasActiveSubscription) {
return true;
}
Expand All @@ -356,7 +356,7 @@ export default function Plan(props: PlanProps) {
(subscription: SubscriptionInfo) =>
subscription.items[0].price.id === product.price.id &&
hasManageableStatus(subscription) &&
quantity &&
quantity !== undefined &&
quantity === subscription.quantity
);
}
Expand Down
4 changes: 4 additions & 0 deletions jsapp/js/account/security/email/emailSection.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export default function EmailSection() {
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>

<Button
Expand All @@ -178,6 +179,9 @@ export default function EmailSection() {
color='blue'
type='frame'
onClick={setNewUserEmail.bind(setNewUserEmail, email.newEmail)}
// quick simple subtle email validation to avoid complete accidents
// a toast showing any API error feedback would be nicer
isDisabled={!/[^@]+@[^@]+\.[^@]+/.test(email.newEmail)}
p2edwards marked this conversation as resolved.
Show resolved Hide resolved
/>
</form>
</div>
Expand Down
4 changes: 2 additions & 2 deletions jsapp/js/account/usage/usageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ const UsageContainer = ({
/**
* Render a limit amount, usage amount, or total balance as readable text
* @param {number|'unlimited'} amount - The limit/usage amount
* @param {number|'unlimited'|null} [available=null] - If we're showing a balance,
* @param {number|'unlimited'} [available] - If we're showing a balance,
* `amount` takes the usage amount and this takes the limit amount
*/
const limitDisplay = useCallback(
(amount, available = null) => {
(amount: LimitAmount, available?: LimitAmount) => {
if (amount === Limits.unlimited || available === Limits.unlimited) {
return t('Unlimited');
}
Expand Down
1 change: 1 addition & 0 deletions jsapp/js/components/common/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface TooltipProps {
ariaLabel: string;
/** Position of the tooltip (centered as default) */
alignment?: TooltipAlignment;
children?: React.ReactNode;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion jsapp/js/components/reports/reportTable.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ export default class ReportTable extends React.Component<ReportTableProps> {
{rows.map((row) => (
<tr key={row[0]}>
{row.map((r, i: number) => (
<td key={i}>{r}</td>
// Note: See TODO above. ReportTableProps probably deserves a
// second look, and TypeScript is trying to tell us something
// about the value of 'r' here.
<td key={i}>{r as React.ReactNode}</td>
))}
</tr>
))}
Expand Down
35 changes: 27 additions & 8 deletions jsapp/js/components/submissions/submissionUtils.tests.es6
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ import {
import chaiExclude from 'chai-exclude';
chai.use(chaiExclude);

// getSubmissionDisplayData might return objects with declared, undefined key:
// {... "label": "hi", "listName": undefined, "name": "hi" ...}
// Assuming this is correct, test fixtures like this are equivalent enough:
// {... "label": "hi", "name": "hi" ...}
// After a recent chai / deep-eql update, tests relying on this behavior would
// fail. Hence, use this looser comparison function.
import chaiDeepEqualIgnoreUndefined from 'chai-deep-equal-ignore-undefined'
chai.use(chaiDeepEqualIgnoreUndefined);

describe('getSubmissionDisplayData', () => {
it('should return a valid data for a survey with a group', () => {
const test = getSubmissionDisplayData(
Expand All @@ -55,7 +64,7 @@ describe('getSubmissionDisplayData', () => {
const target = simpleSurveyDisplayData;
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deep.equal(target);
.to.deepEqualIgnoreUndefined(target);
});

it('should return a null data entries for a survey with no answers', () => {
Expand All @@ -70,7 +79,7 @@ describe('getSubmissionDisplayData', () => {
const target = simpleSurveyDisplayDataEmpty;
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deep.equal(target);
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for a survey with a repeat group', () => {
Expand All @@ -85,7 +94,7 @@ describe('getSubmissionDisplayData', () => {
const target = repeatSurveyDisplayData;
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deep.equal(target);
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for a survey with nested repeat groups', () => {
Expand All @@ -98,7 +107,9 @@ describe('getSubmissionDisplayData', () => {
},
}, 0, nestedRepeatSurveySubmission).children;
const target = nestedRepeatSurveyDisplayData;
expect(test).excludingEvery(['__proto__', 'xpathNodes']).to.deep.equal(target);
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for a survey with a matrix', () => {
Expand All @@ -111,7 +122,9 @@ describe('getSubmissionDisplayData', () => {
},
}, 0, matrixSurveySubmission).children;
const target = matrixSurveyDisplayData;
expect(test).excludingEvery(['__proto__', 'xpathNodes']).to.deep.equal(target);
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for a survey with all kinds of groups', () => {
Expand All @@ -124,7 +137,9 @@ describe('getSubmissionDisplayData', () => {
},
}, 0, groupsSurveySubmission).children;
const target = groupsSurveyDisplayData;
expect(test).excludingEvery(['__proto__', 'xpathNodes']).to.deep.equal(target);
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for every possible question type', () => {
Expand All @@ -137,7 +152,9 @@ describe('getSubmissionDisplayData', () => {
},
}, 0, everythingSurveySubmission).children;
const target = everythingSurveyDisplayData;
expect(test).excludingEvery(['__proto__', 'xpathNodes']).to.deep.equal(target);
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deepEqualIgnoreUndefined(target);
});

it('should return a valid data for a matrix group inside repeat group', () => {
Expand All @@ -150,7 +167,9 @@ describe('getSubmissionDisplayData', () => {
},
}, 0, matrixRepeatSurveySubmission).children;
const target = matrixRepeatSurveyDisplayData;
expect(test).excludingEvery(['__proto__', 'xpathNodes']).to.deep.equal(target);
expect(test)
.excludingEvery(['__proto__', 'xpathNodes'])
.to.deepEqualIgnoreUndefined(target);
});
});

Expand Down
35 changes: 10 additions & 25 deletions jsapp/js/main.es6
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@
* plus it is the file that is handling the root rendering.
*/

require('jquery-ui/ui/widgets/sortable');
import 'jquery-ui/ui/widgets/sortable';
import moment from 'moment';
import AllRoutes from 'js/router/allRoutes';
import RegistrationPasswordApp from './registrationPasswordApp';
import {AppContainer} from 'react-hot-loader';
import React from 'react';
import {Cookies} from 'react-cookie';
import {render} from 'react-dom';
import {createRoot} from 'react-dom/client';
import * as Sentry from '@sentry/react';
import {csrfSafeMethod, currentLang} from 'utils';
require('../scss/main.scss');
import '../scss/main.scss';
import Modal from 'react-modal';

let sentryDsnEl = document.head.querySelector('meta[name=sentry-dsn]');
const sentryDsnEl = document.head.querySelector('meta[name=sentry-dsn]');
if (sentryDsnEl !== null) {
Sentry.init({
dsn: sentryDsnEl.content,
tracesSampleRate: 0.0,
sendClientReports: false,
autoSessionTracking: false,
})
});
window.Raven = Sentry; // Legacy use (formbuilder)
/*
In TS files, it's safe to do
Expand Down Expand Up @@ -86,22 +85,10 @@ if (document.head.querySelector('meta[name=kpi-root-path]')) {
Modal.setAppElement('#kpiapp');
return $d.get(0);
})();

render(<AllRoutes />, el);

if (module.hot) {
module.hot.accept('js/app', () => {
const AllRoutes = require('js/app').default;
render(
<AppContainer>
<AllRoutes />
</AppContainer>,
el
);
});
}
const root = createRoot(el);
root.render(<AllRoutes />);
p2edwards marked this conversation as resolved.
Show resolved Hide resolved
} else {
console.error('no kpi-root-path meta tag set. skipping react-router init');
console.warn('no kpi-root-path meta tag set. skipping react-router init');
}

// Handles rendering a small app in the registration form
Expand All @@ -110,11 +97,9 @@ document.addEventListener('DOMContentLoaded', () => {
'registration-password-app'
);
if (registrationPasswordAppEl) {
render(
<AppContainer>
const root = createRoot(registrationPasswordAppEl);
root.render(
<RegistrationPasswordApp />
</AppContainer>,
registrationPasswordAppEl
);
}
});
5 changes: 2 additions & 3 deletions jsapp/js/router/requireAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, {ReactElement, Suspense, useEffect, useState} from 'react';
import {RouteObject} from 'react-router-dom';
import React, {Suspense, useEffect, useState} from 'react';
import sessionStore from 'js/stores/session';
import LoadingSpinner from '../components/common/loadingSpinner';
import {redirectToLogin} from './routerUtils';

interface Props {
children: RouteObject[] | undefined | ReactElement;
children: React.ReactNode;
redirect?: boolean;
}

Expand Down
Loading
Loading