fix(web): resolve issues board blank rendering due to MobX strict mode in IssueRootStore
All checks were successful
Deploy Plane Project Tracker / Deploy to cisystem (push) Successful in 5m26s

This commit is contained in:
vickytechkey 2026-11-06 19:42:40 +05:30
parent a4687eaf39
commit d27b7fdbea
7 changed files with 115 additions and 69 deletions

View file

@ -6,6 +6,7 @@
import type { MutableRefObject } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type {
GroupByColumnTypes,
IGroupByColumn,
@ -106,11 +107,13 @@ export const KanBan = observer(function KanBan(props: IKanBan) {
const { getIsWorkflowWorkItemCreationDisabled } = useWorkFlowFDragNDrop(group_by, sub_group_by);
const { projectId } = useParams();
const list = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
isEpic: isEpic,
projectId: projectId?.toString(),
});
if (!list) return null;
@ -165,7 +168,7 @@ export const KanBan = observer(function KanBan(props: IKanBan) {
groupByVisibilityToggle.showIssues ? `w-[350px]` : ``
} `}
>
{sub_group_by === null && (
{!sub_group_by && (
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-surface-2 py-1">
<HeaderGroupByCard
sub_group_by={sub_group_by}

View file

@ -135,7 +135,7 @@ export const HeaderGroupByCard = observer(function HeaderGroupByCard(props: IHea
</div>
</div>
{sub_group_by === null && (
{!sub_group_by && (
<button
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm bg-layer-transparent transition-all hover:bg-layer-transparent-hover"
onClick={() => handleCollapsedGroups("group_by", column_id)}

View file

@ -6,6 +6,7 @@
import type { MutableRefObject } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import type {
GroupByColumnTypes,
@ -290,17 +291,20 @@ export const KanBanSwimLanes = observer(function KanBanSwimLanes(props: IKanBanS
// store hooks
const storeType = useIssueStoreType();
// derived values
const { projectId } = useParams();
const groupByList = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
isEpic: isEpic,
projectId: projectId?.toString(),
});
const subGroupByList = getGroupByColumns({
groupBy: sub_group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
isEpic: isEpic,
projectId: projectId?.toString(),
});
if (!groupByList || !subGroupByList) return null;

View file

@ -5,6 +5,7 @@
*/
import { useEffect, useRef } from "react";
import { useParams } from "next/navigation";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
import { observer } from "mobx-react";
@ -87,11 +88,13 @@ export const List = observer(function List(props: IList) {
const containerRef = useRef<HTMLDivElement | null>(null);
const { projectId } = useParams();
const groups = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
isEpic: isEpic,
projectId: projectId?.toString(),
});
// Enable Auto Scroll for Main Kanban

View file

@ -240,9 +240,15 @@ const getModuleColumns = (): IGroupByColumn[] | undefined => {
};
const getStateColumns = ({ projectId }: TGetColumns): IGroupByColumn[] | undefined => {
const { getProjectStates, projectStates } = store.state;
const _states = projectId ? getProjectStates(projectId) : projectStates;
if (!_states) return;
const { getProjectStates, projectStates, stateMap } = store.state;
let _states = projectId ? getProjectStates(projectId) : projectStates;
if (!_states || _states.length === 0) {
const targetProjectId = projectId || store.router.projectId;
if (targetProjectId && stateMap) {
_states = Object.values(stateMap).filter((state) => state.project_id === targetProjectId);
}
}
if (!_states || _states.length === 0) return;
// map project states to group by columns
return _states.map((state) => ({
id: state.id,

View file

@ -4,8 +4,7 @@
* See the LICENSE file for details.
*/
import { isEmpty } from "lodash-es";
import { autorun, makeObservable, observable, runInAction } from "mobx";
import { makeObservable, computed } from "mobx";
// types
import type { ICycle, IIssueLabel, IModule, IProject, IState, IUserLite, TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types";
@ -109,25 +108,6 @@ export interface IIssueRootStore {
}
export class IssueRootStore implements IIssueRootStore {
currentUserId: string | undefined = undefined;
workspaceSlug: string | undefined = undefined;
teamspaceId: string | undefined = undefined;
projectId: string | undefined = undefined;
cycleId: string | undefined = undefined;
moduleId: string | undefined = undefined;
viewId: string | undefined = undefined;
globalViewId: string | undefined = undefined;
userId: string | undefined = undefined;
stateMap: Record<string, IState> | undefined = undefined;
stateDetails: IState[] | undefined = undefined;
workspaceStateDetails: IState[] | undefined = undefined;
labelMap: Record<string, IIssueLabel> | undefined = undefined;
workSpaceMemberRolesMap: Record<string, IWorkspaceMembership> | undefined = undefined;
memberMap: Record<string, IUserLite> | undefined = undefined;
projectMap: Record<string, IProject> | undefined = undefined;
moduleMap: Record<string, IModule> | undefined = undefined;
cycleMap: Record<string, ICycle> | undefined = undefined;
rootStore: RootStore;
serviceType: TIssueServiceType;
@ -175,55 +155,103 @@ export class IssueRootStore implements IIssueRootStore {
projectEpicsFilter: IProjectIssuesFilter;
projectEpics: IProjectIssues;
get currentUserId(): string | undefined {
return this.rootStore?.user?.data?.id;
}
get workspaceSlug(): string | undefined {
return this.rootStore?.router?.workspaceSlug;
}
get teamspaceId(): string | undefined {
return this.rootStore?.router?.teamspaceId;
}
get projectId(): string | undefined {
return this.rootStore?.router?.projectId;
}
get cycleId(): string | undefined {
return this.rootStore?.router?.cycleId;
}
get moduleId(): string | undefined {
return this.rootStore?.router?.moduleId;
}
get viewId(): string | undefined {
return this.rootStore?.router?.viewId;
}
get globalViewId(): string | undefined {
return this.rootStore?.router?.globalViewId;
}
get userId(): string | undefined {
return this.rootStore?.router?.userId;
}
get stateMap(): Record<string, IState> | undefined {
return this.rootStore?.state?.stateMap;
}
get stateDetails(): IState[] | undefined {
return this.rootStore?.state?.projectStates;
}
get workspaceStateDetails(): IState[] | undefined {
return this.rootStore?.state?.workspaceStates;
}
get labelMap(): Record<string, IIssueLabel> | undefined {
return this.rootStore?.label?.labelMap;
}
get workSpaceMemberRolesMap(): Record<string, IWorkspaceMembership> | undefined {
return this.rootStore?.memberRoot?.workspace?.memberMap || undefined;
}
get memberMap(): Record<string, IUserLite> | undefined {
return this.rootStore?.memberRoot?.memberMap || undefined;
}
get projectMap(): Record<string, IProject> | undefined {
return this.rootStore?.projectRoot?.project?.projectMap;
}
get moduleMap(): Record<string, IModule> | undefined {
return this.rootStore?.module?.moduleMap;
}
get cycleMap(): Record<string, ICycle> | undefined {
return this.rootStore?.cycle?.cycleMap;
}
constructor(rootStore: RootStore, serviceType: TIssueServiceType = EIssueServiceType.ISSUES) {
makeObservable(this, {
workspaceSlug: observable.ref,
teamspaceId: observable.ref,
projectId: observable.ref,
cycleId: observable.ref,
moduleId: observable.ref,
viewId: observable.ref,
userId: observable.ref,
globalViewId: observable.ref,
stateMap: observable,
stateDetails: observable,
workspaceStateDetails: observable,
labelMap: observable,
memberMap: observable,
workSpaceMemberRolesMap: observable,
projectMap: observable,
moduleMap: observable,
cycleMap: observable,
currentUserId: computed,
workspaceSlug: computed,
teamspaceId: computed,
projectId: computed,
cycleId: computed,
moduleId: computed,
viewId: computed,
userId: computed,
globalViewId: computed,
stateMap: computed,
stateDetails: computed,
workspaceStateDetails: computed,
labelMap: computed,
memberMap: computed,
workSpaceMemberRolesMap: computed,
projectMap: computed,
moduleMap: computed,
cycleMap: computed,
});
this.serviceType = serviceType;
this.rootStore = rootStore;
autorun(() => {
runInAction(() => {
if (rootStore?.user?.data?.id) this.currentUserId = rootStore?.user?.data?.id;
if (this.workspaceSlug !== rootStore.router.workspaceSlug) this.workspaceSlug = rootStore.router.workspaceSlug;
if (this.teamspaceId !== rootStore.router.teamspaceId) this.teamspaceId = rootStore.router.teamspaceId;
if (this.projectId !== rootStore.router.projectId) this.projectId = rootStore.router.projectId;
if (this.cycleId !== rootStore.router.cycleId) this.cycleId = rootStore.router.cycleId;
if (this.moduleId !== rootStore.router.moduleId) this.moduleId = rootStore.router.moduleId;
if (this.viewId !== rootStore.router.viewId) this.viewId = rootStore.router.viewId;
if (this.globalViewId !== rootStore.router.globalViewId) this.globalViewId = rootStore.router.globalViewId;
if (this.userId !== rootStore.router.userId) this.userId = rootStore.router.userId;
if (!isEmpty(rootStore?.state?.stateMap)) this.stateMap = rootStore?.state?.stateMap;
if (!isEmpty(rootStore?.state?.projectStates)) this.stateDetails = rootStore?.state?.projectStates;
if (!isEmpty(rootStore?.state?.workspaceStates)) this.workspaceStateDetails = rootStore?.state?.workspaceStates;
if (!isEmpty(rootStore?.label?.labelMap)) this.labelMap = rootStore?.label?.labelMap;
if (!isEmpty(rootStore?.memberRoot?.workspace?.workspaceMemberMap))
this.workSpaceMemberRolesMap = rootStore?.memberRoot?.workspace?.memberMap || undefined;
if (!isEmpty(rootStore?.memberRoot?.memberMap)) this.memberMap = rootStore?.memberRoot?.memberMap || undefined;
if (!isEmpty(rootStore?.projectRoot?.project?.projectMap))
this.projectMap = rootStore?.projectRoot?.project?.projectMap;
if (!isEmpty(rootStore?.module?.moduleMap)) this.moduleMap = rootStore?.module?.moduleMap;
if (!isEmpty(rootStore?.cycle?.cycleMap)) this.cycleMap = rootStore?.cycle?.cycleMap;
});
});
this.issues = new IssueStore();
this.issueDetail = new IssueDetail(this, EIssueServiceType.ISSUES);

View file

@ -180,7 +180,9 @@ export class BaseWorkspaceRootStore implements IWorkspaceRootStore {
* fetch user workspaces from API
*/
fetchWorkspaces = async () => {
this.loader = true;
runInAction(() => {
this.loader = true;
});
try {
const workspaceResponse = await this.workspaceService.userWorkspaces();
runInAction(() => {