Is there a way to save dynamic JSON into Mobx State Tree? - reactjs

I was using Redux before and saving dynamic JSON into the store was quite easy. I was thinking, how can we do the same in the Mobx-State-Tree, without actually defining the model structures.
.model({
data:types.array(...)
)}
I was trying, but again I need to define the JSON array structure into it.

The whole idea for using mobx state tree is to define implicit data types into your application.
But if for some reason you want to save JSON without defining is to convert it into a string using JSON.stringify({...})
And the model will be actually a string
.model({
data:types.string
)}
Then you can use actions to save JSON into it
.actions((self) => ({
saveJSON(jsonData){
self.data = JSON.stringify(jsonData)
}
})
You can then use this JSON anywhere by calling store.getJson property with JSON.parse() using view
views((self) => ({
get getJson(){
return JSON.parse(self.data)
}
})

I needed to do this because I receive object of a well defined type, but that type contains a config field, that can be any json.
I managed to do it in MST by defining:
export const AnyJsonValue = types.union(
types.string,
types.number,
types.integer,
types.Date,
types.boolean,
types.map(types.late(() => JsonValue)),
types.array(types.late(() => JsonValue)),
types.undefined,
types.null
);
And then using it like this:
export const MyObject = types.model("MyObject", {
uuid: types.identifier,
name: types.string,
config: types.maybeNull(types.map(AnyJsonValue)),
...
});
If you have some restrictions, you can still set them.
For example, if you want config to be either null, or a dict with any json nested (so config can't be a literal or array, but can be a dict containing them), you can use the type: t.maybeNull(t.map(JsonValue)).

Related

setState mutates the object reference

I am getting an object from parent component and setting to state. In child component I am updating the state, but the parent reference object values also changing instead of only state changes.
Parent Component has a huge object,
obj = {
values: {
per1: { name: "rsk" },
per2: { name: "ssk" },
}
}
Child Component:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState(obj.values);
const onChange = useCallback(({target}) => {
setInp((prev) => {
const nD = { ...prev };
//k1, k2 comes from some logic
nD[k1][k2] = target.value;
return nD;
})
}, []);
return (
Here the "inp" state is looped by objects and keys in text box to build a html form
)
}
Here the question is, why the core obj.values also changing on onChange setInp time. I dont want to disturb the obj.values untill i submit the form.
Because before submit the Form, I need to validate,
obj.values are equal or not to inp state values
Any idea on this.
The original object is changing because in JS, when you pass an array or an object in such a way, you are actually passing a reference to the original object/array.
Meaning that any changes made to the reference, will also affect the original.
In-order to avoid using the reference, you can copy the object/array and work with the copy instead.
There are a few ways of doing this, the simplest IMO is using the spread syntax.
Example:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState({...obj.values});
...
}
What we do here is "spread" the contents of obj.values into a new object, thus creating a new object and avoiding using the reference.
Note that the spread syntax only makes a shallow-copy, this isn't necessarily an issue unless you have some complex object which contains some other nested objects within it.
If you do need to perform a deep-copy, one simple way of doing it is via JSON methods.
Example:
const clone = JSON.parse(JSON.stringify(original));
First, this obj variable is incorrect because per1 is defined as object and object consist of key and value, but it is like a string array so please check that, and below is solution
In Child Component you should create a copy of that obj variable
const [inp, setInp] = useState(Object.assign({}, obj.values));
Bz if you set inp as the same variable it will pass the address of that variable so for that you need to create the copy and then you can change that inp.

How to get an object from firebase in react native

I'm trying to read object data from firebase, but since I use the code below, some error occurred.
This is my method to read data from firebase.
function getMenuData() {
let newMenu = [];
MENUref.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const menu = {
id: doc.id,
Name: doc.data().name,
desc: doc.data().desc,
url: Images[doc.data().name],
people: doc.data().people,
type: doc.data().type,
time: doc.data().time,
ingre: doc.data().ingre,
season: doc.data().season,
like: doc.data().like,
}
newMenu.push(menu)
});
setMenu(newMenu)
setDisplayOfData(newMenu)
// console.log('food', Menu)
});
}
Then pop out "Objects are not valid as a React child." error message.
After I do some research shows it need to done through map() method, but can it performed in the "menu: {}" ?
The goal is to read the object data from firebase and make it like this.
data structure
This is my firebase data structure.
Or some available approaches can do the same thing with the object data like this?
Thanks a lot!
In the JavaScript SDK there is something known as useCollectionData than can be imported from react-firebase-hooks/firestore then you would use something such as const [data] = useCollectionData(query); where query could be a reference to the firebase collection you are pointing to with filtering capabilities ex. firestore.collection('your-collection').orderBy('createdAt). This is the official method of handling firebase data which you can then simply use with the .map(item => <Item data={item}>)
Check how your menu is being rendered to the View.
Since the menu is an array of data from firebase, to render it to the view, you have to use the map() js method because objects or array cannot be converted to a React Child element.
e.g;
{menu.map((item) => {
return <Text>{item.Name}</Text>
})}

How to Format React Form Submission Before API Gets Payload

I am trying to submit a form that takes form field inputs into a React hook state object. However, the payload object is accessed by other components in the application that expect the payload object to be a certain format.
const [formInput, setFormInput] = useState();
const onChange = event => {
event.target.name = event.target.value;
}
return (
<form onSubmit="props.onSubmit">
<label>First Name</label>
<input value="formInput.first-name" name="first-name" onChange={onChange}></input>
<input value="formInput.person-dept" name="person-dept" onChange={onChange}></input>
<button type="submit">Add a cadet</button>
</form>
)
So, the formInput object has two properties. But they need to be nested, like this:
//acceptable payload:
{cadet: [
first-name: '',
dept: ''
]
}
I have tried calling a function for wrapping them using a new state object, but it gives me an undefined error for the schema property:
const schema = () => {
cadet: [
first-name: '',
dept: ''
]
}
const [formattedInput, setFormattedInput] = useState(schema);
const updateInput = () => {
setFormattedInput(
cadet: {
{first-name: {formInput.first-name} || null},
{dept: {formInput.person-dept} || null}
}
)
}
updateInput();
api.post('~/person/cadet', updateInput);
In the above example, the properties from schema are undefined, cadet and first-name.
Also, in order to set the setFormattedInput object before calling the API I need to instantiate the function that has it, but because of React rules, calling updateInput(); runs when the component is rendered and is undefined (sort of like needing a componentDidUpdate() for a functional component).
This should be very common- we all need to reformat our form state objects before they reach the API unless you are building an application from scratch. Does anybody know how?
To give some context, the NPM package mapper does what is needed, but it simply doesn't work (https://www.npmjs.com/package/mapper).
I ended up using a custom wrapper and fixing minor syntax issues along the way. In the payload,
api.post('~/person/cadet', formInput);
I added
const formattedObj = formatted(formInput);
api.post('~/person/cadet', formattedObj);
console.log(formattedObj);
Then in the same file I defined formatted like this:
const formatted = (formInput) => {
const payload = {cadet: [
first-name: formInput.first-name,
dept: formInput.person-dept
]}
return payload;
}
Therefore, formatted() gets passed in the state object and payload is behaving sort of like a block variable and doesn't need to be called for it to be the data being returned by formatted().
Just a minor step of wrapping the object passed to the URL path and some syntax in formatted() and the basic workflow was correct.
I wanted to share because it took several powerful coworker minds to make this work and it should be taught with React hooks everywhere to lower the barriers to entry.
Thanks for reading and I hope this works for you, too!
P.S.- If the object isn't being saved, it's due to other syntax issues in what you are passing into the object from the form fields. Also, minor syntax tweaks will get this solution to work if something is a tiny bit off.

Delete an object inside of array with firestore

I want to query the specific object by the property "id" inside the array and remove/delete the whole object. Is this possible through firestore?
My firestore structure looks like this:
For now I have function called removeCard() that takes in the docId which is the document id in firestore, and the cardId which is property "id" value inside the object.
removeCard() function:
export const removeCard = (retroId, cardId) => {
return (dispatch, getState, { getFirestore }) => {
// make async call to database
const firestore = getFirestore();
firestore.collection('retros').doc(retroId).update({
// firestore query to remove/delete this card.
}).then(() => {
dispatch({ type: 'DELETE_CARD_SUCCESS' });
}).catch(err => {
dispatch({ type: 'DELETE_CARD_ERROR' }, err);
});
}
};
But I cant figure out how to do this query or if it's even possible to do with Firestore. Any help is appreciated. Just tell me if i should provide with any more information to make my question more clear.
Actually, at the time of writing, it is not possible, with array-contains, to query for a specific property of an object stored in an array.
A possible workaround exists, which is to query for the entire object.
In your case it would be like
db.collection('retros').where("cardsAction", "array-contains", {id: "65....", content: "this is...", time: ....})
but obviously this workaround will not be useful in your case as both the time and content values may vary.
So in other words, you will probably need to change your data model.
One possible approach would be to duplicate your data (quite common in NoSQL world) and have another array with only the ids of the cardAction, like:
cardActionIds = ["65....", "5789", ....]
This way you can use array-contains and once you have found the docs corresponding to the cardActionIds you manipulate the other array (i.e. cardAction)

Loading static constants in React+Redux

I'm using server side rendering for my React-Redux application. And I want at application startup to load some constants, for example list of cities with corresponding IDs:
[
{
name: "London",
id: 1
}
...
]
I think it's better to put this data into store on server side and provide it to client using window.__INITIAL_STATE__ as suggested here http://redux.js.org/docs/recipes/ServerRendering.html
This constants will be read-only, and I want to preload them just for data normalization purposes. For example later I can just retrieve list of users with city IDs, instead of users with city names: {name: "Alex", cities: [1,2]}
The problem is that if I put them into store, then I forced to create reducer for this constants, otherwise I'm getting this error:
Unexpected key "cities" found in preloadedState argument passed to
createStore. Expected to find one of the known reducer keys instead:
"colors". Unexpected keys will be ignored.
So I'm searching for some elegant way to handle this situation.
For now I have 2 ideas how to handle it:
Create empty reducer which always will return default state
export const cities = (state = [], action={ type: null }) => {
return state
}
Send from server initial actions with payloads, and execute them on client at startup:
// server.js
window.INITIAL_ACTIONS = [
{ type: "REQUEST_LOGIN_SUCCESS", user: {userId, userName, userEmail, etc} },
{ type: "REQUEST_CITIES_SUCCESS", [..listOfCities] },
]
And in my client-index.js, dispatch those actions right after creating the store:
//client-index.js
window.INITIAL_ACTIONS.forEach(store.dispatch)
So, is one of my approaches is good? Or may be you know some other, more elegant solution?
Thanks.
We do something similar with a dummy "settings" reducer. i.e.
const rootReducer = combineReducers({
...
settings: (state = {}) => state,
...
});
This gives us a convenient place to store all our app config.
Just make sure you key your initial state in the same manner. i.e.
window.__INITIAL_STATE__ = {
...
settings: { ... },
...
};
Some may object to this practise, but I think it's sound. Though settings may be constant, it is nonetheless state. It conforms to the redux practice of a single state object. (Besides, there may come a future point where the settings state slice will be dynamic and require a "real" reducer.)

Resources