Immutable JS: updating the nested dynamic object - redux

My initial state is,
export const initialState = fromJS({
current: {},
page: {
loading: false,
isFirstLoaded: false,
},
meta: {},
});
In My reducer, since I have defined the nested structure for page,
state.setIn(['page', 'loading'], true);
works fine.
Since I haven't defined the nested structure for current or meta
state.setIn(['current', 'status'], 'done')
throws an error 'Invalid keypath'
When I logged the state,
{
current: {..with properties including status is there as object..},
page: Map, // <--- why this alone being as Immutable Map,
meta: {}, // <--- This is also being as plain object
}

You should be able to use the mergeIn method in your case as per the following documentation: http://facebook.github.io/immutable-js/docs/#/Map/mergeIn.
Hope it helps!

Related

Can anyone help implementing Nuxt.js Google Tag Manager with function based id

I installed and add this code to my nuxt.config.js and it works perfectly fine. (Link to package)
modules: [
['#nuxtjs/google-tag-manager', { id: 'GTM-XXXXXXX' }],
]
Now I am trying to implement instead of a static ID a function which will return an ID.
I tried to add this lines into my nuxt.config. js but it is not working. Obviously I have to put it somewhere else or so...
This is what I tried
nuxt.config.js
const code = '1234567'
id: () => {
return 'GTM-' + code
}
export default {
...
modules: [
['#nuxtjs/google-tag-manager', { id: id }],
]
...
}
What would be the correct way implementing this?
I would like to do something like that at the end.
modules: [
['#nuxtjs/google-tag-manager', {
id: ({ req }) => {
if (req.headers.referer == "exmple.com")
return 'GTM-156'
if (req.headers.referer == "exmple.it")
return 'GTM-24424'
if (req.headers.referer == "exmple.es")
return 'GTM-2424'
}
}]]
EDIT:
I solved my problem by rewriting the whole module. It is not possible to use this Module because it is loaded only on build time. I rewrote the module and moved the code into nuxtServerInit.
nuxtServerInit is called on each request (modules only onetime). In the request I asked from which domain the request is coming. Depending on the domain I add different google-tag-manager id's to the head and the plugin.
From package docs:
modules: [
['#nuxtjs/google-tag-manager', {
id: () => {
return axios.get('http://example.com/')
.then(({ data }) => {
return data.gtm_id
})
}
}]]
You can use process.env.NODE_ENV inside function which will return an ID
Edit 1
To put the gtm id, depending on req.headers.referer you need to provide context to the function returning the id. This can be done in middleware
See how it works here
https://github.com/nuxt-community/modules/blob/master/packages/google-tag-manager/plugin.js
Edit 2
As far as I understand your question, it will not work to have a query context in the config.
Look at i18n middleware: request.locale - > store - > update modules (router, vuetify, moment, etc.)
https://nuxtjs.org/examples/i18n/
~/middleware/gtm.js
export default function ({ app, store }) {
// app.$gtm contains id, you can set another from store
}
don't forget to add middleware to the page
page.vue
export default {
middleware: ['gtm']
}

How do I pass a new key as an array with immer?

Say I have some initial state like
const initialState = {
loading: false,
updating: false,
saving: false,
data: {},
error: null
};
And I want add to data as the result of an action but the data I want to add is going to be an array. How do I go about this?
I've tried
export default produce((draft, action) => {
switch (action.type) {
case UPDATE_STATE.SUCCESS:
draft.data.new_Array.push(action.payload);
draft.loading = false;
break;
default:
}
}, initialState);
but this errors.
If I start the initial state as
const initialState = {
loading: false,
updating: false,
saving: false,
data: {
newArray: []
},
error: null
};
any update to the state before I make the array key overwrites the initial state and removes the key. ie
export default produce((draft, action) => {
switch (action.type) {
case OTHER_UPDATE_STATE.SUCCESS:
draft.data = payload.action;
draft.loading = false;
break;
default:
}
}, initialState);
can anyone help?
There's one thing I noticed you'll run into trouble, draft.data.new_Array.push(action.payload);
Make sure you don't modify the existing data structure. The reason is that redux relies on the memory reference of an object, if the object reference doesn't change, it might fool the redux that nothing actually happened in the past.
In your case, i have a feeling that nothing will ever get triggered.
One way to modify the reference is to create an new object, ex.
return [...data, newElementObject]

seamless-immutable is not updating state consistently

I'm working with seamless-immutable and redux, and I'm getting a strange error when updating the state. Here's my code, without the bits like the action return or combineReducers. Just the junk that's running/causing the error.
Initial State
{
things: {
fetching: false,
rows: []
}
}
Action Handler
export default {
[DEALERS_REQUEST]: (state, action) => {
return Immutable({ ...state, fetching: true });
},
[DEALERS_RECEIVE]: (state, action) => {
return Immutable({ ...state, rows: action.payload, fetching: false });
},
Middleware with thunk
export const thingsFetch = (data) => {
return (dispatch, getState) => {
dispatch(thingsRequest());
dispatch(thingsReceive(data));
}
}
Now, what's weird is, if I run these two actions together, everything is fine.
If I only dispatch thingsRequest(), I get a "cannot push to immutable object" error.
I've tried using methods like set, update, replace, merge, but they usually return with "this.merge is not a function".
Am I doing something wrong procedurally or should I contact the module dev to report an issue?
This issue on this was that, on the case of an empty array, the component was trying to write back to the Immutable object with it's own error message.
To get around this, I pass the prop as mutable. There's also some redux-immutable modules that replace the traditional connect function to all the app to pass mutable props to components while maintaining immutability in the state.

How can I add data to the pending action?

I am using Axios, Redux and Redux Promise Middleware.
I have the following action creator:
return {
type: 'FETCH_USERS',
payload: axios.get('http://localhost:8080/users',
{
params: {
page
}
}
)
}
In my reducer for the FETCH_USERS_PENDING action, I would like to save the requested url in my state. How can I do this?
You want to use redux-promise-middleware's "meta" variable. Like so:
return {
type: 'FETCH_USERS',
meta: { url: 'http://localhost:8080/users' },
payload: axios.get('http://localhost:8080/users', config)
}
You could pass it through in your params, but that won't be returned until the page is fetched. Which means it won't be passed back during FETCH_USERS_PENDING.
And I'm pretty sure if you include directly in the return object (like how Lucas suggested), it will be stripped out of the FETCH_USERS_PENDING stage.
Here's the FETCH_USERS_PENDING stage from redux-promise-middleware:
/**
* First, dispatch the pending action. This flux standard action object
* describes the pending state of a promise and will include any data
* (for optimistic updates) and/or meta from the original action.
*/
next({
type: `${type}_${PENDING}`,
...(data !== undefined ? { payload: data } : {}),
...(meta !== undefined ? { meta } : {})
});
As you can see during this stage, the middleware returns the appended "type" attribute and it checks for "data" & "meta" attributes. If present, they are passed along within the action.
Here's the redux-promise-middleware source code if you want to look into it further.
Simply pass the URL in the action as well:
return {
type: 'FETCH_USERS',
url: 'http://localhost:8080/users',
payload: axios.get('http://localhost:8080/users', {
params: {
page
}
}
}
and access it in the reducer with action.url. Or you can leave the action as it is, and access it in the promise resolution:
action.payload.then(function(response) {
var url = response.config.url;
});

MDG ValidatedMethod with Aldeed Autoform: "_id is not allowed by the schema" error

I'm getting the error "_id is not allowed by the schema" when trying to use an autoform to update a collection via a ValidatedMethod.
As far as I can see from this example and the official docs there is no expectation for my schema to include the _id field, and I wouldn't expect to be updating the id from an update statement, so I have no idea why this error is happening.
If I switch from using the validated method to writing directly to the collection (with a schema attached to the collection that doesn't have the id in) everything works as expected, so I'm assuming the issue is with my the validate in my ValidatedMethod.
Any idea what I'm doing wrong?
Template: customer-edit.html
<template name="updateCustomerEdit">
{{> quickForm
collection="CustomerCompaniesGlobal"
doc=someDoc
id="updateCustomerEdit"
type="method-update"
meteormethod="CustomerCompanies.methods.update"
singleMethodArgument=true
}}
</template>
Template 'code behind': customer-edit.js
Template.updateCustomerEdit.helpers({
someDoc() {
const customerId = () => FlowRouter.getParam('_id');
const instance = Template.instance();
instance.subscribe('CustomerCompany.get', customerId());
const company = CustomerCompanies.findOne({_id: customerId()});
return company;
}
});
Update Validated Method:
// The update method
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.update',
// register a method for validation, what's going on here?
validate: new SimpleSchema({}).validator(),
// the actual database updating part validate has already been run at this point
run( newCustomer) {
console.log("method: update");
return CustomerCompanies.update(newCustomer);
}
});
Schema:
Schemas = {};
Schemas.CustomerCompaniesSchema = new SimpleSchema({
name: {
type: String,
max: 100,
optional: false
},
email: {
type: String,
max: 100,
regEx: SimpleSchema.RegEx.Email,
optional: true
},
postcode: {
type: String,
max: 10,
optional: true
},
createdAt: {
type: Date,
optional: false
}
});
Collection:
class customerCompanyCollection extends Mongo.Collection {};
// Make it available to the rest of the app
CustomerCompanies = new customerCompanyCollection("Companies");
CustomerCompaniesGlobal = CustomerCompanies;
// Deny all client-side updates since we will be using methods to manage this collection
CustomerCompanies.deny({
insert() { return true; },
update() { return true; },
remove() { return true; }
});
// Define the expected Schema for data going into and coming out of the database
//CustomerCompanies.schema = Schemas.CustomerCompaniesSchema
// Bolt that schema onto the collection
CustomerCompanies.attachSchema(Schemas.CustomerCompaniesSchema);
I finally got to the bottom of this. The issue is that autoform passes in a composite object that represents the id of the record to be changed and also a modifier ($set) of the data, rather than just the data itself. So the structure of that object is along the lines of:
_id: '5TTbSkfzawwuHGLhy',
modifier:
{
'$set':
{ name: 'Smiths Fabrication Ltd',
email: 'info#smithsfab.com',
postcode: 'OX10 4RT',
createdAt: Wed Jan 27 2016 00:00:00 GMT+0000 (GMT Standard Time)
}
}
Once I figured that out, I changed my update method to this and everything then worked as expected:
// Autoform specific update method that knows how to unpack the single
// object we get from autoform.
update = new ValidatedMethod({
// register the name
name: 'CustomerCompanies.methods.updateAutoForm',
// register a method for validation.
validate(autoformArgs) {
console.log(autoformArgs);
// Need to tell the schema that we are passing in a mongo modifier rather than just the data.
Schemas.CustomerCompaniesSchema.validate(autoformArgs.modifier , {modifier: true});
},
// the actual database updating part
// validate has already been run at this point
run(autoformArgs)
{
return CustomerCompanies.update(autoformArgs._id, autoformArgs.modifier);
}
});
Excellent. Your post helped me out when I was struggling to find any other information on the topic.
To build on your answer, if for some reason you want to get the form data as a single block you can use the following in AutoForm.
type="method" meteormethod="myValidatedMethodName"
Your validated method then might look something like this:
export const myValidatedMethodName = new ValidatedMethod({
name: 'Users.methods.create',
validate(insertDoc) {
Schemas.NewUser.validate(insertDoc);
},
run(insertDoc) {
return Collections.Users.createUser(insertDoc);
}
});
NB: The Schema.validate() method then requires an Object, not the modifier as before.
I'm unclear if there are any clear advantages to either method in general.
The type="method-update" is obviously the way you want to go for updating documents because you get the modifier. The type="method" seems to be the best way to go for creating a new document. It would likely also be the best option in most cases where you're not intending to create a document from the form data.

Resources