componentWillMount() {
console.log('Component WILL MOUNT!')
axios.get('/channels').then( (res) => {
//console.log(res.data.data.playList);
let playlists = [];
res.data.data.playList.map((value, key) => playlists.push(new Audio(value.url)));
this.setState((prevState) => {
return { audioList: playlists, categories: res.data.data.playList }
}, () => console.log(this.state.audioList));
}).catch( (err) => {
console.log(err);
});
}
**I also call this in componentDidUpdate() **
The above code that I used in my ReactJS web app to retrieve data from my DB that looks something like:
{
"_id": {
"$oid": "5a2b903abcf92a362080db4f"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/a3fd5f178b7eb68b9dba4da9711f05a714efc966?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Lil Pump",
"songName": "D Rose",
"_id": {
"$oid": "5a2c5631e54ca10eb84a0053"
}
},
{
"url": "https://p.scdn.co/mp3-preview/155643656a12e570e4dda20a9a24d9da765b9ac5?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Tee Grizzley",
"songName": "From The D To The A (feat. Lil Yachty)",
"_id": {
"$oid": "5a2c5631e54ca10eb84a0054"
}
}
],
"__v": 0
}
I retrieve the url for each songs and store it inside my state this.state.audioList to make a playable list.
I access each song with an index
So, this.state.audioList[0] would be the first song.
When I try to play this music by doing
this.state.audioList[0].play(), this totally works fine.
The problem is when I try to pause it.
this.state.audioList[0].pause() does not pause the song for some reason.
I am assuming that it is because the this.state.audioList is getting updated every time and the Audio object that I am trying to pause is a new object that has nothing to do with the one currently being played.
Am I right? If so, is there a solution to this issue?
Please help!
That should work in componentWillMount although componentDidMount is preferred, see : https://reactjs.org/docs/react-component.html#componentdidmount
Quoted from this link (remote endpoint being your axios URL here) :
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
But you're almost certain that won't work if you put your axios.get() request in componentDidUpdate, because this method is called each time your component has been updated and re-rendered.
From the React Component Lifecycle document, you'll see that componentWillMount and componentDidMount both stay in the Mounting section (that is, they are called only once when the components DOM element are inserted in the DOM), whereas componentDidUpdate is in the Updating section, and therefore is called each time your component's state or props are changed (what happens when your axios.get() promise is fulfilled).
Also, as map returns an array, why not assign its returned value to your audioList ? Here is an example that you may want to try (untested though, sorry !):
componentDidMount() {
console.log('Component DID MOUNT!')
axios.get('/channels').then( (res) => {
//console.log(res.data.data.playList);
this.setState({
audioList: res.data.data.playList.map(value => new Audio(value.url)),
categories: res.data.data.playList
});
}).catch( (err) => {
console.log(err);
});
}
Hope this helps!
Related
I am building a solar system app and I want to have all of my planet's info in a JSON file for easy access. The JSON file is in the following format
Planet-info.json, in the public folder of my React app
"planets": [
{
"Name": "Mercury",
"Description": "The smallest planet in our solar system and closest to the Sun—is only slightly larger than Earth's Moon. Mercury is the fastest planet, zipping around the Sun every 88 Earth days.",
"Moons": 0,
"Habititable": "false"
},
{
"Name": "Venus",
"Description": "is hot",
"Moons": 0,
"Habititable": "false"
}
]
And I am fetching the data with the useEffect hook
const [planetData, setPlanetData] = useState();
useEffect(() => {
const fetchData = () => {
fetch("/planet-info.json").then((result) => {
setPlanetData(result);
});
};
fetchData();
console.log(`planet data is ${planetData}`);
}, []);
However when this code runs and the console.log statement runs it returns the line
planet data is
It does not say undefined, or even [Object object] it is simply blank and I am unable to troubleshoot from there.
fetchData runs asynchronously. So what's happening is
fetchData starts
console.log executes(before the json file has the chance to load)
when the fetch completes, setPlanetData(result) happens.
If you want to see the value printed out, this should do it:
useEffect(() => {
fetch("/planet-info.json").then(result => {
const json = result.json();
console.log(json);
setPlanetData(json);
});
});
I am trying to write a custom command for my app where I create a real user and use its ID in a set of tests.
Since I do test on real BE and DB and I do not stub or mock any data I need to store some values.
The use case is:
I send a request
I get a response with a unique ID
I use this ID in another request and so on...
cy.request('POST', Cypress.env('active') + 'api/players', {})
.then((response) => {
expect(response.status).to.eq(200);
playerID = response.body.id //<-- i need to store is value and use it below
});
cy.request('POST', Cypress.env('active') + 'api/kyc/processes', {
"level": "player",
"player": playerID, //<-- I need to use it here
"venue": 1
});
I have tried aliases and they do not work for this.
Also, I have tried to define variables outside of the test but it is undefined in another request.
The problem is the two cy.request() fire off simultaneously.
Using an additional .then() on the first (nesting the second) ensures sequential execution.
cy.request('POST', Cypress.env('active') + 'api/players', {})
.then((response) => {
expect(response.status).to.eq(200);
playerId = response.body.id
return playerId
})
.then(playerId => {
cy.request('POST', Cypress.env('active') + 'api/kyc/processes', {
"level": "player",
"player": playerId,
"venue": 1
});
});
I had to resort to writeFile and readFile.
It is really dumb but it works.
I dont like it though. I think it should be possible to pass data easier trough few requests.
I use NuxtJs Framework. I created an action to call endpoint and set it response's to store.
I want to call an Endpoint every 30 seconds in some of my dashboard pages. If the user navigates to pages that don't need the API call, I want to disable API call.
In my first try I use to implementing Interval in all pages I need API call data (This is work but I don't want to copy these codes in many of my components those need the API call data):
data: () => {
return {
getting: null,
};
},
computed: {
...mapActions({
myAction: "***name-of-action***",
}),
},
created() {
this.getData();
},
beforeDestroy() {
clearInterval(this.getting);
},
methods: {
getData() {
this.getting = setInterval(() => {
this.myAction()
}, 30000);
},
},
In my first try, I use a NuxtJs middleware and JavaScript Interval to dispatch the action, but when I navigate to pages that don't need the API call, it still calls the API (because JavaScript Interval needs be clear but Nuxt middleware don't have access to component lifecycle component destroy to clear the Interval)
export default (props) => {
setInterval(() => {
props.store.dispatch("***name-of-action***");
}, 3000);
};
I want to do the best practice for this problem.
I believe the less bad thing is to do a mixin which exposes 3 things:
startPolling() :
method that starts polling on the particular component
stopPolling() :
method that stops polling in the component
pollingprop() //name it as you see fit
computed property that always exposes the updated data, this data is calculated every time you make the call inside the mixin
(optional) hooks beforeRouteEnter() + beforeRouteLeave() docs
which automatically calls the this.startPolling() and the this.stopPolling()
For solving this problem I used mixin and it became a fine solution.
I create a mixin like this:
// intervalMixin.js
export default {
data: () => {
return {
getting: null,
};
},
computed: {
...mapActions({
myAction: "***name-of-action***",
}),
},
created() {
this.getData();
},
beforeDestroy() {
clearInterval(this.getting);
},
methods: {
getData() {
this.getting = setInterval(() => {
this.myAction()
}, 30000);
},
},
}
So I add this mixin to each component I want like this:
mixins: [intervalMixin],
I have a simple h3 tag containing a title that is bound to a reactive data property.
I am fetching the value from a Firestore database and assign it to the data property. When I don't reload and access the page through client-side navigation, everything works fine.
However once I reload the title value gets updated properly (seen in console logs and vue dev tools) but the h3-tag remains empty.
Here is the code:
<template>
<h3 #click="displayCoursePreview" class="mt-5">{{ titl }}</h3>
</template>
<script>
props: {
student: {
type: Boolean
}
},
watch: {
rehydrated: {
// Always triggers once store data is rehydrated (seems to work without any problems)
immediate: true,
async handler(newVal, oldVal) {
if (newVal) {
await this.getSections();
return this.getTopics();
}
}
}
},
data() {
return {
titl: null
};
},
computed: {
rehydrated() {
return this.$store.state.rehydrated; // Equals true once store is rehydrated from local storage
}
},
methods: {
getSections() {
console.log('running') // Runs every time
let ref = this.$store.state.courses;
var cid = this.student
? ref.currentlyStudying.cid
: ref.currentlyPreviewing.cid;
// Get Course Title
this.$fireStore
.collection("courses")
.doc(cid)
.get()
.then(doc => {
console.log(doc.data().name) // Logs correct title every time
this.titl = doc.data().name;
this.thumbSrc = doc.data().imgsrc;
})
.catch(err => console.log(err));
}
</script>
I can't figure out why it sometimes displays the title and sometimes does not. Is there another way to bind titl to the content of the h3-tag without the {{}} syntax?
Thank you in advance!
EDIT:
I have changed the {{}} syntax to v-text like so:
<h3 #click="displayCoursePreview" class="mt-5" v-text="titl"></h3>
And now it works every time, even after a hard reload. Can anyone explain the difference and why this works?
To answer the original question it looks like you might have a race condition between this component and the store. The watch will only trigger 'getSections' if it sees a change in this.$store.state.rehydrated after it's been mounted, but the store might have completed that before this component got mounted, so then the watch never gets triggered.
Not sure why switching to v-text would have altered this, maybe it allows the component to mount slightly faster so it's getting mounted before the store completes it's rehydration?
I am having a hard time to render a nested object on to a reactjs page
import React, { Component } from "react";
import Toolpanel from "./Todopanel";
import Toollist from "./Toollist";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
city: "Auckland",
cityWeather: {}
};
this.updateUser = this.updateUser.bind(this);
}
updateUser(entry) {
console.log(entry);
let item = {
text: entry,
key: Date.now()
};
this.setState(prevstate => {
return {
users: prevstate.users.concat(item)
};
});
}
componentDidMount() {
let apiId = "***************************";
let city = this.state.city;
let ApiString =
"http://api.openweathermap.org/data/2.5/weather?q=" +
city +
"&APPID=" +
apiId;
fetch(ApiString)
.then(results => results.json())
.then(data => this.setState({ cityWeather: data }));
}
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test.wind.speed} </div>
</div>
);
}
}
export default App;
I have added my JSON file that I received from my weather API
//Json file
{
"coord": { "lon": 174.77, "lat": -36.85 },
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04n"
}
],
"base": "stations",
"main": {
"temp": 293.7,
"pressure": 1018,
"humidity": 77,
"temp_min": 293.15,
"temp_max": 294.26
},
"visibility": 10000,
"wind": { "speed": 5.1, "deg": 360 },
"clouds": { "all": 92 },
"dt": 1553672420,
"sys": {
"type": 1,
"id": 7345,
"message": 0.0043,
"country": "NZ",
"sunrise": 1553624951,
"sunset": 1553667823
},
"id": 2193733,
"name": "Auckland",
"cod": 200
}
I am trying to render the wind speed from the JSON to my page.. but is throwing me a error message saying "TypeError: Cannot read property 'speed' of undefined"...Please help. I am fairly new to ReactJs.
If you look at the code, here is the sequence of events:
Component is created, i.e. constructor is called
Component is mounted, i.e. componentDidMount is called
componentDidMount starts an async request to fetch the data which is then parsed and set in state.
render method tries to read the data from state.
Now, since the request in #3 is an async one, it may not have completed in time when the render method has been called the first time.
So, you need to check if your request has completed or failed or is running.
You can use that to conditionally render the content in your render method.
Recommended reading
The official reactjs blog entry on async rendering with examples of when data is fetched from an external resource
You're not wrong the way you approached it. The error you're getting is because the fetch you're performing is taking some time, and render first executes without having the data populated.
So first time it gets in your render method the value of test = {}. So test.wind.speed will throw an error.
Instead, show a loading state of some sort or simply return null until the call is performed:
render() {
let test = this.state.cityWeather;
if (!test) {
return 'Loading...';
}
....
}
You are accessing the properties too fast since fetch is an asynchronous call it will take some time but your render fires before that already.
Use it like this
{ test && <div>wind speed : {test.wind.speed} </div>}
Initially your test will be null as you haven't received any response from your API so you should check the variable presence before using it. So just check if it is present before using it like this:
render() {
let test = this.state.cityWeather;
return (
<div>
<Toolpanel parentUpdate={this.updateUser} />
<div>wind speed : {test && test.wind && test.wind.speed ? test.wind.speed : ''} </div>
</div>
);
}
Since you didn't post ToolPanel Component implementation, I may be wrong (I'm missing some information). But, I'm also pretty sure that your problem is not having a loading variable.
Basically, the first time render() method is called, you have this.state.cityWeather to be an empty object {}; that is because you fetch the data in componentDidMount(). Thus, the first time render() is called, being this.state.cityWeather empty, you cannot access this.state.cityWeather.wind.speed because you don't have the property wind in this.state.cityWeather!
So, usually, the common way to do this is adding a property loading in the state, and setting it to true in the constructor. Then, in the callback of the fetch, while you set the data in this.state.cityWeather, you also set loading to true.
Finally, in the render() method you wrote a conditional rendering: if this.state.loading === true, then you print a simple paragraph like <p>I'm retrieving info!</p>, otherwhise, if this.state.loading === false, you can render what you want.