I have a Vue component with lots of properties (> 40). It is a form for editing some business entity. The flow is the following:
On mounted() load the data as json from server and initialize component properties
Edit the data as required
Put all the properties into json structure and send back to server to update data
The properties in my component are named exactly the same as in json structure. I want to iterate through properties in my component and create json structure with 1 line of code instead of doing something like this:
var data = {
field1 = this.field1,
field2 = this.field2,
field3 = this.field3
...
field40 = this.field40
}
I use TS and vue-class-component, so the component code looks like this:
import Vue from 'vue'
import Component from 'vue-class-component'
#Component({
template: ...
})
export default class MyComponent extends Vue {
field1: Number = null;
field2: Date = null;
field3: String = null;
...
field40: Number = null;
mounted() {
axios.get(..., response => {
this.field1 = response.data.field1
this.field2 = response.data.field2
this.field3 = response.data.field3
...
this.field40 = response.data.field40
}
}
submit() {
const data = {
field1 = this.field1,
field2 = this.field2,
field3 = this.field3,
...
field40 = this.field40,
};
axios.put(..., data);
}
}
You could wrap your fields in a model field in your data:
data:{ model:{}}
Then on mounted you could set reactive props in your model
mounted(){
for(let field in YOUR_JSON_OBJ){
Vue.set(this.model, field , YOUR_JSON_OBJ[field])
}
}
Then when you need to submit form, just pass your vue model prop
YOUR_SUBMIT_METHOD(JSON.stringify(this.model))
I can see two ways to do this.
Provide a list of properties in an array, if you know it in advance (this is better practice as the data property gets initiated in the right way):
const props=['field1','field2', ...];
export default {
async mounted(){
//load the data and ensure only the expected properties are mounted
//this avoids any unexpected behaviour.
const result = await loadMyData();
for(let prop of props) this[prop]=result[prop];
}
data(){
//instantiate the data object to ensure all the properties are reactive
const data={};
for(let prop of props) data[prop]=null;
return data;
}
methods:{
async updateServer(){
//build the data object to send back to the server then send it.
const data={};
for(let prop of props) data[prop]=this[prop];
await sendUpdate(data);
}
}
}
The second way is to store a list of properties when you load the data from the server using Object.keys() and store this as a data property. You will then need to use vm.$set to ensure all of the properties have the correct reactivity, and you will not be able to have the properties at the root level, instead you will need to nest them (see the vue docs). However I assume that if your view object is designed to react to these properties then you must know them in advance.
Related
I am building a FlashCard website using NextJs and firebase. I have a homepage which I want to render server side and so I am using getServerSideProps. InsidegetServerSideProps function, I am fetching all the documents of the current user from firestore and is stored in an array and is returned as props as below:
export const getServerSideProps = async(ctx: GetServerSidePropsContext) {
let notes: DocumentData[];
// fetch documents here and populate notes array like so [doc,doc,doc,..]
// data() method works here and returns document fields
console.log(notes[0].data());
// NextJs throws error "`object` ("[object Object]") cannot be serialized as JSON. Please only return JSON serializable data types.", so I have to JSON.stringify() the notes
return {
props: {
notes: JSON.stringify(notes),
}
}
}
Below, I have my homepage where I parse the JSON string and have access to the notes, but now the data() method on the document does not exist/works and throws method does not exist errors. If I have to access the document fields, I have to use the dot operator on every property of the document till I reach the fields property which is nested deep down in the object as follows:
export default function Home({ notes }) {
let docs = JSON.parse(notes); // can access notes
// data() method throws function does not exist error
console.log(docs[0].data());
// I am only able to access document fields as below
console.log(docs[0]._document.data.value.mapValue.fields);
return (
<Layout>
<HomeContent notes={docs}/>
</Layout>
);
}
I have searched everywhere and found nothing that helped me why data() method won't work. If I directly fetch the documents inside the page component on client side, the data() method on the document returns its' fields. I don't know how using JSON serializations affect it. I would always prefer to use data() method to access fields than to go that deep plus I am planning to fetch data on server on other pages as well.
I would really appreciate if you can shed some light on it. It took all of my days time.
EDIT: The code that gets notes from firestore:
// inside getServerSideProps
let notes: DocumentData[] = null;
const getNotes = async(ref: DocumentReference < DocumentData > , uid: string) => {
let tempNotes = [];
const categoriesSnapshot = await getDoc < DocumentData > (ref);
const categoriesObject = categoriesSnapshot.data();
// return if user doesn't have any documents
if (!categoriesObject) {
return;
}
const promises: [] = categoriesObject.categories.map((category: string) => {
const userCategoriesRef = collection(database, 'CategoryCollection', uid, category);
return getDocs(userCategoriesRef); // returns a promise
});
const allQuerySnapshots = await Promise.all < DocumentData > (promises);
allQuerySnapshots.forEach((querySnapshot) => {
tempNotes.push(...querySnapshot.docs);
});
notes = tempNotes;
}
const categoryDocRef = doc(database, "CategoryCollection", uid);
await getNotes(categoryDocRef, uid);
So I have a database that I can query using ExpressJS and NodeJS. The Database is a MySQL db. and the data within looks like this:
id: 1
username: 'someUsername'
email: 'randomEmail#email.email'
I want to somehow put the data from within the database into a JSON list and then map over it and show it to the user. Another option to achieve this, I reasoned, would be to populate the state of the app. I've thought of creating some class component, adding a mapStateToProps and assign the data returned from the queries to the state and then use the data from the state in the Reactapp itself. I am not so sure if that would be effective.
This is the minimum code example for a component that fetches data from your backend onLoad, and displaying the data using .map, without using redux (mapstatetoprops)
const DisplayData = () => {
const [ data, setData ] = useState([]);
const fetchData = async () => {
const results = await fetch(url).then(res => res.json());
setData(data)
}
useEffect(() => {
fetchData()
},[])
return ( <div>
{ data.map(item => <p>{item.name}</p> }
<pre>
{ JSON.stringify(null, data, 4) }
</pre>
</div>
}
Well, the return data that you get from the SQL query is itself an array of objects,
Your answer lies in simply iterating over the returned data and assigning it to whatever object you like.
let queryResults = returnedQueryData // data returned from SQL query
let jsonarray = {}
for (row in queryResults) {
jsonarray[row.id] = {
id: row['id'],
username: row['username'],
email: row['email']
}
To access data from the JSON array use
Object.keys(jsonarray).forEach(e => {
// Here e is the object element from the JSON array
}
I want to change all the static text in my app with a text from a server so I have to fetch this text in LoadingScreen and then export it to use it in other screens
<Text>My products</Text> to <Text>{constants.myProduct}</Text> after import constant from LoadingScreen
How to export the json response from LoadingScreen to all the other Screen ?
there is another approach ?
PS. I don't use Redux
The simplest way, but not the best, is just create and export variable with all text keys. For example:
// text.utils.js
let _textData;
export const loadTextData = () => fetch(YOUR_SERVER)
.then((r) => r.json())
.then(r => _textData = r);
export const getTextData = (key, defaultValue) => _textData[key] || defaultValue;
// init inside App or something else
import {loadTextData} from 'text.utils.js';
componentDidMount() {
loadTextData();
}
// inside components
import {getTextData} from 'text.utils.js';
const myProductText = getTextData('myProduct', 'This is product text');
const myProductCountText = getTextData('myProductCount', 'This is product count text');
<Text>{myProductText}</Text>
<Text>{myProductCountText}</Text>
P.S. It's not a good solution for react. The best choice is create a context or hook, that will provide strings to your components. Also if your string object has nested objects, you may reorganise getTextData function
I want to access json with a value in input.
My function and json
import pet3 from '../../utils/pet3' //my json file
const getValueFromJson = (value) => {
const data = pet3
console.log(data.components) //it works fine
console.log(data.value) // it is undefined
}
getValueFromJson("components")
You can access the object keys dynamically by using the square brackets:
import pet3 from "../../utils/pet3"; //my json file
const getValueFromJson = (value) => {
const data = pet3;
console.log(data[value]);
};
getValueFromJson("components");
Edit:
Alternately, you can install and use a 3rd party library like lodash which provides the _.get() method that can be used like this:
import get from "lodash/get"; // if not installed, run `npm install lodash --save-dev`
const getValueFromJson = (value) => {
const data = pet3;
console.log(get(data, value, "default value")); /* returns "default value" if key is undefined. */
};
getValueFromJson("components.filename");
I'm trying to display game information from the Steam API in a React Native Flatlist. I'm new to React and JSX, so a lot of what I'm reading doesn't make sense.
I want the Flatlist to display a list of game titles owned by a particular account. The data returned from Steam's API call (via fetch) looks like this:
{
"response": {
"game_count": 69,
"games": [
{
"appid": 220,
"name": "Half-Life 2",
"playtime_forever": 24,
"img_icon_url": "fcfb366051782b8ebf2aa297f3b746395858cb62",
"img_logo_url": "e4ad9cf1b7dc8475c1118625daf9abd4bdcbcad0",
"has_community_visible_stats": true,
"playtime_windows_forever": 0,
"playtime_mac_forever": 0,
"playtime_linux_forever": 0
},
{
"appid": 320,
"name": "Half-Life 2: Deathmatch",
"playtime_forever": 0,
"img_icon_url": "795e85364189511f4990861b578084deef086cb1",
"img_logo_url": "6dd9f66771300f2252d411e50739a1ceae9e5b30",
"has_community_visible_stats": true,
"playtime_windows_forever": 0,
"playtime_mac_forever": 0,
"playtime_linux_forever": 0
},
and so on. Since I'm trying to display a list of games by name, the name attribute is the only one I need.
The data lists each game as an anonymous object, so I can't access the properties within each game using dot notation like I normally would. I tried using a for loop to iterate through them, but that doesn't work either. From my research, it seems like people normally use an Array.map for this kind of thing, but I'm unclear if that can be used with Objects.
Another problem I've encountered is the Flatlist keyExtractor property. I know it's supposed to be an anonymous function that returns some unique index or property about each Flatlist item, for the purpose of making the structure more efficient and to allow it to track updates to the list. However, I have no idea how to create this function myself. I think the appid field from the JSON data would be a good candidate, but I'm not sure how to get that into the keyExtractor function.
So, to put it as a question: How would I go about displaying data from a JSON object containing anonymous sub-objects in a Flatlist, and how would I populate the keyExtractor of that list with a different data entry (the appid from that list?
Below is my starting code:
import React, {Component} from 'react';
import {FlatList, Stylesheet, Text, View} from 'react-native';
export default class App extends Component {
state = {
dataset: []
};
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("<API URL>");
const json = await response.json();
//const data = json.map((item) => item.games.name);
var key = 0;
const data = json[games][0][name];
this.setState({ dataset: data });
}
render() {
console.log(this.state.dataset);
return (
<View>
<FlatList
data={this.state.dataset}
keyExtractor={(x, i) => i} //I have no idea what this does, or if it makes sense here.
//Where do x and i come from? (I got this from a tutorial video
//and this was glossed over)
renderItem={({ item }) => //Where does item come from?
<Text>
{item}
</Text>
}
/>
</View>
);
}
}
Alright, it seems you're having a few minor problems with understanding how FlatList works. Let me break it down for you.
Let's start with the Steam API request. In your example, you're first declaring dataset as an empty array in your state, then trying to update it with the result of a network request which is the way to go. The problem is, when you do json['games'][0]['name'] you're accessing the first item (index 0) of the games array and getting its name property and then setting that name as your dataset. Although you forgot the quotes around property names, it won't work. What you need to do instead is something like this:
fetchAllGames = async () => {
const steamResponse = await fetch("<API URL>");
const json = await steamResponse.json();
// We get all the games back from Steam in the form of an array
this.setState({ games : json.games });
}
We're now correctly updating the array inside our state with the data from the games array.
Let's move on to the keyExtractor and renderItem functions. The keyExtractor function is used to tell React about a unique identifier for each of your list items. In this case, this would be the appid property of a game. React then uses this unique ID to differentiate between items and determine which ones need updating. This function provides you with two parameters, namely the actual item and its index. Using these, we can then do something like this:
keyExtractor = (item, index) => {
return item.appid.toString();
}
We're now returning the appid property as a string (which is the type React expects key to be).
The renderItem function is a bit different, React is providing you with a parameter which contains your item plus a lot of other properties. Since we're only interested in the actual item, we're destructuring it using brackets like so: { item }. This is a technique commonly used in JavaScript to "extract" properties from objects. It is normally used like this:
const testObj = {
name : "John",
surname : "Doe"
}
const { name, surname } = testObj;
This way, you can directly refer to name and surname as if they were independent variables. Another way of doing this would be:
const testObj = {
name : "John",
surname : "Doe"
}
const name = testObj.name;
const surname = testObj.surname;
I hope this cleared some of the questions you might've been asking yourself! Here's the complete working code below. You may notice I moved some inline functions to class members, this is just a performance optimization to prevent the functions from being recreated on every render, you can ignore that.
import React, { Component } from 'react';
import { FlatList, Text } from 'react-native';
export default class App extends Component {
state = {
games : []
};
componentDidMount() {
this.fetchAllGames();
}
fetchAllGames = async () => {
const steamResponse = await fetch("<API URL>");
const json = await steamResponse.json();
// We get all the games back from Steam in the form of an array
this.setState({ games : json.response.games });
}
keyExtractor = (item, index) => {
return item.appid.toString();
}
renderItem = ({item}) => {
return (
<Text>{item.name}</Text>
);
}
render() {
return (
<FlatList
data={this.state.games}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem} />
);
}
}
EDIT #1 - As pointed out by the OP, I made a typo and corrected it. I also changed the JSON object to reflect the response property.