Ajax call in MobX: MobX observer: Store is not available! Make sure it is provided by some Provider - ajax

I'm migrating from a traditional React application with the tree-structure to a state management structure with MobX.
Currently, my individual components propagate down data from an Ajax call made by the parent/grandparent, which works fine.
Now I wish to change this so that I don't work in the tree structure anymore due to the change of complexity and dependency of parallel grandchildren/children.
Say I do an axios.get in componentDidMount of a <Parent1/> React class. How do I then access the data using MobX?
What I've tried so far:
Creating a store.jsx that looks as such:
import { observable } from 'mobx';
const axios = require('axios');
class Store {
#observable parentdata;
loadParent = () => {
let that = this;
axios.get("/api/parent").then(function(response){
that.parentdata = response.parentdata;
}).catch(function(error){
// Error handling
})
};
}
export default Store;
My ReactDOM is rendered in container.jsx which contains all parents, meaning also <Parent1/>. In container.jsx we do the following:
import { Provider } from 'mobx-react';
import Store from './store/store.jsx';
let store = new Store();
and
ReactDOM.render(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById('content')
);
.. in the end.
In the render method of container.jsx I don't do anything with <Parent1/> - it simply renders the component as normal(that's the idea here, right?)
In parent1.jsx, I remove the previous axios.get and add these:
import { inject, observer } from 'mobx-react';
#inject('store') #observer
export default class Parent1 extends React.Component {
// .....
componentDidMount () {
this.props.store.loadParent();
After that, the error is provided: MobX observer: Store 'parentdata' is not available! Make sure it is provided by some Provider
Where did I go wrong in binding the data here?
Edit: Removing #inject and only having #observer results in: TypeError: Cannot read property 'loadParent' of undefined

Few things to address:
Assuming you named your parentData store 'store' and not 'Store' like the classname, the Provider looks like it's configured correctly
Here's how I'd change the store itself:
import { observable } from 'mobx';
const axios = require('axios');
class Store {
#observable parentdata;
loadParent = () => {
// There's no need to clone this if you're using arrow functions
axios.get("/api/parent").then((response) => {
this.parentdata = response.parentdata;
}).catch(function(error){
// Error handling
})
};
}
export default Store;
In the component, I might do this:
import { inject, observer } from 'mobx-react';
#inject('store') #observer
export default class Parent1 extends React.Component {
// ... //
componentDidMount () {
this.props.store.loadParent();
}
render() {
if (!this.props.store.parentData) { return (<div>Loading...</div>) }
const { parentData } = this.props.store
return (<pre>{JSON.stringify(parentData, null, 4)}</pre>)
}
}
My suspicion is that your store is case sensitive and mistakenly named.
A nifty tricks to debugging could be to console.log important variables or attach them to a window object to test.
eg:
class Parent1 {
componentDidMount() {
window.Parent1 = this
window.Parent1Props = this.props
window.Store = this.props.store // or `this.props.Store` check case sensitivity!
}
// Open browser console and try to access your now global variables

Related

Creating an Observable that gets its value from a subscriptions calculation

Hi i'm trying to create an Observable that will have values emitted to it from another subscription, it this case an ngrx Store Reducer.
export class IsolatedAgentService {
missionList$: Observable<any>; // I need this observables subscription to emit to calculatedValue$
calculatedValue$:Observable<any>; // I need this observable to get its values from the subscription of missionList$ subscription
missionList:any;
constructor(
private _store:Store<any>
){
this.missionList$ = this._store.select(root_reducers.getMissionList).pipe(skip(1));
this.missionList$.subscribe((val:any)=> {
let mostIsolatedCountry:any; //will hold value of calculation
this.missionList = val;
mostIsolatedCountry = this.getMostIsolatedCountry(this.missionList);
// I want tot emit mostIsolatedCountry to another subscription
});
}
What I'm trying to do:
export class IsolatedAgentService {
missionList$: Observable<any>;
calculatedValue$:Observable<any> = Observable.create((observer)=>{
// moved this line here from the previous missionList$ subscription
let calculated:any = this.getMostIsolatedCountry(this.missionList);
observer.next(calculated)
});
missionList:any;
calculatedValue:any;
constructor(
private _store:Store<any>
){
this.missionList$ = this._store.select(root_reducers.getMissionList).pipe(skip(1));
this.missionList$.subscribe((val:any)=> {
let mostIsolatedCountry:any;
this.missionList = val;
this.calculatedValue$.subscribe((value)=>{
this.calculatedValue = value;
});
});
}
Currently, I'm basically seting a class property in one subscription, then inside that same subscription, after setting the class property, I'm calling the second subscription which calculates the value from that set class property.
This does not feel right, and I'm sure its not the way to do it, but I'm lacking in my rxjs/observable knowledge at this point.
Note! Im not interested in emiting the calculated value trough a Store Action, I want an Observable that is specific to the class instance.
Here's the answer to your question:
export class IsolatedAgentService {
missionList$: Observable<Mission[]>;
calculatedValue$:Observable<any>;
constructor(
private _store:Store<any>
){
this.missionList$ = this._store.select(root_reducers.getMissionList).pipe(skip(1));
this.calculatedValue$ = this.missionList$.pipe(
map( missions => this.getMostIsolatedCountry(missions) )
);
}
}
or even
this.calculatedValue$ = this.missionList$.pipe(
map( this.getMostIsolatedCountry )
);
See more about NGRX facades: https://medium.com/#thomasburleson_11450/ngrx-facades-better-state-management-82a04b9a1e39
Why you arent exposing & using observables why even subscribe in a service?
What you should have instead
export class IsolatedAgentService {
missionList$: Observable<Mission[]>;
calculatedValue$:Observable<any>;
constructor(
private _store:Store<any>
){
this.missionList$ = this._store.select(root_reducers.getMissionList).pipe(skip(1));
this.calculatedValue$ = this._store.select(root_reducers.getMissionCalculatedValue).pipe(skip(1));
}
}
And a selector for doing that calculation you need.
export const getMissionCalculatedValue= createSelector(
getMissionList,
(missionList) => {
// do the calculations here
return calculationResult;
}
);

Ordered list of redux-form fields

Do you know how can I get the ordered list of field names from given form? Instance API has a property called "fieldList" and it's an array but it's not in correct order. (ordered list = [firstFieldName, secondFieldName, ...] so what I need is a list of field names in order they appear in my form - top to bottom)
Also the redux-form' action '##redux-form/REGISTER_FIELD' is dispatching out of correct form order so I guess it's not what I need here...
(My redux-form version: 7.3.0)
I have experience with redux-form and also have checked its API, but didn't find a documented way for getting the fields in the way they appear in the form.
However, here's how I would do it:
I'll create a Reducer, that will keep track of the fields in the order,
they are registered (appear in the form).
We have very detailed action. As you already mentioned - ##redux-form/REGISTER_FIELD action is dispatching out all the fields in process of being registered in the correct order. This action has the following payload:
{
type: '##redux-form/REGISTER_FIELD',
meta: {
form: 'user'
},
payload: {
name: 'firstName',
type: 'Field'
}
}
Create a reducer. So I'll just create a Reducer, that will listen for all ##redux-form/REGISTER_FIELD actions. Something like that:
// The reducer will keep track of all fields in the order they are registered by a form.
// For example: `user` form has two registered fields `firstName, lastName`:
// { user: ['firstName', 'lastName'] }
const formEnhancedReducer = (state = {}, action) {
switch (action.type) {
case '##redux-form/REGISTER_FIELD':
const form = action.meta.form
const field = action.payload.name
return { ...state, [form]: [...state[form], field] }
default:
return state
}
}
Usage. In order to get ordered fields by a form, you just have access the Store (state) formEnhancer property with the form name: state.formEnhanced.user.
Keep in mind that you have to consider some cases as ##redux-form/DESTROY, but I think it's a pretty straightforward implementation.
I would prefer to keep things simple and just subscribed to ##redux-form/REGISTER_FIELD and just change the reducer implementation a little bit, in order to prevent form fields duplication. So I will just validate if the form field is already registered and won't care for supporting ##redux-form/DESTROY.
Hope it helps.
One way that I have been able to retrieve an ordered list of form field names from a given form is via the registered fields stored in the redux form state using the connect HOC (Higher Order Component) from 'react-redux':
import React, { Component } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
class Foo extends Component {
render() {
const {
registeredFields,
} = this.props;
...
...
...
}
}
const mapStateToProps = (state, props) => {
// retrieve the registered fields from the form that is stored in redux state; using lodash 'get' function
const registeredFields = _.get(state, 'form.nameOfYourForm.registeredFields');
// creating an object with the field name as the key and the position as the value
const registeredFieldPositions = _.chain(registeredFields).keys().reduce((registeredFieldPositions, key, index) => {
registeredFieldPositions[key] = index;
return registeredFieldPositions;
}, {}).value();
return({
registeredFieldPositions,
});
};
// registeredFieldPositions will now be passed as a prop to Foo
export default connect(mapStateToProps)(Foo);

Using Config.skip with a React-Apollo Query

I'm having some trouble making use of the Config.skip property inside of my graphql() wrapper.
The intent is for the query to be fired with an argument of currentGoalID, only after a user has selected an item from the drop-down (passing the associated currentGoalID) , and the (Redux) state has been updated with a value for currentGoalID.
Otherwise, I expect (as per Apollo documentation) that:
... your child component doesn’t get a data prop at all, and the options or props methods are not called.
In this case though, it seems that my skip property is being ignored based upon the absence of a value for currentGoalID, and the option is being called because the webpack compiler/linter throws on line 51, props is not defined...
I successfully console.log the value of currentGoalID without the graphql()
wrapper. Any idea why config.skip isn't working? Also wish to be advised on the proper use of this in graphql() function call. I've excluded it here, but am unsure of the context, thanks.
class CurrentGoal extends Component {
constructor(props) {
super(props)
}
render (){
console.log(this.props.currentGoalID);
return( <p>Current Goal: {null}</p>
)
}
}
const mapStateToProps = (state, props) => {
return {
currentGoal: state.goals.currentGoal,
currentGoalID: state.goals.currentGoalID,
currentGoalSteps: state.goals.currentGoalSteps
}
}
const FetchGoalDocByID = gql `
query root($varID:String) {
goalDocsByID(id:$varID) {
goal
}
}`;
const CurrentGoalWithState = connect(mapStateToProps)(CurrentGoal);
const CurrentGoalWithData = graphql(FetchGoalDocByID, {
skip: (props) => !props.currentGoalID,
options: {variables: {varID: props.currentGoalID}}
})(CurrentGoalWithState);
// export default CurrentGoalWithState
export default CurrentGoalWithData
See the answer here: https://stackoverflow.com/a/47943253/763231
connect must be the last decorator executed, after graphql, in order for graphql to include the props from Redux.

How do I create a generic reducer plugin, that fires on all forms?

Currently my code works, but only for the forms specified specifically in the combine reducer function. But, I would like to have my code work generally for all forms loaded in my single page app.
Here is the relevant code:
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
someReducer,
anotherReducer,
form: formReducer.plugin({
specificFormId: (state, action) => { // <-- I don't want this only for specificFormId, I want this to happen for all my forms,
// or at least have a dynamic way of adding more forms
const {type, payload} = action;
switch(type) {
case 'RESET_LINK_TYPE_FIELDS': {
return {
...state,
registeredFields: {
...state.registeredFields,
// Do some custom restting here based on payload
}
};
}
default:
return state;
}
}
})
});
So, anytime my <Field ..of a certain type/> fires off this the RESET_LINK_TYPE_FIELDS action, I want the correct form to respond to it.
In the action payload, I can specifically the form identifier or anything else I would need to make this work.
In fact, if the .plugin let me do my own form state slicing, I could easily do this, but because it forces me to pass an object, with a hardcoded form identifier it doesn't work.
Is there a way to have the plugin give me the WHOLE form state, and then I will slice as needed, and return state as needed based on payload?
There is currently no way to do this with the existing API.
You could jury rig a solution by wrapping the redux-form reducer in your own thing.
export default combineReducers({
someReducer,
anotherReducer,
form: resetHack(formReducer)
})
function resetHack(formReducer) {
return (state, action) => {
if(action.RESET_LINK_TYPE_FIELDS) {
// manipulate slice somehow
} else {
return formReducer(state, action)
}
}
}

Aurelia - injecting a service, but its properties remaining undefined

I have a service:
import {eventsData} from 'services/eventsData';
import {singleton} from 'aurelia-framework';
#singleton()
export class DataRepository{
constructor(){
}
getEvents(){
var promise = new Promise((resolve,reject)=>{
if(!this.events){
setTimeout(_=>{
this.events = eventsData;
resolve(this.events);
},2000)
}
else{
resolve(this.events);
}
});
return promise;
}
getEvent(eventId){
return this.events.find(event => event.id == eventId);
}
}
which I am initiating as is in:
import {inject} from 'aurelia-framework';
import {DataRepository} from 'services/datarepository';
#inject(DataRepository)
export class events{
constructor(dataRepository, lazyOfImLazy){
dataRepository.getEvents().then(events => this.events = events);
}
createAndUseLazy(){
console.log("About to use lazy");
this.lazyOfImLazy().doStuff();
}
}
And then in :
import {inject} from 'aurelia-framework';
import{DataRepository} from 'services/dataRepository';
#inject(DataRepository)
export class EventDetail{
constructor(dataRepository){
this.dataRepository = dataRepository;
}
activate(params, routeConfig){
console.log(params.eventId);
console.log(this.dataRepository);
this.event = this.dataRepository.getEvent(1);
}
}
But when calling this.dataRepository.getEvent(1), then in dataRepository return this.events.find(event => event.id == eventId);, the this.events is undefined. I would have thought that by defining it as a singleton it would preserve this.events, when being instantiated in the promise. And the this.events is then populated properly. The view of events is using it.
Am I missing something?
Figured it out: The imports are case sensitive.
import{DataRepository} from 'services/datarepository'; and import{DataRepository} from 'services/dataRepository'; notice the lower cased r in datarepository in the first line and the capital one in dataRepository in the second.
It creates two different instances of the same service, due to the different case sensitivity in the string after from.
For those who ever encounter this, and spent hours trying to figure it out :)

Resources