Skip to main content

How to properly organize mobx data structure in react app with complex hierarchy

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

Popular posts from this blog

How to split a rinex file if I need 24 hours data

Trying to divide rinex file using the command gfzrnx but getting this error. While doing that getting this error msg 'gfzrnx' is not recognized as an internal or external command Trying to split rinex file using the command gfzrnx. also install'gfzrnx'. my doubt is I need to run this program in 'gfzrnx' or in 'cmdprompt'. I am expecting a rinex file with 24 hrs or 1 day data.I Have 48 hrs data in RINEX format. Please help me to solve this issue. source https://stackoverflow.com/questions/75385367/how-to-split-a-rinex-file-if-i-need-24-hours-data

ValueError: X has 10 features, but LinearRegression is expecting 1 features as input

So, I am trying to predict the model but its throwing error like it has 10 features but it expacts only 1. So I am confused can anyone help me with it? more importantly its not working for me when my friend runs it. It works perfectly fine dose anyone know the reason about it? cv = KFold(n_splits = 10) all_loss = [] for i in range(9): # 1st for loop over polynomial orders poly_order = i X_train = make_polynomial(x, poly_order) loss_at_order = [] # initiate a set to collect loss for CV for train_index, test_index in cv.split(X_train): print('TRAIN:', train_index, 'TEST:', test_index) X_train_cv, X_test_cv = X_train[train_index], X_test[test_index] t_train_cv, t_test_cv = t[train_index], t[test_index] reg.fit(X_train_cv, t_train_cv) loss_at_order.append(np.mean((t_test_cv - reg.predict(X_test_cv))**2)) # collect loss at fold all_loss.append(np.mean(loss_at_order)) # collect loss at order plt.plot(np.log(al...

Sorting large arrays of big numeric stings

I was solving bigSorting() problem from hackerrank: Consider an array of numeric strings where each string is a positive number with anywhere from to digits. Sort the array's elements in non-decreasing, or ascending order of their integer values and return the sorted array. I know it works as follows: def bigSorting(unsorted): return sorted(unsorted, key=int) But I didnt guess this approach earlier. Initially I tried below: def bigSorting(unsorted): int_unsorted = [int(i) for i in unsorted] int_sorted = sorted(int_unsorted) return [str(i) for i in int_sorted] However, for some of the test cases, it was showing time limit exceeded. Why is it so? PS: I dont know exactly what those test cases were as hacker rank does not reveal all test cases. source https://stackoverflow.com/questions/73007397/sorting-large-arrays-of-big-numeric-stings