I'm writing an app with react framework, mobx state manager and using typescript. The application includes a left menu and a workspace in the form of tabs. As in the picture below. Visual representation of the application Each tab contains its own content. As in any browser.Tabs can be created by clicking on the menu button, as well as by clicking on the button from the content of the tab itself. For example, we open the first tab. Its content is a table that contains information about some list of elements. We can edit each element by clicking on the "Edit" button, this should open a new tab. Table with editable data Thus we get a hierarchical structure. Also, the same tabs should manage one global storage. What MobX data structure would you suggest to use for such an application?
At the moment, I came up with the following data structure Based on the MVVM pattern. There is a global tab manager which has the following methods: add tab, remove tab and set current open tab.
class TabManager {
currentTab: BaseTabViewModel | null = null
allTabs: BaseTabViewModel[] = []
constructor() {
makeAutoObservable(this, {}, { autoBind: true })
}
addTab<T extends BaseTabViewModel>(Tab: T) {
this.allTabs.push(Tab)
this.setCurrentTab(Tab)
}
deleteTab<T extends BaseTabViewModel>(Tab: T) {
this.allTabs.splice(this.allTabs.indexOf(Tab), 1)
}
setCurrentTab<T extends BaseTabViewModel>(Tab: T) {
this.currentTab = Tab
}
}
export default TabManager
Each tab is inherited from the base tab and is rendered by the view model.The base model looks like this.
class BaseTabViewModel {
id: string | null = null
title = ''
constructor(id: string, title: string) {
this.id = id
this.title = title
}
}
export default BaseTabViewModel
The tab itself might look like this::
class SomeTabViewModel extends BaseTabViewModel {
constructor(
id: string,
title: string,
private readonly _someStore: SomeStore,
private readonly _someService: typeof SomeService
) {
super(id, title)
this.getListItems()
}
get someStore() {
return this._someStore
}
getListItems = () => {
this._someService
.getListItems()
.then((someItemsResponse) => {
runInAction(() => {
this.someStore.setListItems(someItemsResponse)
})
})
.catch()
}
addSomeItem = (data: NewSomeItemRequest) => {
this._someService
.addSomeItem(data)
.then((someItemResponse) => {
runInAction(() => {
this.someStore.addSomeItem(someItemResponse)
})
})
.catch()
}
//...other methods
}
export default SomeTabViewModel
Services simply interact with the server through requests. The store simply stores business entities and operations on them. The base store from which the rest are inherited:
export default class BaseListStore<TListItem extends Identifiable> {
protected items: Array<TListItem> = []
constructor() {
makeObservable<BaseListStore<TListItem>>(this, {
list: computed,
setItems: action,
addItem: action,
removeItem: action
})
}
get list(): Array<TListItem> {
return this.items
}
setItems(items: Array<TListItem>) {
this.items = items
}
addItem(item: TListItem) {
this.items.push(item)
}
removeItem(item: TListItem) {
this.items.splice(this.items.indexOf(item), 1)
}
}
export type BaseListStoreType = BaseListStore<Identifiable>
Tabs are rendered according to the following principle.
const MainTabs: FC = observer(() => {
const { allTabs, currentTab, deleteTab } = useTabManager()
return (
<div className={styles.tabsContainer}>
{allTabs.length !== 0 && (
<Tabs>
<TabList>
{allTabs.map((tab) => (
<Tab key={tab.id}>
<div className={styles.title}>{tab.title}</div>
<div className={styles.close} onClick={() => deleteTab(tab)}>
<FontAwesomeIcon icon={faTimes} />
</div>
</Tab>
))}
</TabList>
{allTabs.map((tab) => (
<TabPanel key={tab.id}>
{tab instanceof SomeTabViewModel && <SomeTabScreen content={tab} />}
{tab instanceof SecondSomeTabViewModel && (
<SecondSomeTabScreen content={tab} />
)}
</TabPanel>
))}
</Tabs>
)}
</div>
)
})
export default MainTabs
Complexity arises in those situations when we create a tab that depends on the previous one. Since the tabs are stored independently of each other and neither of them knows about the existence of the other, there is some difficulty. Let's imagine that we have a tab with a table and we clicked on the "Edit" button from the example above. How to transfer a specific item there? I didn’t come up with anything better than creating a view model that stores information about a specific entity. Example:
class RedactorTabViewModel extends BaseTabViewModel {
constructor(
id: string,
title: string,
private readonly _specificItem: Item,
private readonly _notificationService: typeof SomeService
) {
super(id, title)
}
get item() {
return this._specificItem
}
getFile = () => {
if (!this.item.fileId) return
this._someService.getFile(this.item.fileId).then((data: File) => {
runInAction(() => {
this.item.setFile(data)
})
})
}
}
export default RedactorTabViewModel
But this approach, in my opinion, is not the most correct. Because we are modifying an entity that is in a particular store regardless of that store. What storage structure can you suggest or my approach is optimal?
Via Active questions tagged javascript - Stack Overflow https://ift.tt/FTH1t2w
Comments
Post a Comment