MobX displaying previous query's results when this query errors - reactjs

When a timeout error occurs while retrieving data from an API to render on a MobX-enabled React page, I am seeing the results from the last successful query displayed in place of the desired data (or empty data). Specifically, the steps are:
Enter a page that requires an item Id to retrieve results from a database and puts query results in state for display
Go back, enter the same page with a new Id, this request times out. Instead of seeing nothing or an error, I am seeing the results from step 1, i.e. the wrong item data.
This is happening site-wide, and I need a fix I can implement everywhere. Below is some code I wrote to fix the problem on one page, but this pattern will need to be copied into every store in our app. I'm not confident it's the right solution, because it works by tracking an item Id and emptying all observables when there's a change - this feels like something MobX should be doing, so I'm afraid my solution is an anti-pattern.
Is there a better solution to this problem than the one I'm presenting below?
class SupplierUtilizationStore {
#observable key = 0; //supplierId
utilizationSearchStore = new SearchStateStore();
#observable utilizationSearchResults = [];
#observable selectedChartType = 'ByMonth';
#observable supplierUsageForChart = {};
#observable utilizationSummaryData = {};
constructor(rootStore, dataLayer) {
this.rootStore = rootStore;
rootStore.supplierStore = this;
this.db = dataLayer;
this.initUtilizationSearchStore();
}
initUtilizationSearchStore() {
this.utilizationSearchStore.searchResultsTotalUnitCost = observable({});
this.utilizationSearchStore.searchResultsTotalCost = observable({});
this.utilizationSearchStore.searchResultsTotalQty = observable({});
this.utilizationSearchStore.supplierId = observable({});
}
//Call this in componentDidMount()
#action
initStore(key) {
if (key !== this.key) {
this.utilizationSearchStore = new SearchStateStore();
this.initUtilizationSearchStore();
this.utilizationSearchResults = [];
this.selectedChartType = 'ByMonth';
this.supplierUsageForChart = {};
this.utilizationSummaryData = {};
this.utilizationSearchStore.supplierId = key;
this.key = key;
}
}
...
}

As I understand "key" is id, which value depends on user action. So you can change it when a page is opened and observe the change to reset all values.
It doesn't seem correct to me to reset/init every single value of another object. Instead create a method for doing that in the exact object(SearchStateStore should have its own init method).
I haven't changed that part, but I am not sure it is also correct architectural behavior to pass the root store to the child store and assign the child store to the parent inside itself(this is not a mater of the question. Just something you can think of)
After all the code looks like this:
class SupplierUtilizationStore {
#observable key = 0; //supplierId
utilizationSearchStore;
#observable utilizationSearchResults;
#observable selectedChartType = 'ByMonth';
#observable supplierUsageForChart;
#observable utilizationSummaryData;
constructor(rootStore: any, dataLayer:any) {
this.rootStore = rootStore;
rootStore.supplierStore = this;
this.db = dataLayer;
this.initStore();
observe(this.key,()=>{ this.initStore() });
}
#action
initStore() {
this.utilizationSearchStore = new SearchStateStore();
this.utilizationSearchStore.init(); // all logic for initialization should be in the concrete store
this.utilizationSearchResults = [];
this.selectedChartType = 'ByMonth';
this.supplierUsageForChart = {};
this.utilizationSummaryData = {};
this.utilizationSearchStore.supplierId = this.key;
}
}
...
}
This way, you don't have to interact with the store and tell it when to init/reset. Just change the observable "key" value from the react component and the store will know what to do.

Related

Firestore navigation for onUpdate and shuffle an array

I have this Firebase structure:
Firebase Structure.
Then I have a function in my Code, which adds a map called "set".
My Structure is looking like this after: New structure.
Now i want an onUpdate Firebase function, which is called after the map "set" is added in any document.
This function should shuffle my "question" array.
I tried something like this:
exports.shuffleSet = functions.firestore
.document('duell/{duell_id}/set/questions')
.onUpdate((change, context) => {
const data = change.after.data();
const previousData = change.before.data();
if (data.name == previousData.name) {
return null;
}
//shuffle code here
});
But Im not sure if .document('duell/{duell_id}/set/questions') is the correct way to navigate to the question array. And at the beginning the "set" is not even existing as explained at the top.
How do I navigate to the question array correctly, that I can pull it & update it shuffled?
You should pass a document path to functions.firestore.document(). You cannot pass a field name, since Firestore Cloud Functions are triggered by documents events.
So you should do as follows:
exports.shuffleSet = functions.firestore
.document('duell/{duell_id}')
.onUpdate((change, context) => {
if (!change.after.data().shuffledSet) {
const data = change.after.data();
const question = data.set.question; // get the value of the question field
const shuffledSet = shuffle(question); // generate the new, suffled set. It’s up to you to write this function
return change.after.ref.update({shuffledSet});
} else {
return null; // Nothing to do, the shuffled field is already calculated
}
});

unable to set record in react usestate array from sqlite

I am using sqlite for local storage in react native but when I tried to fetch record and set it into the useState , it doesn't update the state
here is the code
const SelectQuery=async()=>{
const token = await AsyncStorage.getItem('docToken')
let selectQuery = await ExecuteQuery("SELECT * FROM DoctorConversations where d_phone=?",[token]);
var rows = selectQuery.rows;
var temp = [];
for (let i = 0; i < rows.length; i++) {
temp.push(rows.item(i))
}
return temp;
}
This is how I am calling
SelectQuery().then(res=>res.map(item=>{
setFlatListItem([...flatListItem,item])
}))
I guess that you have:
const [flatListItem, setFlatListItem] = useState([]);
setFlatListItem changes internal state and schedules a rerender. flatListItem gets a new value only on the next render.
You need to use functional updates to modify the current internal state:
setFlatListItem(state => [...state, item])
If you want to replace the whole list, you do not need to map individual items:
SelectQuery().then(res=> setFlatListItem(res))
Note that flatListItem is a bad name in your case since it holds an array. You should probably rename it to just [flatList, setFlatList].

How to use ranges saved to React state - Microsoft Word javascript API?

I am using the Microsoft Word Javascript API. I have used the .search() function to retrieve an array of ranges and then have saved them to state.definitions in my App.js React component state. This part works. When I try to print out the state using console.log(JSON.stringify(this.state.definitions)), I see the ranges that I just saved.
In a separate function, I want to retrieve those ranges and highlight them in a new color. This part does not work. I don't get any errors, but I don't see any highlight changes in the document.
Interestingly, if I try to highlight the ranges BEFORE saving them to state, it works. This makes me think that the ranges that I am retrieving from state are not actually the ranges understood by Word.
Any help would be much appreciated.
var flattenedTerms contains an array of range items that were retrieved from Word a few lines above. This code successfully changes the font
for (var i = 0; i < flattenedTerms.length; i++) {
console.log('flattenedTerms: ', flattenedTerms[i]);
flattenedTerms[i].font.color = 'purple';
flattenedTerms[i].font.highlightColor = 'pink';
flattenedTerms[i].font.bold = true;
}
return context.sync().then(function () {
return resolve(flattenedTerms);
})
})
Now the flattenedTerms array, which contains the range items, has been saved to state.definitions using this.setState. This fails to change the font. All of the console.logs do print.
highlightDefinedTerms = () => {
var self = this;
return Word.run(
function (context) {
var definitions = self.state.definitions;
console.log('Highlighting ', definitions.length, ' terms.');
for (var i = 0; i < definitions.length; i++) {
console.log('Highlighting definition: ', JSON.stringify(definitions[i]));
definitions[i].font.color = 'blue';
definitions[i].font.highlightColor = 'red';
definitions[i].font.bold = true;
}
return context.sync();
}
)
}
You need to pass a first parameter to “Word.run” to specify the object whose context you want to resume.
Word.run(self.state.definitions, function(context) ...)

Copying two-dimensional array from state in React.js

I'm attempting to build a small react app to model Conway's Game of Life. I've set up a two-dimensional array to track the state of each cell in a 10-by-10 grid.
I'm attempting to store this array in the State. At each "tick" of the game, I want to make a copy of the array, evaluate each cell, potentially giving it a new value, and then assign the copy back to state. I'm basing this off of the official React Tutorial where they use this exact approach:
handleClick(i) {
//Make a copy from state
const squares = this.state.squares.slice();
//Make some changes to it
squares[i] = 'X';
//Set state to the new value
this.setState({squares: squares});
}
My initial approach was to use slice() as in the example above. Through debugging, I discovered that this didn't work; somehow state is being changed even though I have used various methods to copy it that shouldn't make changes to it. (I understand that if I say var x = this.state.blah and x = 5 that I have changed state because blah is a reference to it)
Here is my code:
doTick = () => {
console.log("doin a tick");
console.log(this.state.squares);
//None of these approaches works
//Three different copy strategies all fail
//const newSquares = Object.assign({}, this.state.squares);
//const newSquares = [...this.state.squares];
//const newSquares = this.state.squares.slice();
const newSquares = this.state.squares.slice();
const origSquares = [...this.state.squares];
//Iterating over the array
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
newSquares[i][j] = evaluateCell(origSquares[i][j], this.countLiveNeighbors(i, j, origSquares));
//evaluateCell(origSquares[i][j], this.countLiveNeighborsAndLog(i, j, origSquares));
}
}
//this.setState({
//squares: newSquares
//});
}
Even though the setState() call is commented out, just having the assignment of newSquares[i][j] = //... is enough to somehow modify state.
Here's the code where I set up the initial array in the constructor for the Board component:
constructor(props) {
super(props);
var array = new Array(10);
for (var i = 0; i < 10; i++) {
array[i] = new Array(10).fill(false);
}
this.state = {
squares: array
};
console.log(this.state.squares);
}
I took a look here but I'm not having any trouble updating the squares based on clicks (that part of my code works fine). Various SO posts and in-person troubleshooters suggested the three different copy strategies that all produce the same problem. I also took a look here.
I'm very new to React and not very skilled in JS generally, and obviously I don't have a good handle on State. Here are my questions:
How can I make a copy of state/part of state/data in state in such a way that the copy is not a reference to state? I want to be able to change this new data without changing state (until I am ready).
Why are the methods used above NOT working properly? The documentation for slice() assures me that I'm getting a copy and not a reference.
Thanks in advance! I'm very confused.
The spread operator only does a shallow copy of the values. This means if you have any nested values in them, they will be referenced rather than copied. For instance:
const a = { field: { innerField: 'test' } };
const b = { ...a } // b === { field: { innerField: 'test' } } SAME field as a
To copy a nested array you should use deep copy methods, such as Lodash's cloneDeep or Ramda's clone
for example, with Lodash's cloneDeep:
const newSquares = _.cloneDeep(this.state.squares);

Extended event emitter functions across stores are clashing in Flux

I have multiple Flux stores. Now clearly, all of them are extending the same Event emitter singleton. This has led to events across stores clashing with each other (even the most common, emitChange). There seems to be no difference between doing Store1.getID() and Store2.getID(), because stores seem to be one large object extended from every other store. What am I doing wrong?
I have been having this issue for a while now, and its driving me nuts. I am sure this has a simple answer that I am missing. It's one of the reasons I am waiting for relay and GraphQL.
EDIT: What all my stores look like in code.
var Events = require('events'), extend = require('deep_extend'),
EventEmitter = Events.EventEmitter,
CHANGE_EVENT = 'change';
var SomeStore = extend(EventEmitter.prototype, {
someGetter: function(){
return _someVar;
},
dispatchToken: AppDispatcher.register(function(action) {
switch(action.type) {
case 'SOME_ACTION':
_someVar = 'someValue'
break;
default:
return true;
}
SomeStore.emitChange();
return true;
})
});
return SomeStore;
stores seem to be one large object extended from every other store.
There must be some problem with how you extend from EventEmitter otherwise your code should be working fine.
Now that there are a few ways to do the same thing, here is how facebook implemented it in their official examples:
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var TodoStore = assign({}, EventEmitter.prototype, {
...
UPDATE
Now looking at your code
extend(EventEmitter.prototype, {
is actually writing on the prototype itself, hence the errors you got. Instead you should be extending an empty object:
extend({}, EventEmitter.prototype, {

Resources