I am new to React, Mobx stack. I bumped into a problem where my Store does not reflect its value immediately after the first call. when I make the second call, it returns the first value. I am unable to understand why it happens since I have a long way to go in React, Mobx stack.
My Store Class
export default class ServiceTemplateStore {
loadingInitial = false;
serviceTemplateDetail: any | undefined;
constructor() {
makeAutoObservable(this)
}
setLoadingInitial = (state: boolean) => {
this.loadingInitial = state;
}
get getServiceTemplateDetail() {
return this.serviceTemplateDetail;
}
loadServiceTemplateDetail = async (id: string) => {
this.loadingInitial = true;
try {
const serviceTemplateDetail = await agent.serviceTemplateDetail.details(id);
runInAction(() => {
console.log("Service loaded");
this.serviceTemplateDetail = serviceTemplateDetail;
console.log(this.serviceTemplateDetail); //Here I can see it immediately outputs the value.
this.setLoadingInitial(false);
});
} catch (error) {
console.log(error);
this.setLoadingInitial(false);
}
}
}
Child component
Here is where I am using the Store, when I use the useEffect
Hook it actually populates the data, but when I run in inside onClick
function it does not populate the value.
What my understanding is that Axios GET
call executes as expected, also it returns the value back and assigns it to serviceTemplateDetail
variable. but when I am accessing the value via getServiceTemplateDetail
method it returns null for the first time, and when I click the Import Button next time it returns the old value which was returned from GET
Request
import DataGrid, { Selection } from "devextreme-react/data-grid";
import Popup, { Position, ToolbarItem } from "devextreme-react/popup";
import { Column } from "devextreme-react/tree-list";
import { observer } from "mobx-react-lite";
import React, { Component, useCallback, useEffect, useState } from 'react'
import { useStore } from "../store/Store";
export default observer(function ImportServiceTemplate(props: importServiceProp) {
const [ serviceTemplateIds, setServiceTemplateIds] = useState([]);
const { serviceTemplateStore } = useStore();
const { serviceTemplateList, loadServiceTemplateList, loadServiceTemplateDetail, getServiceTemplateDetail, serviceTemplateDetail } = serviceTemplateStore;
useEffect(() => {
if (serviceTemplateList?.length === 0 || serviceTemplateList === undefined) loadServiceTemplateList();
}, [loadServiceTemplateList])
const closeButtonOption = {
icon: 'close',
text: 'Close',
onClick: ()=>{
props.onCloseImportModel();
}
};
//Added a useEffect to test..
//UseEffect returns the value but not my Import function below
useEffect(() => {
console.log("use effect");
console.log(JSON.stringify(getServiceTemplateDetail));
}, [getServiceTemplateDetail])
//Actual function that needs to work.
const importButtonOption = {
icon: 'download',
text: 'Import',
onClick: () => {
if(serviceTemplateIds){
loadServiceTemplateDetail(String(serviceTemplateIds))
.then(()=>{
console.log("Callback");
console.log(JSON.stringify(serviceTemplateDetail)); // Getting undefined as output.
props.importingServiceTemplate(getServiceTemplateDetail); //Passing the imported value to parent component
});
}
}
};
const onSelectionChanged = (e: any) => {
console.log("Processing", e.selectedRowKeys);
setServiceTemplateIds(e.selectedRowKeys);
};
return (
<>
<Popup
visible={props.isImportVisible}
dragEnabled={false}
closeOnOutsideClick={false}
showCloseButton={false}
showTitle={true}
title="Service Template"
width={800}
height={280}>
<Position
at="center"
my="center"
/>
<DataGrid
id="serviceTemplateGrid"
key="ServiceTemplateId"
keyExpr="ServiceTemplateId"
focusedRowEnabled={true}
onSelectionChanged={onSelectionChanged}
selectedRowKeys={serviceTemplateIds}
dataSource={serviceTemplateList}
showRowLines={true}
showBorders={true}>
<Selection mode="multiple" showCheckBoxesMode="onClick" />
<Column dataField="Name" caption="Name" />
<Column dataField="CustomerName" caption="Customer Name" />
<Column dataField="BaseCurrencyCode" caption="Currency" />
<Column dataField="Description" caption="Description" />
</DataGrid>
<ToolbarItem
widget="dxButton"
toolbar="bottom"
location="before"
options={closeButtonOption}
/>
<ToolbarItem
widget="dxButton"
toolbar="bottom"
location="after"
options={importButtonOption}
/>
<div>{JSON.stringify(getServiceTemplateDetail)}</div>
</Popup>
</>
)
})
interface importServiceProp{
isImportVisible: boolean;
onCloseImportModel: Function
importingServiceTemplate: any
}
It is not React or MobX problem, just a regular javascript closure.
When you create importButtonOption
function it remembers
all the variables around it, and serviceTemplateDetail
is equals undefined at first time. So after you invoke loadServiceTemplateDetail
serviceTemplateDetail
is changed in the store, but inside importButtonOption
function it is still an old value which was remembered
when the function was created.
Hope it makes sense. Basically you just need to read about closures and how they work.
There is a little guide in React docs
What you can do for example is remove destructuring of values and dereference them as you need, like that:
loadServiceTemplateDetail(String(serviceTemplateIds))
.then(()=>{
console.log("Callback");
console.log(JSON.stringify(serviceTemplateStore .serviceTemplateDetail)); // reference it from the store to get actual value
props.importingServiceTemplate(getServiceTemplateDetail); //Passing the imported value to parent component
});