Updating view based on API response - react-native

I'm having an issue with showing information in my App.js state object in a child component. Below are the relevant parts. App.js, WeatherDetail is the aforementioned component, and then the response from API request.
App.js
import React from "react";
import openweather from "../api/openweather";
import SearchBar from "./SearchBar";
import WeatherDetail from "./WeatherDetail";
class App extends React.Component {
state = { weather: [], daily: [] };
onSearchSubmit = async zip => {
const currentWeather = openweather.get("/data/2.5/weather", {
params: { zip }
});
const fiveDayForecast = openweather.get("/data/2.5/forecast", {
params: { zip }
});
const [weather, daily] = await Promise.all([
currentWeather,
fiveDayForecast
]);
this.setState({ weather: weather.data, daily: daily.data.list });
};
render() {
return (
<div>
<SearchBar onSubmit={this.onSearchSubmit} />
<WeatherDetail weather={this.state.weather} />
</div>
);
}
}
export default App;
WeatherDetail.js
const WeatherDetail = ({ weather }) => {
return (
<div>
<h1>Today</h1>
<h3>City: {weather.name}</h3>
<h5>Temperature:</h5>
</div>
);
};
>forecast:
base: "stations"
>clouds: {all: 75}
cod: 200
>coord: {lon: -82.54, lat: 40.7}
dt: 1553023267
id: 420031370
>main:
humidity: 45
pressure: 1030
temp: 44.22
temp_max: 46.99
temp_min: 41
name: "Elyria"
Now, weather.name shows up in the view with no problem. If I try to grab any information deeper than that I get an error saying the property is undefined. For example, weather.main.temp is how I thought I would get the temperature but it shows that error. weather.main alone gives an obvious error, but it shows the object I'm trying to access in the message. So I'm confused about how that can be when weather.main.temp also says the property (main) is undefined. Am I trying to access the object incorrectly or is something else set up wrong?

The problem comes with the initial render, before you have received results from the API. The first time WeatherDetail renders, it has the default state, which you've set to state = { weather: [], daily: [] };. This means that on initial render there is no main attribute existing on state.weather. It's just an empty array. The attributes only exist when you populate them on running onSearchSubmit.
I would add some validation in your WeatherDetail component. Something like the below:
const WeatherDetail = ({ weather }) => {
const { name, main } = weather;
return (
<div>
<h1>Today</h1>
<h3>City: { name ? name : ''}</h3>
<h5>Temperature: { main ? main.temp : '' }</h5>
</div>
);
};
That way on initial render if the attributes don't exist you can still render empty strings, and when the state gets populated and the correct attributes exist, it will render the correct content.

Related

How to use highlight.js in a VueJS app with mixed content

I'm currently using highlight.js to hightlight the code in the HTML content being received from my backend. An example of something I might receive from the backend is:
<h3> Check this example of Javascript </h3>
<pre>
<code class="language-javascript">let x = 0;
function hello() {}
</code>
</pre>
As you can see it is a mixed content of HTML and code examples wrapped in pre -> code tags.
I have a component to render WYSIWYG content returned from the backend. In this component, I use highlight.js to highlight the code blocks.
import { defineComponent, h, nextTick, onMounted, ref, watch } from 'vue';
// No need to use a third-party component to highlight code
// since the `#tiptap/extension-code-block-lowlight` library has highlight as a dependency
import highlight from 'highlight.js'
import { QNoSsr } from 'quasar';
export const WYSIWYG = defineComponent({
name: 'WYSIWYG',
props: {
content: { type: String, required: true },
},
setup(props) {
const root = ref<HTMLElement>(null);
const hightlightCodes = async () => {
if (process.env.CLIENT) {
await nextTick();
root.value?.querySelectorAll('pre code').forEach((el: HTMLElement) => {
highlight.highlightElement(el as HTMLElement);
});
}
}
onMounted(hightlightCodes);
watch(() => props.content, hightlightCodes);
return function render() {
return h(QNoSsr, {
placeholder: 'Loading...',
}, () => h('div', {
class: 'WYSIWYG',
ref: root,
innerHTML: props.content
}));
};
},
});
Whenever I visit the page by clicking on a link the page works just fine, but when I hard refresh the page I get the following error:
`line` must be greater than 0 (lines start at line 1)
Currently, I'm not sure precisely why this happens, and tried a couple of different approaches
Aproach 1: try to build the whole content and then replace
const computedHtml = computed(() => {
if (import.meta.env.SSR) return '';
console.log(props.content);
const { value } = highlight.highlightAuto(props.content);
console.log(value);
return '';
})
With this approach, I get the same error as before
`line` must be greater than 0 (lines start at line 1)
I have checked out this error in https://github.com/withastro/astro/issues/3447 and https://github.com/vitejs/vite/issues/11037 but it looks like that this error is more related to Vite than my application - please, correct me if I'm wrong here.
Is there a way for me to highlight the code in the backend that is being returned from the backend in Vue?

vue 3 composition api, passing data and making it reactive

In my component I have a simple select menu with two options ("all", and "Investment"). The idea here is to get an array of data from a composable, and display on screen each row of this data. If I select "all" in the menu it displays all rows, if I select "Investment" it will filter the data and display only those with obj.link == "usa".
Once I fetch the data and bring it into my component, if I console.log the data, it works fine. If I console.log the data after i filter it, I get an empty array.
I have then tried to hard code the data in my component and test the filter function, and it works fine. So the error comes from how I am getting my data and how I try to use it. I have tried to use different hooks such as onMounted, but was unsuccessfull.
Here is a minimalistic sample of my code.
Any suggestion or advice is more than welcome
The composable that fetches the data from my database looks like this:
import {ref} from 'vue'
import { projectFirestore } from '../firebase/config'
import { collection, getDocs } from "firebase/firestore";
const getActorDocs = () => {
const actorDocs = []
const error = ref(null)
const loadActors = async () => {
try {
const querySnapshot = await getDocs(collection(projectFirestore, "actors"));
querySnapshot.docs.map(doc => {
actorDocs.push(doc.data())
})
} catch (err) {
error.value = err.message
console.log(error.value)
}
}
return { actorDocs, error, loadActors}
}
export default getActorDocs
My component:
<template>
<div class="col-2">
<span class="lbl">MA</span>
<select v-model="selectedMA" class="form-select" >
<option value="all">all</option>
<option value="Investment">Investment</option>
</select>
</div>
<p v-for="obj in actorListTest2" :key="obj" :value="obj"> {{obj}} </p>
<template/>
<script >
import {onMounted, onBeforeMount, ref} from 'vue'
import getActorDocs from '../../composables/getActorDocs'
export default {
setup(){
const selectedMA = ref("Investment")
const error = ref(null)
const {actorDocs, loadActors} = getActorDocs()
var actorListTest1 = actorDocs
const actorListTest2 = ref([])
loadActors() // loads actors array into actorDocs
actorListTest2.value = actorListTest1
console.log(actorListTest1) // <----- prints correctly (see image below)
if(selectedMA.value === "all"){
actorListTest2.value = actorListTest1
}else{
actorListTest2.value = actorListTest1.filter(obj => {
return obj.link == selectedMA.value
})
}
console.log(actorListTest2.value) // <----- prints undefined !
return { error, selectedMA, actorListTest2}
}//setup
}
</script>
This is the output of console.log(actorListTest1):
Then this is the output of console.log(actorListTest2) after filtering :
This is a known problem with console.log, it shouldn't be used to debug object values in real time.
actorDocs is not reactive and won't work correctly with asynchronous operations in Vue. Side effects are supposed to be done in lifecycle hooks, e.g.: mounted.
In current state getActorDocs isn't ready to be used with composition API because it's limited to follow promise control flow in order to avoid this race condition:
onMounted(async () => {
await loadActors();
console.log(actorListTest2.value);
});
A correct way to avoid this is to make actorDocs reactive array or a ref:
const actorDocs = reactive([]);
In case there's a need to access filtered value in side effect, e.g. console.log, this is done in a watcher
const actorListTest2 = computed(() => actorDocs.filter(...));
watch(actorListTest2, v => console.log(v));
onMounted(() => {
loadActors();
});

Getting 'undefined is not an object' error when trying to use MST in react native Ignite template

I am using Ignite template for react native. I have created a simple object model and store that looks like so:
export const TimeObject = types.model('TimeObject', {
id: types.identifier,
hour: types.string,
minutes: types.string,
amOrPm: types.enumeration(['AM', 'PM']),
timeStamp: types.number,
numOfDrugs: types.number,
});
export const TimeStore = types
.model('TimeStore', {
time: types.map(TimeObject),
})
.actions(self => ({
addTime(json) {
const ids = [];
json.forEach(timeJson => {
if (!timeJson.id) {
timeJson.id = uuid.v1();
}
ids.push(timeJson.id);
self.time.put(timeJson);
});
return ids;
},
}));
When I use this in a screen component:
const { timeStore } = useStores();
const timeId = timeStore.addTime([
{
hours,
mins,
amOrPm,
timeStamp: timeStamp.getTime(), //With correct values given for inputs
},
]);
I get the undefined error. I am not sure what I'm doing wrong. I am testing this on Storybook, is there a different procedure to import it there?
I was able to solve this by just changing the order of ToggleStorybook and RootStoreProvider in app.tsx so that it now looks like this:
<RootStoreProvider value={rootStore}>
<ToggleStorybook>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</SafeAreaProvider>
</ToggleStorybook>
</RootStoreProvider>
I think because Storybook was toggled it never went to the RootStore and was thus unable to access it inside Storybook components but this method works now.

Vue getter property undefined

i have state pages obtained from api
const state = () => ({
info: [],
pages: []
})
and Getter
const getters = {
// HomePage_Getters
HomeAdvantages(state) {
return state.pages.find((page) => (page.template = 'home')).home_advantages
}
}
On the vue Page
<div>
{{ HomeAdvantages }}
</div>
...
computed: {
...mapGetters({ HomeAdvantages: 'modules/general/HomeAdvantages' })
},
On Page refresh i get
Cannot read property 'home_advantages' of undefined
But in the state pages property is present
How i could solve this?
I will be grateful, for help
If pages property is empty, then state.pages.find((page) => (page.template = 'home')) may return undefined.
And in your code you want to get home_advantages from the find result.
Your code should look like this:
var page = state.pages.find((page) => (page.template == 'home'));
if (!page) {
return null;
}
return page.home_advantages

Vuex Getters Come Back as Undefined

I'm having a Vuex getters issue where the gitters return as undefined (in the Vue Dev Console and no errors are logged in the Chrome Dev Console).
If mapGetters() is commented out (like the example code below), the returned data is displayed on screen -> Providing if the user clicks into the link that has the data. The data will NOT display if the user enters the app directly at the point where the data should display.
There is a similar question but there is no accepted answer
Vue Console Logs:
STATE:
$_lgdHRS:Object
totHrs:129
GETTERS:
$_lgdHRS/totHrs:undefined
SomeContainer.vue
<script>
import store from '../../_store'
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState('$_lgdHRS',{
totHrs : 'totHrs',
}),
// ...mapGetters('$_lgdHRS',{
// totHrs : 'totHrs',
// airHrs : 'airHrs',
// picHrs : 'picHrs',
// pmcHrs : 'pmcHrs',
// voHrs : 'voHrs',
// trngHrs : 'trngHrs'
// }),
},
created() {
this.storeKey = '$_lgdHRS';
if (!(this.storeKey in this.$store._modules.root._children)) {
this.$store.registerModule(this.storeKey, store);
}
},
mounted() {
this.$store.dispatch('$_lgdHRS/getLogSummary');
},
}
</script>
<template>
<total-summary :hours="totHrs" />
</template>
state.js
export const state = {
totHrs: Number,
}
getters.js
const totHrs = state => state.totHrs;
export default {
totHrs,
};
mutations.js
const
TOTAL_HRS_UPDATED = (state, totHrs) => {
state.totHrs = +totHrs;
};
export default {
TOTAL_HRS_UPDATED,
};
Most probably because you have just displatched the request in mounted and before the data is set into the state variable your component is displayed.
Hence you can trying using async await in mounted as well as in store actions.
Do refer the following link and check the last example in this.
https://vuex.vuejs.org/guide/actions.html
The problem was that I was nesting my variables as I usually would in other frameworks.
Example:
// NESTED VARS
const r = response
totHrs = r.total,
airHrs = r.airborne,
picHrs = r.PIC,
pmcHrs = r.PMC,
voHrs = r.VO,
trngHrs = r.training;
// CHANGE TO:
const r = response
const totHrs = r.total
const airHrs = r.airborne
const picHrs = r.PIC
const pmcHrs = r.PMC
const voHrs = r.VO
const trngHrs = r.training
I don't know enough to why but your input would be greatly appreciated in the comments.