Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { get as levenshtein } from "fast-levenshtein";
import _compact from "lodash/compact";
import _isEmpty from "lodash/isEmpty";
import _map from "lodash/map";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { FormattedMessage } from "react-intl";
import BusySpinner from "../../../BusySpinner/BusySpinner";
import WithPagedProjects from "../../../HOCs/WithPagedProjects/WithPagedProjects";
Expand All @@ -14,12 +14,20 @@ import messages from "./Messages";

export function ProjectPickerModal(props) {
const [isSearching, setIsSearching] = useState(false);
const mountedRef = useRef(true);
useEffect(
() => () => {
mountedRef.current = false;
},
[],
);

const executeSearch = (queryCriteria) => {
if (!queryCriteria.query) {
return; // nothing to do
return;
}

if (!mountedRef.current) return;
setIsSearching(true);
props
.searchProjects(
Expand All @@ -31,7 +39,7 @@ export function ProjectPickerModal(props) {
queryCriteria?.page?.resultsPerPage,
)
.finally(() => {
setIsSearching(false);
if (mountedRef.current) setIsSearching(false);
});
};

Expand Down
11 changes: 8 additions & 3 deletions src/components/ChallengeDetail/ChallengeDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export class ChallengeDetail extends Component {
resetSelectedClusters = () => this.setState({ selectedClusters: [] });

componentDidMount() {
this._isMounted = true;
window.scrollTo(0, 0);

const { url, params } = this.props.match;
Expand All @@ -121,6 +122,10 @@ export class ChallengeDetail extends Component {
}
}

componentWillUnmount() {
this._isMounted = false;
}

componentDidUpdate() {
if (!_isObject(this.props.user) && this.state.detailTab === DETAIL_TABS.COMMENTS) {
this.setState({ detailTab: DETAIL_TABS.OVERVIEW });
Expand Down Expand Up @@ -150,6 +155,7 @@ export class ChallengeDetail extends Component {

if (response.ok) {
const body = await response.json();
if (!this._isMounted) return;
if (body?.total_count) {
this.setState({ issue: body.items[0] });
}
Expand Down Expand Up @@ -263,9 +269,8 @@ export class ChallengeDetail extends Component {
disabled
className="mr-bg-black-15 mr-w-full mr-p-2 mr-text-sm"
style={{ height: 500 }}
>
{challenge.overpassQL}
</textarea>
defaultValue={challenge.overpassQL}
/>
);
case DETAIL_TABS.COMMENTS:
return (
Expand Down
8 changes: 7 additions & 1 deletion src/components/Dropdown/Dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ const Dropdown = ({
const [visible, setVisible] = useState(false);
const referenceRef = useRef();
const popperRef = useRef();
const visibleTimeoutRef = useRef();

useEffect(() => {
return () => clearTimeout(visibleTimeoutRef.current);
}, []);

const toggle = useCallback(
(bool) => {
setActive(bool);
toggleVisible();
setTimeout(() => setVisible(bool), 1);
clearTimeout(visibleTimeoutRef.current);
visibleTimeoutRef.current = setTimeout(() => setVisible(bool), 1);
},
[toggleVisible],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,62 +157,61 @@ export const WithChallengeTaskClusters = function (
ignoreLocked,
)
.then((results) => {
if (currentFetchId >= this.state.fetchId) {
const totalCount = results.length;
// If we retrieved 1001 tasks then there might be more tasks and
// they should be clustered. So fetch as clusters
// (unless we are zoomed all the way in already)
if (totalCount > UNCLUSTER_THRESHOLD && (this.props.criteria?.zoom ?? 0) < MAX_ZOOM) {
this.props
.fetchTaskClusters(challengeId, searchCriteria, 25, overrideDisable)
.then((results) => {
const clusters = results.clusters;
if (currentFetchId >= this.state.fetchId) {
const taskCount = _sum(_map(clusters, (c) => c.numberOfPoints));
this.setState({
clusters,
loading: false,
taskCount: taskCount,
showAsClusters: true,
});
}
if (!this._isMounted || currentFetchId < this.state.fetchId) return;
const totalCount = results.length;
// If we retrieved 1001 tasks then there might be more tasks and
// they should be clustered. So fetch as clusters
// (unless we are zoomed all the way in already)
if (totalCount > UNCLUSTER_THRESHOLD && (this.props.criteria?.zoom ?? 0) < MAX_ZOOM) {
this.props
.fetchTaskClusters(challengeId, searchCriteria, 25, overrideDisable)
.then((results) => {
if (!this._isMounted || currentFetchId < this.state.fetchId) return;
const clusters = results.clusters;
const taskCount = _sum(_map(clusters, (c) => c.numberOfPoints));
this.setState({
clusters,
loading: false,
taskCount: taskCount,
showAsClusters: true,
});
} else {
this.setState({
clusters: results,
loading: false,
taskCount: totalCount,
});
}
} else {
this.setState({
clusters: results,
loading: false,
taskCount: totalCount,
});
}
})
.catch((error) => {
console.log(error);
this.setState({ clusters: {}, loading: false, taskCount: 0 });
if (this._isMounted) this.setState({ clusters: {}, loading: false, taskCount: 0 });
});
} else {
this.props
.fetchTaskClusters(challengeId, searchCriteria, 25, overrideDisable)
.then((results) => {
if (!this._isMounted || currentFetchId < this.state.fetchId) return;
const clusters = results.clusters;
if (currentFetchId >= this.state.fetchId) {
const taskCount = _sum(_map(clusters, (c) => c.numberOfPoints));
const taskCount = _sum(_map(clusters, (c) => c.numberOfPoints));
this.setState({
clusters,
loading: false,
taskCount: taskCount,
showAsClusters: true,
});
})
.catch((error) => {
console.log(error);
if (this._isMounted) {
this.setState({
clusters,
clusters: {},
loading: false,
taskCount: taskCount,
taskCount: 0,
showAsClusters: true,
});
}
})
.catch((error) => {
console.log(error);
this.setState({
clusters: {},
loading: false,
taskCount: 0,
showAsClusters: true,
});
});
}
}
Expand Down Expand Up @@ -278,6 +277,7 @@ export const WithChallengeTaskClusters = function (
};

updateTaskInClusters = async (updatedTasks) => {
if (!this._isMounted) return;
if (!Array.isArray(updatedTasks)) {
updatedTasks = [updatedTasks];
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/HOCs/WithFeatured/WithFeatured.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ const WithFeatured = (WrappedComponent, options = {}) => {
};

componentDidMount() {
this._isMounted = true;
// Fetch featured challenges
if (!options.excludeChallenges) {
this.props.fetchFeaturedChallenges().then((normalizedResults) => {
if (!this._isMounted) return;
if (normalizedResults && !_isEmpty(normalizedResults.entities)) {
this.setState({
featuredChallenges: _filter(
Expand All @@ -39,6 +41,7 @@ const WithFeatured = (WrappedComponent, options = {}) => {
// Fetch featured projects
if (!options.excludeProjects) {
this.props.fetchFeaturedProjects().then((normalizedResults) => {
if (!this._isMounted) return;
if (normalizedResults && !_isEmpty(normalizedResults.entities)) {
this.setState({
featuredProjects: _values(normalizedResults.entities.projects),
Expand All @@ -48,6 +51,10 @@ const WithFeatured = (WrappedComponent, options = {}) => {
}
}

componentWillUnmount() {
this._isMounted = false;
}

render() {
return (
<WrappedComponent
Expand Down
13 changes: 7 additions & 6 deletions src/components/HOCs/WithLeaderboard/WithLeaderboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,11 @@ const WithLeaderboard = function (WrappedComponent, initialMonthsPast = 1, initi
startDate,
endDate,
).then((leaderboard) => {
if (!this._isMounted) return;
if (currentFetch >= this.state.fetchId) {
this.setState({ leaderboard });

const userId = this.props.user?.id;
// The reason for using _get is that the structure of the props may vary
// depending on where this component is used, and accessing the user's score
// directly through `this.props.user.score` may result in runtime errors if
// the `user` object or the `score` property is not available in certain contexts.
// By using `_get`, we safely handle cases where the expected property may be missing
// or nested within a deeper structure.
const userScore = this.props.user?.score;
if (userScore && userId && !options.ignoreUser && userType !== USER_TYPE_REVIEWER) {
this.props
Expand All @@ -135,6 +130,7 @@ const WithLeaderboard = function (WrappedComponent, initialMonthsPast = 1, initi
endDate,
)
.then((userLeaderboard) => {
if (!this._isMounted) return;
this.mergeInUserLeaderboard(userLeaderboard);
this.setState({ leaderboardLoading: false });
});
Expand Down Expand Up @@ -228,6 +224,7 @@ const WithLeaderboard = function (WrappedComponent, initialMonthsPast = 1, initi
};

componentDidMount() {
this._isMounted = true;
if (!initialOptions.isWidget) {
this.updateLeaderboard(
this.monthsPast(),
Expand All @@ -239,6 +236,10 @@ const WithLeaderboard = function (WrappedComponent, initialMonthsPast = 1, initi
}
}

componentWillUnmount() {
this._isMounted = false;
}

componentDidUpdate(prevProps) {
// A change to state will also fetch leaderboard data, so we only need to
// worry about fetching if we're controlled and props change.
Expand Down
9 changes: 8 additions & 1 deletion src/components/HOCs/WithNearbyTasks/WithNearbyTasks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const WithNearbyTasks = function (WrappedComponent) {
: MAX_NEARBY_TASK_LIMIT,
);

if (!this._isMounted) return;
const tasksLength = nearbyTasks.tasks.length;
this.setState({
nearbyTasks: {
Expand All @@ -100,7 +101,7 @@ export const WithNearbyTasks = function (WrappedComponent) {
});
} catch (error) {
console.error("Error fetching nearby tasks:", error);
this.setState({ loading: false });
if (this._isMounted) this.setState({ loading: false });
}
}
};
Expand Down Expand Up @@ -141,6 +142,7 @@ export const WithNearbyTasks = function (WrappedComponent) {
boundingBox,
MAX_NEARBY_TASK_LIMIT,
);
if (!this._isMounted) return;
const tasksLength = nearbyTasks.tasks?.length;

if (tasksLength > 0) {
Expand All @@ -161,9 +163,14 @@ export const WithNearbyTasks = function (WrappedComponent) {
};

componentDidMount() {
this._isMounted = true;
this.updateNearbyTasks();
}

componentWillUnmount() {
this._isMounted = false;
}

componentDidUpdate(prevProps) {
if (this.props.task && this.props.task?.id !== prevProps.task?.id) {
this.setState({
Expand Down
12 changes: 11 additions & 1 deletion src/components/HOCs/WithProgress/WithProgress.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ const WithProgress = function (WrappedComponent, operationName) {
stepsCompleted: 0,
};

componentDidMount() {
this._isMounted = true;
}

componentWillUnmount() {
this._isMounted = false;
}

updateProgress = (inProgress, stepsCompleted) => {
this.setState({ inProgress, stepsCompleted });
if (this._isMounted) {
this.setState({ inProgress, stepsCompleted });
}
};

render() {
Expand Down
7 changes: 6 additions & 1 deletion src/components/HOCs/WithSystemNotices/WithSystemNotices.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ export const WithSystemNotices = function (WrappedComponent) {
};

async componentDidMount() {
this._isMounted = true;
if (!this.state.systemNotices) {
const activeNotices = await fetchActiveSystemNotices();

if (!this._isMounted) return;
this.setState({ systemNotices: activeNotices });
}
}

componentWillUnmount() {
this._isMounted = false;
}

/**
* Retrieves all acknowledged notices from the user's app settings
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ export class ActiveTaskControls extends Component {
}
}
} catch (error) {
this.setState({ completingTask: false });
if (this._isMounted) this.setState({ completingTask: false });
throw error;
} finally {
this.setState({ completingTask: false });
if (this._isMounted) this.setState({ completingTask: false });
}
};

Expand Down Expand Up @@ -372,7 +372,12 @@ export class ActiveTaskControls extends Component {
}
}

componentDidMount() {
this._isMounted = true;
}

componentWillUnmount() {
this._isMounted = false;
if (!_isEmpty(this.props.activeKeyboardShortcuts?.[hiddenShortcutGroup])) {
for (const shortcut of hiddenShortcuts) {
this.props.deactivateKeyboardShortcut(
Expand Down
Loading