Create react immutable JS nested state - reactjs

I am trying to combine some of my flat state settings into a settings state object, and at the same time, I want to convert this object to a immutable JS state object.
I get errors though that my key is not defined, although I have set the initial state in the constructor.
Here is what I have so far:
constructor() {
super();
this.state = {
"settings": Immutable.Map()
};
}
Then in componentWillMount (since I get the data from an external API):
componentWillMount() {
/* Some code */
this.setState({
settings: settings.setIn({airlines: Immutable.fromJS(airlines).toList()}),
});
}
The error I get is: Uncaught ReferenceError: settings is not defined
Btw. the airlines element is an array of objects
Can someone help me out? Is my approach directionally right? And do I need to use updateIn afterwards (on update) once the airlines array is first set and I need to update, or is it safer to use setIn?

As the error says, settings is not defined at this position.
Instead, refer to the settings slice of your state:
this.setState({
settings: this.state.settings.setIn({airlines: Immutable.fromJS(airlines).toList()}),
});
Edit:
You are also using ImmutableJS' setIn function incorrectly:
this.setState({
settings: this.state.settings.setIn(['airlines'], Immutable.fromJS(airlines).toList()),
});
See the docs or this SO answer for more details.

settings needs to be referenced like this.state.settings.setIn...

Related

Cannot assign to read only property of Object in TypeScript

can anyone explain to me please why and how this might happen:
I have a typescript app with Zustand state management.
Somewhere during the app I am updating certain elements by extracting them from the state and cloning via simple Object.Assign :
let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);
console.log(elemToUpdate);
if(elemToUpdate) {
if(elemToUpdate.title) elemToUpdate.title[editorLang] = newName;
else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;
updateElement(nodeId,elemToUpdate);
}
Now the interesting part is on my first try the update goes through without fail, but the next object I am trying to update fails with the following message:
Tree.tsx:39 Uncaught TypeError: Cannot assign to read only property 'en' of object '#<Object>'
I can't understand WHY the first one comes through, but the second gets blocked.
(I know HOW to fix it, need to do deep clone, I just want to understand WHY)
Thanks
First, let's start from why some objects in your code are readonly. Based on what you described in the question, you use a Zustand state manager. Such managers traditionally wraps you stored data to readonly objects to prevent it's manual mutation (expecting, you will change the state only via built-in mechanisms), to guarantee data stability. So, if the story?.content?.elementsData[nodeId] is the Zustand state object, it self and all it's nested objects are converted to readonly.
Second, let's define, which objects will be blocked. I see at least two objects here: elemToUpdate: { ..., title: { [lang]: string }} (elemToUpdate and it's title). Both will be converted to readonly.
Third, you use Object.assign({}, ...) which creates a new object (new reference) and clones all properties of the source object. It happens only for first level of properties, no deep clone. So, as the title is a reference to another object, it will be cloned as is and in the new object it still leads to the existing { [lang]: string } object. There are several way to solve that: 1) deep clone as you mentioned; 2) manually clone title property, for instance {..., title: { ... elemToUpdate.title }} or via Object.assign
But I would suggest don't mutate you object this way. Probably, your entire algorithm has some architectural issues in general.
That is expected because in the first case you are not assigning value to the title you are only changing the value of the title property. In the second case, you are reassigning the value of the title property,
it's the read-only value you cant change it. Let's understand with a simple example
Javascript: Only for example not related to problem
const user = {
name: 'John',
}
user.name = "Pete"; // This works
const user = {
name: 'John',
}
user = { name: 'Pete'} // This doesn't work
Typescript:
const user: Readonly<{
a: {
name: string
}
}> = {
a:{ name: 'John',}
}
user.a.name = "Pete"; // This works
user.a = { name: 'John',} // not work
The same is happening there, Typescript does not check deep Readonly prop. check here

DocumentSnapshot.exists returns false when Document exists

I am using React Native and Firebase Firestore. Below is my code where the error persists. It should display true, as the document does exist inside of the firestore, however it keeps returning false. I tried testing it with a value that I manually created on the Firestore website and the .exists property was true. When I create a document with .set however, it returns false. Anyone have an explanation or a solution as to why this could be happening? I have referred to other StackOverflow articles but they were not helpful as this does not seem to be happening to anyone else. Let me know if more info is needed. Thanks in advance.
export default class GuestSession extends Component {
state = {
isLoading: true,
users: [],
code: 0
}
constructor(props) {
super(props);
let displayName = firebase.auth().currentUser.displayName
this.state.code = props.route.params.code
const docRef = firebase.firestore().collection('sessions').doc(this.state.code)
docRef.get().then((docSnapshot) => {
if(docSnapshot.exists) {
console.log("exists")
} else {
console.log("doesn't exist")
}
})
this.state.isLoading = false
}
}
I have figured out my issue. When dynamically creating documents on the go, you want to make sure that you are creating a single document first, instead of a document that includes a collection with another document. For example,
//dynamic document creation of 2 separate documents
firebase.firestore().collection('sessions').doc(this.state.code)
.collection('users').doc('exampleUserDocument').set({username: username})
should instead be written as
const sessionRef = firebase.firestore().collection('sessions')
sessionRef.doc(this.state.code).set({exampleField: value})
//properly sets session document
sessionRef.doc(this.state.code).collection('users').doc('exampleUserDocument').set({username: username})
//properly sets user document
This allows for the document to be properly created, with all property values of a document to be set for both a session document, and for a user document. I feel like when you do the longer version stated first, it skips over properly creating a document and setting proper values in the session collection to save some loading time for the system. This is more of a hypothesis on what happens rather than a factual statement, but that is my assumption. I hope this helps others in the future.

Why does `tslint` report `TS2531: Object is possibly 'null'.` even I checked it before use?

I am using typescript in React project. I have below code in typescript:
if(this.state.deletedItem !== null) {
this.props.deleteItem({refItemIdx: this.state.deletedItem.itemIdx});
}
tslint gives me an error TS2531: Object is possibly 'null'. on the object this.state.deletedItem.itemIdx. It says this.state.deletedItem could be null. But I already checked it in the if condition and why it still reports such error?
Below is the type definition:
interface State {
deletedItem: ItemDiscountTranItem | null;
}
export class MainView extends React.Component<Props, State> {
state = {
deletedItem: null,
};
...
I have tried to update the code as below but still get the error:
if(this.state.deletedItem) {
this.props.voidItemDiscount({refItemIdx: this.state.deletedItem ? this.state.deletedItem.itemIdx : 0});
}
I tried below syntax and still get the same error:
this.props.voidItemDiscount({refItemIdx: this.state.deletedItem && this.state.deletedItem.itemIdx});
interface State {
deletedItem: ItemDiscountTranItem | null;
}
This line indicate that:
+ deletedItem have to be in the State
+ The deletedItem could be null
Since it could be null and have to be there at the same time
When you call {refItemIdx: this.state.deletedItem.itemIdx}
the deletedItem could be null then javascript will throw error, something like this can not read property itemIdx of null, sort of.
typescript help you indicate that problem first with the TS2531: Object is possibly 'null'. to preventing further error.
So you should declare your interface like this:
interface State {
deletedItem?: ItemDiscountTranItem;
}
{refItemIdx: this.state.deletedItem && this.state.deletedItem.itemIdx}
This indicate that:
+ You could or could have the deletedItem in your interface
+ And the deletedItem is type of ItemDiscountTranItem
And you can get the value through checking whether the this.state.deletedItem is exist or not then get the itemIdx out of it.
=====================
Here is just my thought, it's good if you consider using it.
That is, I usually let the variables or something that related to the global context or application state in your case is State
Have a default value and reduce as much null and undefined as possible. You can declare your state like this
deletedItems: ItemDiscountTranItem[],
There is no harm in this declaration, and in fact, it help your code more readable and extendable,
Eg: you can check whether there is anything to delete by simply if(state.deletedItems.length)
and you can even further loop throw the list of deletedItems.forEach
what next? you don't even have to scratch your head about when this should be null is it undefined? or when should I check that?, you can leave all of that behind.
Because it now is simple, you want to delete something? you check if there is something to delete, you want to delete more? you loop. simple enough.
The problem about my code is that I declared my component class as class MainView extends React.Component<Props, State>. The State here only indicate that the component state extends from this class. So the component state defined below has a narrow type of State which is null.
state = {
deletedItem: null,
};
below change fixed my problem:
state: State = {
deletedItem: null,
};

flow type error on type State and type Props

I am using flow, and mostly things are working out, but I am catching this error in my linting on something as trivial as this:
type Props = {
actions: object,
products: object,
};
type State = {
email: string,
name: string
};
class SomeComponent extends Component {
// etc..
}
The linting errors show right after the "type" keyword and the error is:
"Expecting newline or semicolon"
There are 2 possibilities that I see here:
1) object should be capitalized (Object)
2) You are not using eslint-plugin-flowtype
This may seem silly, but I had to go into IntelliJ IDEA -> Preferences -> Languages & Frameworks -> JavaScript and change JavaScript language version to Flow (was previously React JSX). I was already using eslint-plugin-flowtype, and was wondering why it was not working.

How to override a default config option on Ext.form.Action.Submit?

Is there a way to configure ExtJS (through an Ext.override?) so that submitEmptyText config option for Ext.form.Action.Submit is false by default instead of true?
I know it's possible to override methods on classes but i don't know about default config properties, one might think that since config options are mostly public properties that console logging Ext.form.Action.Submit.submitEmptyText would output false but it is undefined.
One way I succeeded is by creating an interceptor for the run method but that basically renders the submitEmptyText config option useless since it can't be set anymore through a config object passed to the constructor.
Ext.form.Action.Submit.prototype.run = Ext.form.Action.Submit.prototype.run.createInterceptor(function() {
this.options.submitEmptyText = false;
});
[Edited a few times, but this works for me]
submitEmptyText is not actually a property of the Ext.form.Action.Submit prototype -- it is just checked in the run function as a property of the "this.options" object -- which itself is defined on the superclass, Ext.form.Action
If you want it to be globally false by default, then just set it on the options property of Ext.form.Action after it is constructed
Ext.form.Action.prototype.constructor = Ext.form.Action.prototype.constructor.createSequence(function() {
Ext.applyIf(this.options, {
submitEmptyText:false
});
});
It's tricky, because the Ext.form.Action sets this.options to an empty object in its constructor, so you have to get access to that options property after the constructor executes, which is why a createSequence works in this case.
If you want to override the Ext.form.Action.submit() for all instances then why not just use an Ext.override?
Ext.override(Ext.form.Action.submit, {
submitEmptyText: false
});
If you want the submitEmptyText from Ext.form.Action.submit to be false, just set it in the config when you create the Ext.form.Action.submit object.
var submit = Ext.form.Action.submit({
submitEmptyText: false
});
It is very bold to use override, however if you are able to manage that changes the right way is:
Ext.override ( Ext.form.Action.submit, {
submitEmptyText: false
});
it will let to reverse this settings to true if needed. I also suggest to read http://edspencer.net/2009/07/extoverride-monkey-patching-ext-js.html
Maybe:
Ext.form.Action.submit.prototype.submitEmptyText = false.
This helps me with:
Ext.Window.prototype.constrainHeader = true;

Resources