Getting primitive values from mobx observable in reactjs - javascript

I have a observable store that is being based via props to components that need it.
Console logging from components does show store as expected, but only if I stick to the whole store. Once I start chaining into it I get undefined.
Base store
export let TutorStore = observable({
Tutor: {},
Queue: [],
QLength: null
});
Component logging
checkBtn = () => {
console.log(this.props.tutorStore);
};
Result TutorStore in Console as expected
All the correct tutorStore objects are there, with the right data, as expected.
But if I try to chain into a object there are no values attached to it, not behaving as expected.
checkBtn = () => {
console.log(this.props.tutorStore.Tutor);
};
Result TutorStore.Tutor no values
I've tried messing around with mobx's toJS method, but it seems unreliable at best.
Considering just assigning the appropriate object to components state, but that defeats the purpose of having a store.
The data is there, so how do I access it?

Related

Vue child component not displaying dynamic data on first page load

Given the code below, my child component alerts trigger before any of the code in the Parent mounted function.
As a result it appears the child has already finished initialization before the data is ready and therefor won't display the data until it is reloaded.
The data itself comes back fine from the API as the raw JSON displays inside the v-card in the layout.
My question is how can I make sure the data requested in the Parent is ready BEFORE the child component loads? Anything I have found focuses on static data passed in using props, but it seems this completely fails when the data must be fetched first.
Inside the mounted() of the Parent I have this code which is retrieves the data.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
console.log('DATA ...') // fires after the log in Notes component
this.checkAttendanceForPreviousTwoWeeks().then(()=>{
this.getCurrentParticipants().then((results) => {
this.currentP = results
this.notesArr = this.notes // see getter below
})
The getter that retrieves the data in the parent
get notes() {
const newNotes = eventsModule.getNotes
return newNotes
}
My component in the parent template:
<v-card light elevation="">
{{ notes }} // Raw JSON displays correctly here
// Passing the dynamic data to the component via prop
<Notes v-if="notes.length" :notesArr="notes"/>
</v-card>
The Child component:
...
// Pickingn up prop passed to child
#Prop({ type: Array, required: true })
notesArr!: object[]
constructor()
{
super();
alert(`Notes : ${this.notesArr}`) // nothing here
this.getNotes(this.notesArr)
}
async getNotes(eventNotes){
// THIS ALERT FIRES BEFORE PROMISES IN PARENT ARE COMPLETED
alert(`Notes.getNotes CALL.. ${eventNotes}`) // eventNotes = undefined
this.eventChanges = await eventNotes.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
...
I am relatively new to Vue so forgive me if I am overlooking something basic. I have been trying to fix it for a couple of days now and can't figure it out so any help is much appreciated!
If you are new to Vue, I really recommend reading the entire documentation of it and the tools you are using - vue-class-component (which is Vue plugin adding API for declaring Vue components as classes)
Caveats of Class Component - Always use lifecycle hooks instead of constructor
So instead of using constructor() you should move your code to created() lifecycle hook
This should be enough to fix your code in this case BUT only because the usage of the Notes component is guarded by v-if="notes.length" in the Parent - the component will get created only after notes is not empty array
This is not enough in many cases!
created() lifecycle hook (and data() function/hook) is executed only once for each component. The code inside is one time initialization. So when/if parent component changes the content of notesArr prop (sometimes in the future), the eventChanges will not get updated. Even if you know that parent will never update the prop, note that for performance reasons Vue tend to reuse existing component instances when possible when rendering lists with v-for or switching between components of the same type with v-if/v-else - instead of destroying existing and creating new components, Vue just updates the props. App suddenly looks broken for no reason...
This is a mistake many unexperienced users do. You can find many questions here on SO like "my component is not reactive" or "how to force my component re-render" with many answers suggesting using :key hack or using a watcher ....which sometimes work but is almost always much more complicated then the right solution
Right solution is to write your components (if you can - sometimes it is not possible) as pure components (article is for React but the principles still apply). Very important tool for achieving this in Vue are computed propeties
So instead of introducing eventChanges data property (which might or might not be reactive - this is not clear from your code), you should make it computed property which is using notesArr prop directly:
get eventChanges() {
return this.notesArr.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
Now whenever notesArr prop is changed by the parent, eventChanges is updated and the component will re-render
Notes:
You are overusing async. Your getNotes function does not execute any asynchronous code so just remove it.
also do not mix async and then - it is confusing
Either:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray)
await this.checkAttendanceForPreviousTwoWeeks()
const results = await this.getCurrentParticipants()
this.currentP = results
this.notesArr = this.notes
or:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
Promise.all(promisesArray)
.then(() => this.checkAttendanceForPreviousTwoWeeks())
.then(() => this.getCurrentParticipants())
.then((results) => {
this.currentP = results
this.notesArr = this.notes
})
Great learning resource

Can't undertsand why single array inside state is not working

I'm learning react and redux and can't seem to understand why a simple array in my state is not functioning right but if I add just another variable which is not even being used then every thing works fine. This is my store:
const store = createStore(
reducer,
{items:[],
a:100
}
This is the mapping:
const mapStateToProps = (state) => {
return {
list:state.items,
a:state.a
};
};
This is what my reducer return:
return {a:state.a-1000,
items:state.items}
The variable 'a' is not being used but for some reason if I remove it from the above code blocks then the application does not function correctly. In the reducer's return statement even if I change state.a-1000 with state.a it stops functioning correctly. I can't seem to understand what is happening. The state works fine when there is just a variable in it that is not an array but when there is just an array inside the state for some reason it is requiring another variable.
redux state needs to be immutable. items:state.items mutates the state which wont be picked up by redux.
try {...state, items} (where items is the new list) or try using Immutable.JS
further info here - https://redux.js.org/faq/immutable-data

On page refresh, destructuring props gives unhydrated values in componentDidMount

I have a strange error in my application that I haven't come across in React.
Basically, when the page refreshes, I grab certain parameters from the URL, make API requests on the queries and then populate my Redux state with the results.
For example:
async componentDidMount() {
const {
values1, values2, match,
} = this.props;
await getSingleData(match.params.id);
await getData(values1, values2);
}
So from the URL parameter, I grab the Id, then make an API request to get the data and store it in Redux. However, when I use the desctructured syntax, I get empty or null values. When using the destructured syntax, it seems like it references the empty Redux store before the data hydration, while the full reference gives the correct values after the data hydration.
When I use the full this.props.values1 and this.props.values2 reference, it works fine.
For example, this code, everything works like it's supposed to:
async componentDidMount() {
const {
values1, values2, match,
} = this.props;
await getSingleData(match.params.id);
await getData(this.props.values1, this.props.values2);
}
I'm confused as to why this is happening. As far as I understand, descructuring objects will have the same reference as the non-descructured counterpart. But it's not the case apparently. Any input?
Destructuring happens immediately when the function is called and at that time, this.props is null.
After call to await getSingleData(match.params.id) (which is run after the destructuring) this.props is filled.
Therefore, you are able to access this.props.value1.
Destructuring does not track or observe the original object's changes.

React - Changing the state without using setState: Must avoid it?

My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect().
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.
var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().
Normally I should do:
var data = [];
for (var i in this.state.data) {
data.push(this.state.data[i]);
}
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:
Do I have to create the deep clone in this case? (for var i in ...)
If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.
My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react';
var ForumList = React.createClass({
render: function() {
return <div className="section-inner">
{this.state.data.map(this.eachBox)}
</div>
},
eachBox: function(box, i) {
return <div key={i} className="box-door">
<div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
{box.id}
</div>
</div>
},
getInitialState: function() {
return {data: [
{
id: 47,
is_collected: false
},
{
id: 23,
is_collected: false
},
{
id: 5,
is_collected: true
}
]};
},
clickCollect: function(index) {
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
}
});
module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js
Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
So, while nothing really stops you from mutating the component state without setState({}), you would have to avoid that at all costs if you want to really take advantage of React, otherwise you would be leapfrogging one of the library's core functionalities.
If you want follow react best practices, you should do shallow copy of all your array, when you change any property. Please look into "immutable" library implementation.
But, from my experience, and from my opinion, setState method should be called if you have "shouldCompomenentUpdate" implementations. If you think, that your shallow copy will be consume much more resources, then react virtual dom checks, you can do this:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
If I understood your question right, you have an array of objects and when a property of a single object in array changes,
Create a deep clone of the array and pass to setState
Create a shallow clone and pass to setState
I just checked with the redux sample todo app and in case of a single property of an object changes you've to create a fresh copy of that single object not the entire array. I recommend you to read about redux and if possible use to manage the state of your app.

what's the best way to deal with undefined props in react.js?

Let's assume I have an action that gets dispatched on page load, like say, in the index.js file.
example
store.dispatch(loadData());
in my reducer I initialize state to to an object. Something like this
function myReducer(state = {}, action)
now I have some smart component that subscribes to my state and then passes it down to another component to display the data. Another important note for this scenario is the fact that the retrieval of the data is happening asynchronously.
Let's also assume that the key of this object is some array.
So the markup component would have something like this
{this.props.object.key.map(k => do something)}
Now since key is undefined, if I call map on it, I blow up. The way I have been dealing with this, is by using a simple if check. If key is defined then run .map otherwise return null. Then by the time my data gets back from the server, the render will be called again due to a change in state that this component subscribed to. At this point the key is defined and map can be called.
Another approach, Is to define what your state will look like in the reducer. In other words, if I know that my state will be an object with an array property on it, I might do something like this.
const initialState = {
key:[]
}
function myReducer(state = initialState, action)
Doing this will benefit in the fact that now I won't need my if check since key is never undefined.
My questions is; are any of these approaches better than the other? Or perhaps, is there another way entirely to deal with this?
Generally, the approach I like to take is to define default props on the component which have the semantic meaning of "empty." For example, in context of the issue you describe I would typically structure my component like this (ES6 classes style):
class MyComponent extends React.Component {
static defaultProps = {
object: {
key: []
}
};
...
method() {
// this.props.object.key is an empty array, but is overriden
// with a value later after completion of the reducer
// (trigerred asynchronously)
this.props.object.key.map(doSomething);
}
}
This is relatively clean, prevents the code from throwing at run time, and forces you to create well-defined behaviors for semantically null, empty, or undefined input states.

Categories