mobx-state-tree error while converting to anonymousModel - javascript

What should happen - succesfully create RootStore from defaultSnapshot and reset it when needed, successful backcuping in localStorage.
What happens - getting an error while trying to apply a snapshot, when attempting to open page, just by running code even without interacting with it.
When checking types manually I do not see problems with type mistakes, so can not understand why it throws error.
Codesandox live minimum code
Error
Error: [mobx-state-tree] Error while converting `{"token":"","myInnerInfo":{"login":"","type":""},"myDisplayInfo":{"login":"","type":""},"loginInfo":{"login":"","type":""},"loginList":[],"loading":false,"logined":false}` to `AnonymousModel`:
at path "/myInnerInfo/login" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
at path "/myInnerInfo/type" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
at path "/myDisplayInfo/login" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
at path "/myDisplayInfo/type" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
at path "/loginInfo/login" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
at path "/loginInfo/type" value `""` is not assignable to type: `AnonymousModel` (Value is not a plain object).
File structure
store.js (imported in index.js)
import { types, flow, onSnapshot, applySnapshot } from 'mobx-state-tree';
import { values } from 'mobx';
import axios from 'axios';
const defaultSnapshot = {
token: '',
myInnerInfo: { login: '', type: '' },
myDisplayInfo: { login: '', type: '' },
loginInfo: { login: '', type: '' },
loginList: [],
loading: false,
logined: false,
}
const User = types
.model({
login: '',
type: '',
}).actions(self => ({
setUserInfo({ login, type }) {
self.login = login;
self.type = type;
}
}))
const RootStore = types
.model({
token: '',
myInnerInfo: types.map(User),
myDisplayInfo: types.map(User),
loginInfo: types.map(User),
loginList: types.array(types.string),
loading: false,
logined: false,
}).views(self => ({
get loginListLength() {
return values(self.loginList).length;
},
})).actions(self => ({
// setToken (token) {
// self.token = token;
// },
// setMyInnerInfo (userInfo) {
// self.myInnerInfo.setUserInfo(userInfo);
// },
// setMyDisplayInfo (userInfo) {
// self.myDisplayInfo.setUserInfo(userInfo);
// },
// setLoginInfo (userInfo) {
// self.loginInfo.setUserInfo(userInfo);
// },
// setLoginList (loginList) {
// self.loginList = loginList;
// },
// setLoading (loading) {
// self.loading = loading;
// },
// setLogined (logined) {
// self.logined = logined;
// },
// reset() {
// self.token = '';
// self.myInnerInfo = User.create({});
// self.myDisplayInfo = User.create({});
// self.loginInfo = User.create({});
// self.loginList = [];
// self.loading = false;
// self.logined = false;
// },
register: flow(function* register(login, password) {
self.loading = true;
try {
const res = yield axios({
method: 'POST',
url: `${process.env.REACT_APP_HOST}/users/register`,
data: { login, password },
});
alert('Registered');
self.loading=false;
} catch (e) {
console.error(e);
alert(`Error registering! Please retry!`);
resetStore();
}
}),
login: flow(function* login(login, password) {
self.loading = true;
try {
const res = yield axios({
method: 'POST',
url: `${process.env.REACT_APP_HOST}/users/login`,
data: { login, password },
});
self.token = res.data.token;
self.myInnerInfo.setUserInfo(res.data.user);
self.myDisplayInfo.setUserInfo({ login: '', type: '' });
self.loginInfo.setUserInfo({ login: '', type: '' });
self.loginList = [];
alert('Logined');
self.logined = true;
self.loading=false;
} catch (e) {
console.error(e);
alert(`Error logining! Please retry!`);
resetStore();
}
}),
unlogin() {
self.loading = true;
self.logined = false;
self.token = '';
self.myInnerInfo.setUserInfo({ login: '', type: '' });
self.myDisplayInfo.setUserInfo({ login: '', type: '' });
self.loginInfo.setUserInfo({ login: '', type: '' });
self.loginList = [];
alert('Unlogined');
self.loading=false;
},
getMyInfo: flow(function* getMyInfo() {
self.loading = true;
try {
const res = yield axios({
method: 'GET',
url: `${process.env.REACT_APP_HOST}/users/my-info`,
headers: {'Authorization': self.token ? `Bearer ${self.token}` : ''},
});
// self.token = res.data.token;
// self.myInnerInfo.setUserInfo(res.data.user);
self.myDisplayInfo.setUserInfo(res.data);
// self.loginInfo.setUserInfo({});
// self.loginList = [];
alert('Loaded information');
// self.logined = true;
self.loading=false;
} catch (e) {
console.error(e);
alert(`Error loading information! Please retry!`);
resetStore();
}
}),
getLoginList: flow(function* getLoginList() {
self.loading = true;
try {
const res = yield axios({
method: 'GET',
url: `${process.env.REACT_APP_HOST}/users/list-logins`,
headers: {'Authorization': self.token ? `Bearer ${self.token}` : ''},
});
// self.token = res.data.token;
// self.myInnerInfo.setUserInfo(res.data.user);
// self.myDisplayInfo.setUserInfo(res.data);
// self.loginInfo.setUserInfo({});
self.loginList = res;
alert('Loaded list');
// self.logined = true;
self.loading=false;
} catch (e) {
console.error(e);
alert(`Error loading list! Please retry!`);
resetStore();
}
}),
getUserInfo: flow(function* getUserInfo(login) {
self.loading = true;
try {
const res = yield axios({
method: 'GET',
url: `${process.env.REACT_APP_HOST}/users/my-info/${login}`,
headers: {'Authorization': self.token ? `Bearer ${self.token}` : ''},
});
// self.token = res.data.token;
// self.myInnerInfo.setUserInfo(res.data.user);
// self.myDisplayInfo.setUserInfo(res.data);
self.loginInfo.setUserInfo(res.data);
// self.loginList = [];
alert('Loaded information');
// self.logined = true;
self.loading=false;
} catch (e) {
console.error(e);
alert(`Error loading information! Please retry!`);
resetStore();
}
}),
}));
const store = RootStore.create();
if(!(localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY] && JSON.parse(localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY]))) {
localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY] = JSON.stringify(defaultSnapshot);
}
applySnapshot(store, JSON.parse(localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY]));
onSnapshot(store, snapshot => {
localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY] = JSON.stringify(snapshot);
console.info(snapshot);
});
export default store;
export function resetStore() {
localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY] = JSON.stringify(defaultSnapshot);
applySnapshot(store, JSON.parse(localStorage[process.env.REACT_APP_LOCALSTORAGE_KEY]));
}
package.json
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"#testing-library/jest-dom": "^5.11.9",
"#testing-library/react": "^11.2.3",
"#testing-library/user-event": "^12.6.0",
"axios": "^0.21.1",
"mobx": "^6.0.4",
"mobx-react": "^7.0.5",
"mobx-state-tree": "^5.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

It appears that your defaultSnapshot does not match your defined model structure.
You define your default snapshot as follows:
const defaultSnapshot = {
token: '',
myInnerInfo: { login: '', type: '' },
myDisplayInfo: { login: '', type: '' },
loginInfo: { login: '', type: '' },
loginList: [],
loading: false,
logined: false,
}
However, if you getSnapshot of your store after you create it with no arguments, you get:
{
token: "",
myInnerInfo: {},
myDisplayInfo: {},
loginInfo: {},
loginList: [],
loading: false,
logined: false
}
Which would be a "default snapshot" in the sense that it is what happens when you create your store with no specific data.
Now this looks like the two should be compatible, except that you defined the three Info fields as maps. Maps of models look like this:
{
"<id>": { <model snapshot> },
…
}
Therefore, when loading your default snapshot, it causes an error because it tries to treat what you intended to be model data as map data - it thinks you have a collections of two Users with keys of login and type, and values of "", instead of objects compatible with User. For instance,
…
myInnerInfo: {
login: { login: 'some user data', type:'' },
type: { login: 'another user data', type:'' }
},
…
Would work, but doesn't seem like what you intended.
What you probably intended to do was make the Info fields directly of the User type, not a map of the User type, or perhaps an optional or the User type, since then you don't need to specify a User when creating the store. So perhaps your store model should look like this:
.model({
token: '',
myInnerInfo: types.optional(User, {}),
myDisplayInfo: types.optional(User, {}),
loginInfo: types.optional(User, {}),
loginList: types.array(types.string),
loading: false,
logined: false,
})
This structure is compatible with your default snapshot, and does not require values when creating the store.
Note that primitive values are automatically optional, but models are not (hence why the explicit optional call). An optional parameter has a default value, but will still exist. It just does not need to be explicitly defined at create time. Also, be sure to reset your localStorage when testing, or it may seem like it didn't work...

Related

Select key value javascript param comment

So I'm trying to make it so the user doesn't have to type in a value only select. For better understand this is what I mean.
But when I try running it is get this
Promise { 'Isekai' }
{ error: true, error_message: 'This genre does not exist.' }
index.d.ts file:
interface fetchAnimeByGenre {
animeTitle: string,
animeId: string,
animeImg: string,
animeSeason: string,
score: number
}
function fetchAnimeByGenre(genre:'Action' | 'Adventure' | 'Anti-Hero' | 'CGDCT' | 'College'): Array<fetchAnimeByGenre>;
const GENRES = [
'Action', 'Adventure', 'Anti-Hero', 'CGDCT',.....];
if (!genre) {
return {
error: true,
error_message: "No genre provided"
}
};
var list = []
if (genre.toLowerCase() === "anti-hero") {
genre = "Anti-Hero"
} else if (genre.toLowerCase() === "cgdct") {
genre = "CGDCT"
} else {
genre = firstLetterToUpperCase(genre);
}
if (!GENRES.includes(genre)) {
return {
error: true,
error_message: "This genre does not exist."
}
};
const res = await axios.request({
url: animixBase + "api/search",
method: 'POST',
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": USER_AGENT
},
data: new URLSearchParams({ genre, minstr: 99999999, orderby: "popular" })
});
res.data.result.map((anime) => {
list.push({
animeTitle: anime.title,
animeId: anime.url.split("/v1/")[1],
animeImg: anime.picture,
animeSeason: anime.infotext,
score: anime.score / 100
})
});
return list;
Is there any way I can fix this? (side note: I am fairly new to this d.ts stuff so)

apollo client offsetLimitPagination not working

I have a hook..
export function useLazyProposalList() {
const [getQueueData, { loading, data, error, fetchMore }] = useLazyQuery(PROPOSAL_LIST, {
fetchPolicy: 'no-cache',
});
const proposalList = React.useMemo(() => {
if (!data) {
return null;
}
return transformProposals(data);
}, [data]);
return {
getQueueData,
fetchMore,
loading,
data: proposalList,
error,
};
}
In the component
const {
getQueueData,
data: queueData,
fetchMore: fetchMoreProposals,
// loadMore: loadMore,
} = useLazyProposalList();
If user clicks on fetch more button, I call: fetchMoreProposals .
await fetchMoreProposals({
variables: {
offset: visibleProposalList.length,
},
});
but this doesn't update my data. I read that we should use offsetLimitPagination, but my data from query is not array itself. It's like this: queue { id: '1', items:[] } and because of this, offsetLimitPagination doesn't work. So I tried merge
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
queue: {
keyArgs: false,
merge(existing, incoming) {
console.log(existing, incoming);
if (!incoming) return existing;
if (!existing) return incoming;
},
},
},
},
}
but in the console, it just prints refs instead of real data.
What could be the issue ?

How can I detect that it's the same file in this custom method call

I learn JavaScript React.
I have this state:
state = { files: [] };
And when I call this method with the same prop two times the test is always true when it should be false in the second call:
someMethod() {
const { newFile } = this.props;
const { files } = this.state;
let test = files.every((file) => newFile.size !== file.size);
if (test) {
const newList = files.concat({ newFile });
this.setState({
files: newList,
});
}
}
In The code above the newFile looks like this;
{
source: "react:Dashboard",
id: "uppy-/object/object/1612029328210-2r-10-2t-image/jpeg-42985-1612029045180",
name: "[object Object]1612029328210",
extension: "",
meta: {
relativePath: null,
name: "[object Object]1612029328210",
type: "image/jpeg",
},
type: "image/jpeg",
data: {
},
progress: {
percentage: 0,
bytesUploaded: 0,
bytesTotal: 42985,
uploadComplete: false,
uploadStarted: null,
},
size: 42985,
isRemote: false,
remote: "",
preview: undefined,
}
How can I detect that it's the same newFile and by so not adding it?
Why files.concat({ newFile })? I think you meant to do files.concat(newFile) directly without creating an object with a newFile field that contains the file itself.

How use a mutation function in a action function in Vuex?

I have this Vuex:
export default new Vuex.Store({
state: {
userInfo: {
nit_ID: { ID: '', Desc: '' },
userName: { ID: '', Desc: '' },
typeDocument: { ID: '', Desc: '' },
document: '',
},
globalPublicKey: 'ASDFGHJKL1234567890',
},
mutations: {
updateUserInfo(state, payload) {
state.userInfo = payload;
},
},
getters: {
userInfo: (state) => { return state.userInfo; },
},
actions: {
validateUserSession(context) {
var valido = false;
try {
let storageInfo = JSON.parse(
sjcl.decrypt(context.state.globalPublicKey, localStorage.userInfo)
);
if (localStorage.userToken === storageInfo.token) {
context.mutations.updateUserInfo(storageInfo);
valido = true;
}
} catch (e) {
console.error(e);
}
return valido;
},
},
})
But the problem is that I can't access to the mutation updateUserInfo(), I know that is easy to solved, only do the updateUserInfo process in my action, but the question is How can I use a mutation into a action?
In VueJS you can call a mutation from an action by calling context.commit, like this:
context.commit('mutationName', params)
params can be omitted if not parameters are passed to the mutation.
More on this here: vuex.vuejs.org/guide/actions.html
Actually you call a mutation from anywhere with a commit - but it's advised to use actions (so dispatch an action) that in turn commits the data (actually mutates the state).

How to make a custom route for users? And how to add hooks to it?

I'm trying to add a route /me to get user authenticated information. This is what I have at my files.
I've tried adding a route /me at users.services file, but I'm getting this error: "error: MethodNotAllowed: Method find is not supported by this endpoint."
I want to get response with a user object (based on token) to a GET method to route '/me'.
users.service.js
// Initializes the `users` service on path `/users`
const createService = require('feathers-sequelize');
const createModel = require('../../models/users.model');
const hooks = require('./users.hooks');
module.exports = function (app) {
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'users',
Model,
paginate
};
// Initialize our service with any options it requires
app.use('/users', createService(options));
app.use('/me', {
get(id, params) {
return Promise.resolve([
{
id: 1,
text: 'Message 1'
}
])
}
})
// Get our initialized service so that we can register hooks and filters
const service = app.service('users');
service.hooks(hooks);
};
users.hooks.js
const { authenticate } = require('#feathersjs/authentication').hooks;
const {
hashPassword, protect
} = require('#feathersjs/authentication-local').hooks;
module.exports = {
before: {
all: [ ],
find: [ authenticate('jwt') ],
get: [],
create: [ hashPassword() ],
update: [ hashPassword() ],
patch: [ hashPassword() ],
remove: []
},
after: {
all: [
// Make sure the password field is never sent to the client
// Always must be the last hook
protect('password')
],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
users.model.js
// See http://docs.sequelizejs.com/en/latest/docs/models-definition/
// for more of what you can do here.
const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;
module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const users = sequelizeClient.define('users', {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
}, {
hooks: {
beforeCount(options) {
options.raw = true;
}
}
});
users.associate = function (models) { // eslint-disable-line no-unused-vars
// Define associations here
// See http://docs.sequelizejs.com/en/latest/docs/associations/
};
return users;
};
What you did through
app.use('/me', {
get(id, params) {
return Promise.resolve([
{
id: 1,
text: 'Message 1'
}
])
}
})
Was implement routes for /me/:id. The find method is what runs for the base route of /me.
I don't think a separate service is really necessary though. An easier solution would be to use a before all hook that changes the id if you are accessing /users/me:
module.exports = function() {
return async context => {
if(context.id === 'me') {
context.id = context.params.user._id;
}
}
}

Categories

Resources