I am fetching data inside componentDidMount but i get undefined during initial render and again render happens and during that time the state variable gets populated. Now when it is not undefined and after population I want to destructure it and display the data inside my component.
Note: getProjectDetails() makes a GET req to populate the data.
I am getting typer error name of undefined.
constructor(props) {
super(props);
this.state = {
projectDetails: ''
};
}
componentDidMount() {
getProjectDetails(this.props.logged_in_user, this.props.projectId)
.then( res => {
this.setState({projectDetails: res});
})
.catch(err => {
console.log('Error: ' + err);
})
}
//Inside render()
render() {
console.log('Project details from API endpoint: ', this.state.projectDetails.project)
const { projectDetails } = this.state;
console.log('Destructure: ', projectDetails);
const project = this.state.projectDetails.project;
let {
name,
location,
city,
builder_name } = project;
You could check with the following if the state is set:
render() {
if(this.state.projectDetails === ''){
return <div>Loading</div>;
}
else{
return <div>{this.state.projectDetails.project}</div>
}
}
So as long as the state is false, Loading will be returned, if there is a value, then this.state.projectDetails.project gets returned. I hope that helps.
Edit:
The render method will be called twice, before the data is fetched and then, after the state is set. So this would only return the data, if its really set.
Related
I've done a lot of searching and can't seem to find the answer to this - maybe I'm just not using the right terminology.
What I need to do is pass data from a WebSocket component, down to a child component. I'm already passing the WebSocket via props to the child so that it can use the send() function and send data to the socket. I need to also pass any received data via onmessage. Setting this up in the usual way inside the child doesn't work.
What I need to happen is when the data is received in the socket it gets sent to the child, with a function inside the child to then do something with it (send it via MIDI using the Web MIDI API)
Parent
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
var midi = data["command"]; // Need to send this to the child somehow.
};
......
}
render() {
return <MIDIComponent websocket={this.state.ws} />;
}
}
EDIT: So I've managed to get the data from the parent to the child, and I've rendered it to the screen for testing. But I can't extract it inside the functions I need. I've tried combinations of using arrow functions, binding 'this' etc. I either can't access this or the midi ports either come back as undefined or null, the default value.
Child
class MIDIComponent extends Component {
constructor(props) {
super(props);
this.state = {
midiInput: null,
midiOutput: null,
};
}
componentDidMount() {
const that = this;
this.setupMIDI(that);
}
setupMIDI = (that) => {
navigator.requestMIDIAccess({ sysex: true }).then(onMIDISuccess);
function onMIDISuccess(midiAccess) {
that.setState({ midiInput: Array.from(midiAccess.inputs.values())[0] });
that.setState({ midiOutput: Array.from(midiAccess.outputs.values())[1] });
that.state.midiInput.onmidimessage = getMIDIMessage;
// storing the midi ports in the state like this, but it doesnt work.
}
function getMIDIMessage(msg) {
console.log(msg.data);
that.props.websocket.send(
JSON.stringify({ message: Array.from(msg.data), type: "config" })
);
}
};
sendMIDIMessage = (msg) => {
this.state.midiOutput.send(msg); // need to get to the midiOutput port here to send the data
};
render() {
return <div key={this.props.midi}>{this.props.midi}</div>; // Just using this to render the data to the screen for testing
}
}
I should probably mention that I will be eventually having two Child Components that will need to receive data from the Socket depending on the type of data received. At the moment I'd just like to get it set up using one. Any help would be greatly appreciated.
Simply save the received data in the state as well like this:
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
midi: [] // Create an empty array so that the child always received something and not undefined
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
const midi = data["command"]; // Need to send this to the child somehow.
this.setState({
midi // Save the received data in the state
});
};
}
render() {
const {ws, midi} = this.state; // Extract the data from the state
return <MIDIComponent websocket={ws} midi={midi}/>; // Pass the data as a prop to the child
}
}
I am using react-select async to load options from a input passed through a API. I get the JSON response that has a list inside it which has a field "FullName" I am attempting to go through the JSON file and store all the field names in an array. The array is then returned as the options.
Inside the JSON there is a list and the list contains the results and for each number there is a FullName
The format of the JSON response:
version:
status:
-status code
-status message
error:
result:
-paginate
list:
-0
--FullName
Below is my class I've shown where I think my issues lies with -----
class ReactSelectExample extends Component {
constructor(props, context) {
super(props, context);
this.state = {
selectedOption: {}
}
}
fetchData = (inputValue, callback) => {
if (!inputValue) {
callback([]);
} else {
setTimeout(() => {
fetch("-----API URL----" + inputValue, {
method: "GET",
})
.then((resp) => {
return resp.json()
})
----------------
.then((data) => {
const tempArray = [];
data.forEach((element) => {
tempArray.push({ label: `${element.fullname}`, value: element.FullName });
});
callback(tempArray);
---------------
})
.catch((error) => {
console.log(error, "catch the hoop")
});
});
}
}
onSearchChange = (selectedOption) => {
if (selectedOption) {
this.setState({
selectedOption
});
}
};
render() {
return ( <div>
<AsyncSelect
value={this.state.selectedOption}
loadOptions={this.fetchData}
placeholder="Admin Name"
onChange={(e) => {
this.onSearchChange(e);
}}
defaultOptions={false}
/>
</div>)
}
}
when I start it up and search all I see is loading but no options with names load in the. the error when I inspect the page I get the error
JSON.parse: unexpected end of data at line 1 of the column 1 of JSON data catch the hoop
this was a CORS policy issue. because the api I was using was external and didn't match my url, the JSON response was not being seen. I switched to calling the api on the backend because there is no CORS for server to server.
I'm a React beginner, so go easy on me..
I'm making a really really basic app right now that I will expand on later..
Right now I'm simply trying to get React to pull a JSON object from Youtube with channel data, store the returned list of videos (I guess in state?), and then pull the video IDs so that I can throw them in an object and randomize them for playback later.
My code is pulling the data alright, but at the moment I can't seem to access the "videoId" property of each video's ID... I can't for the life of me, understand why the video ID objects are accessible, but not their properties within..
It seems like YouTube API is returning an array of objects.. so I thought I should be able to do this:
videos[0].id.videoId
but this is giving errors... however videos[0].id seems to work, so I'm lost..
What am I doing wrong? Am I using state wrong? Am I accessing the object properties wrong?
See below for console logs in the example:
import React, { Component } from "react";
import "./App.css";
import YouTube from "react-youtube";
class App extends Component {
constructor(props) {
super(props);
console.log(">> [App.js] Inside Constructor", props);
this.state = {
videos: {}
};
}
componentDidMount() {
var that = this;
var API_key = "MY API KEY IS HERE (working) ";
var channelID = "UCs3o4RhBiP2wcwqkZR2QVLw";
var maxResults = 10;
var url =
"https://www.googleapis.com/youtube/v3/search?key=" +
API_key +
"&channelId=" +
channelID +
"&part=snippet,id&order=date&maxResults=" +
maxResults;
fetch(url)
.then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(function(data) {
that.setState({ videos: data.items });
console.log(">> data items: ", data.items);
});
}
render() {
const opts = {
height: "390",
width: "640",
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1
}
};
console.log(">> states = ", JSON.stringify(this.state.videos[0]));
//logs :
// >> states = {"kind":"youtube#searchResult","etag":"\"XI7nbFXulYBIpL0ayR_gDh3eu1k/z_ggxomb8RCC1cd6Bx8IrkCUHxA\"","id":{"kind":"youtube#video","videoId":"IoId8_4mvT4"},"snippet":{"publishedAt":"2017-07-18T19:12:33.000Z","channelId":"UCs3o4RhBiP2wcwqkZR2QVLw","title":"WATCH in 360 - CP24 tours Toronto Island. July 18 2017","description":"Subscribe to CP24 to watch more videos: https://www.youtube.com/channel/UCs3o4RhBiP2wcwqkZR2QVLw Connect with CP24: For the latest news visit: ...","thumbnails":{"default":{"url":"https://i.ytimg.com/vi/IoId8_4mvT4/default.jpg","width":120,"height":90},"medium":{"url":"https://i.ytimg.com/vi/IoId8_4mvT4/mqdefault.jpg","width":320,"height":180},"high":{"url":"https://i.ytimg.com/vi/IoId8_4mvT4/hqdefault.jpg","width":480,"height":360}},"channelTitle":"CP24","liveBroadcastContent":"none"}}
console.log(">> states = ", JSON.stringify(this.state.videos[0].id));
//logs : ERROR :
// >> states = ncaught TypeError: Cannot read property 'id' of undefined
// at App.render (App.js:50)
// at finishClassComponent (react-dom.development.js:13193)
// at updateClassComponent (react-dom.development.js:13155)
return (
<div className="App">
{
// using an NPM package for displaying YouTube Videos
// will add this back when I get the videoIds pulling correctly..
//<YouTube
// videoId={this.state.videos[1].videoId}
// opts={opts}
// onReady={this._onReady}
// />
}
</div>
);
}
}
export default App;
Your request is asynchronous, so the videos will not be set on first render. this.state.videos[0] will give undefined, and trying to access id on that will result in your error.
You could instead e.g. keep an additional piece of state loading and just render null until your network request has finished.
Example
class App extends Component {
state = { videos: [], loading: true };
componentDidMount() {
var that = this;
var API_key = "MY API KEY IS HERE (working) ";
var channelID = "UCs3o4RhBiP2wcwqkZR2QVLw";
var maxResults = 10;
var url =
"https://www.googleapis.com/youtube/v3/search?key=" +
API_key +
"&channelId=" +
channelID +
"&part=snippet,id&order=date&maxResults=" +
maxResults;
fetch(url)
.then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(function(data) {
that.setState({ videos: data.items, loading: false });
})
.catch(error => {
console.error(error);
});
}
render() {
const { loading, videos } = this.state;
if (loading) {
return null;
}
return (
<div className="App">
<YouTube
videoId={videos[1].id.videoId}
opts={{
height: "390",
width: "640",
playerVars: {
autoplay: 1
}
}}
onReady={this._onReady}
/>
</div>
);
}
}
Whenever socket receive new data i am trying to set this data to state.
I am creating simple chat application and i want to set this recent chat data received from socket set to state.
var socket = constants.SERVER;
var chatData = ""
class Chat extends Component {
constructor(props) {
super(props);
this.state = { recentChat:[] }
}
componentDidUpdate()
{
socket.on(constants.SOCKET_GET_CHAT, function(data){
console.log(JSON.stringify(data))
this.setState({
recentChat:data
})
});
}
render() {
return(<div/>)
}
}
componentWillMount() {
socket.on(constants.SOCKET_GET_CHAT, (data) => {
this.setState({
recentChat:data
})
});
}
You have to attach listener in componentWillMount or didMount.
socket.on(constants.SOCKET_GET_CHAT, data => {
this.setState(previousState => ({
recentChat: [...previousState.recentChat, data]
}))
});
You'll want to do this as you'll constantly receive new messages from the sockets, and will want to retain the chats in this.state.recentChat and append new chats.
Basically I am creating a email form with react as view and express for the server. It is working fine.
Instead of redirecting on success I am just wanting to re render the form component on the contact page.
Is there anyway I can access the post request success value from within a react component? maybe calling a fetch method within the contact component that fetchs at the '/contact' end point? will this trigger the app.post() method within my express.js file??
this is my post request on button submit within react:
handleFormSubmit = (name, email, text) => {
axios.post('/contact', {
name: name,
email: email,
text: text
}).then(res => {
this.setState({sent: true})
console.log(this.state);
}).catch(err => {
console.log(err);
})
}
this is my express js post:
server.post('/contact', async (req, res) => {
try {
sendMail(req.body).then(info => console.log(info))
// I am getting a response here just fine
console.log(res)
} catch (error) {
console.log('express line 25: ', error);
}
});
I am using Next.js and nodemailer with GMail
Thanks
Ok sorry guys, problem has nothing to do with any code, its next js setup, i built it and ran and seems to be returning fine!
I don't see your code, but if we use fetch inside a React component, try using componentDidMount:
class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {data: null, isLoading: true};
}
// from the server
componentDidMount() {
fetch('/contact').then( response => response.json()
).then( data => {
this.setState({data, isLoading: false}); // es6 syntax same as this.setState({data: data, isLoading: false})
}).catch( err => {
throw new Error(err);
});
}
render() {
const {data, isLoading} = this.state;
if(isLoading){return (<div>Loading...</div>)} //no data fetched in the initial render
return (
<div>
{data}
</div>
);
}
}
Hopefully that will help you.