LitElement with data from Firestore - google-cloud-firestore

I've been trying to dynamically insert data from Firestore into my component.
Currently, I'm using the firstUpdated() lifecycle. My code works but it fell like there's a better way of doing this.
This is my current component.
static get properties() {
return {
firebaseData: {type:Object},
}
}
constructor() {
super()
this.firebaseData = {}
}
firstUpdated() {
firestore.doc(`...`).get()
.then(doc => {this.firebaseData = doc.data()})
})
.catch(err => console.error(err))
}
render() {
return html `${firebaseData.title}`
}
I was hope someone with more experience would be open to sharing their knowledge. Thanks in advance!

firstUpdated should be used when you need to interact with shadow DOM elements inside your web component, as they aren't created until then. It's the earliest moment when you can be sure your component DOM exists.
I would prefer to do the firebase call earlier, even in the constructor.
The idea is, your firebase call isn't dependent of the rendering, so you could directly do it at the earliest moment, and as in the callback of the function you update the firebaseData property, a new rendering cycle will be done then.

Related

How to get data from an API only once (on app creation, outside component or view) in Vue3 SPA, with Pinia store

Is it possible and is it a good practice to avoid fetching data from an API every time the router view is loaded or the component is Mounted?
The thing is that some data rarely changes (like a dropdown options list, imagine allowed animal picks for my app) and it's logical not to send a request every time to a server, instead on app creation would be sufficient.
Tried in App.vue, is that a common thing?
IN APP.vue
import { computed, onMounted, onUpdated, ref } from 'vue';
onMounted(()=>{
axios.get('/data')....
.then((res)=>{
store.property = res.data
...
})
})
I think having it on mount in the App.vue component is acceptable since the App component would not be remounted.
The ideal setup, however, depends on some other parameters like size of application and size of team that's maintaining it. In a large applications you might want to organize things in amore structured and consistent way so you and other folks working on the code know where to find things.
You could consider moving the API call into the pinia action.
store.loadMyData()
// instead of
axios.get('/data')
.then((res)=>{
store.property = res.data;
})
That way you have fewer lines of code in the component. Having "lean" components and moving "business logic" out of components usually makes for better organization which makes it easier to maintain.
Within the action, you can track the state of the API
const STATES = {
INIT: 0,
DONE: 1,
WIP: 2,
ERROR: 3
}
export const useMyApiStore = defineStore('myapi', {
state: () => ({
faves: [],
favesState: STATES.INIT
}),
actions: {
loadMyData() {
this.store.favesState = STATES.WIP;
axios.get('/data')
.then((res) => {
this.store.property = res.data;
this.store.favesState = STATES.DONE;
})
.catch((e) => {
this.store.favesState = STATES.ERROR;
})
},
},
getters: {
isLoaded(){
return this.store.favesState === STATES.DONE;
}
isLoading(){
return this.store.favesState === STATES.WIP;
}
}
})
This is, obviously, more verbose, but allows for the components to be smaller and contain less logic. Then, for example, in your component you can use the getter isLoading to display a loading indicator, or use isLoaded inside another component to determine whether to show it.
Yes, this is a oft used way to load some data into the Vue App.
You could also load data before the Mounting in beforeMount() or created() Lifecycle Hooks (see Vue Lifecycle Diagram) to prevent unnecessary HTML updates.

Events provider is deprecating. Using Redux or Observables for state in ionic apps

I've been using events in my ionic application, where i subscribe in one page, and publish the event in the other page. Now I see a warning that Events are going to be changed with Observables and Redux state and effect.
I was using Events mainly to call for component function changes outside it, so I had a components for example:
Component1.ts
this.events.subscribe('event:addValue1', (data: any) => {
this.valueName = 'VALUE1';
});
this.events.subscribe('event:addValue2', (data: any) => {
this.valueName = 'VALUE2';
});
and than outside this component I was calling the publish methods from any page, like:
Page1.ts
this.events.publish('event:addValue1');
Page2.ts
this.events.publish('event:addValue2');
By this i was able to change the data (this.valueName) outside the Component1.ts from any other page, simply by publishing the desired event.
I know that this might not sound or be right approach, but It was the only way I was doing changes to my Component1.ts outside it from any page.
I have now changed this and just put separate functions and than i access them via ViewChild component name like
#ViewChild('component') component: any;
....
this.component.functionAddValue1().
and additionally I send additional params via Angular NavigationExtras if i need to calculate and call some function from the Component1.ts, lets say if I navigate to some route.
Before this I was just calling the events.publish and I was able to make the changes to the Component1.ts on the fly.
Create event service.
In the EventService.ts:
export class EventService {
private dataObserved = new BehaviorSubject<any>('');
currentEvent = this.dataObserved.asObservable();
constructo(){}
publish(param):void {
this.dataObserved.next(param);
}
}
For publishing the event from example page1:
constructor(public eventService:EventService){}
updatePost(value){
this.eventService.publish({name:'post:updated',params:value});
}
In page 2:
constructor(public eventService:EventService){
eventService.currentEvent.subscribe(value=>{
if(value.name=='post:updated'){
//get value.name
}else if(value.name=='another:event'){
//get value or update view or trigger function or method...
}
// here you can get the value or do whatever you want
});
}

Vue/Vuex/GSAP-Animation: Add DOM elements to store

In a Vue project, I am looking for a way to save DOM elements to the store. Those elements shall then be animated with GSAP.
Unfortunately, there is a bit of a problem with when the DOM is ready (so I can use document.querySelector) and when Vue's transition system is firing.
the store has about this structure:
const store = new Vuex.Store({
state: {
settings: {
ui: {
btns: {
cBtnNavMain: {
el: document.querySelector('.c-btn__nav--main') // does not work, because DOM is not yet ready
[...]
}
}
}
}
},
mutations: {
// Add DOM element later via a mutation?
addDomElementToStore: (state, obj) => {
console.log("MUTATION addDomElementToStore, obj =", obj) // shows `null`
obj.el = obj.domEl
}
}
}
Then, in App.vue (loaded by main.js) there is basically this script:
created() {
console.log("App.vue created")
}
mounted() {
console.log("App.vue created")
}
methods: {
beforeEnter(el) {
console.log("BeforeEnter called, obj")
// This could be the place to add DOM to the store, but how?
// I tried a mutations like this (see above in store.mutations):
this.$store.commit('addDomElementToSettings', {
el: this.$store.state.settings.ui.btns.cNavMainBtn.el,
domEl: document.querySelector('.c-btn__nav--main')
})
// ...but won't work though, the console shows, obj params are empty
}
[...]
}
The console result shows something like this:
App.vue created
BeforeEnter called // <-- beginEnter before mounted called!
MUTATION addDomElementToStore, obj = {el: null, domElem: null}
App.vue mounted
Since "beforeEnter" is called after created, but BEFORE mounted (which is, where the DOM would be easily accessible), the DOM is not really accessible yet, it seems. So I thought, I use "beforeEnter" to assign new DOM elements to store.settings using a mutation. But that doesn't work at all - and GSAP eventually has do DOM elements to animate on.
What do I have to do to get my DOM elements saves to the settings, so that I do not have to use document.querySelector all the time, when I want to address a DOM element with GSAP?
Why don't try you using the mounted() lifecycle method to fire the mutation that stores DOM element selections in vuex state? That way you know something exists to select.

How do I make a React Subscription function reactive?

I'm trying to implement Pagination for my Meteor App using React and mongo. I've done this by passing a limit prop to my subscription function like so:
export default class BookListTable extends TrackerReact(React.Component) {
constructor(props) {
super(props);
var limit = this.props.LimitProp
limit = parseInt(limit) || 5;
this.state = {
subscription: {
booksData: Meteor.subscribe("allBooks", {limit: limit})
}
}
///// rest of component
This works great the first time the react component renders but when I update the props nothing changes. I expect the component to re-render with the updated limit property - however this doesn't happen. What am I missing?
Any related info around pagination appreciated!
When you update the property LimitProp, the component re-renders with LimitProp changed but the constructor is not invoked again. You only copy the value of the LimitProp to limit and then use it when the component is created, so the state (suscription) is not updated when it changes. I think that you should use componentDidMount.
Component Specs and Lifecycle

Is it fine to mutate attributes of React-controlled DOM elements directly?

I'd like to use headroom.js with React. Headroom.js docs say:
At it's most basic headroom.js simply adds and removes CSS classes from an element in response to a scroll event.
Would it be fine to use it directly with elements controlled by React? I know that React fails badly when the DOM structure is mutated, but modifying just attributes should be fine. Is this really so? Could you show me some place in official documentation saying that it's recommended or not?
Side note: I know about react-headroom, but I'd like to use the original headroom.js instead.
EDIT: I just tried it, and it seems to work. I still don't know if it will be a good idea on the long run.
If React tries to reconcile any of the attributes you change, things will break. Here's an example:
class Application extends React.Component {
constructor() {
super();
this.state = {
classes: ["blue", "bold"]
}
}
componentDidMount() {
setTimeout(() => {
console.log("modifying state");
this.setState({
classes: this.state.classes.concat(["big"])
});
}, 2000)
}
render() {
return (
<div id="test" className={this.state.classes.join(" ")}>Hello!</div>
)
}
}
ReactDOM.render(<Application />, document.getElementById("app"), () => {
setTimeout(() => {
console.log("Adding a class manually");
const el = document.getElementById("test");
if (el.classList)
el.classList.add("grayBg");
else
el.className += ' grayBg';
}, 1000)
});
And here's the demo: https://jsbin.com/fadubo/edit?js,output
We start off with a component that has the classes blue and bold based on its state. After a second, we add the grayBg class without using React. After another second, the component sets its state so that the component has the classes blue, bold, and big, and the grayBg class is lost.
Since the DOM reconciliation strategy is a black box, it's difficult to say, "Okay, my use case will work as long as React doesn't define any classes." For example, React might decide it's better to use innerHTML to apply a large list of changes rather than setting attributes individually.
In general, if you need to do manual DOM manipulation of a React component, the best strategy is to wrap the manual operation or plugin in its own component that it can 100% control. See this post on Wrapping DOM Libs for one such example.