I'm trying to pass data from my database to a page in my react project. The database stores the user data and the data is called with validateCookie() function. I'm getting data from the validateCookie function but I can't seem to get the data out of the function to the main page so I can use it to update the user's state and calendar and return that to update their information in the database.
The setState is not sending data to the page state. I've tried so much but I'm still new to react so I'm a bit out of my league
import ScheduleSelector from 'react-schedule-selector'
import React, { Component } from 'react';
import Moment from 'moment';
import { Row, Col, Button } from 'react-bootstrap';
import API from '../../utils/API';
class Availability extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
email: "",
calendar: [],
schedule: [],
}
// this.handleInputChange = this.handleInputChange.bind(this);
// this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.validateCookie();
console.log(this.state.user); // coming back empty because validate cookie is not passing data upstream
}
handleSubmit = (event) => {
event.preventDefault();
// let schedule = this.state.schedule;
// // alert("Your availability has been submitted successfully!");
// let ISOschedule = this.state.schedule.map(date => Moment(date).toISOString());
// let newCalendar = this.state.schedule
console.log(this.state.user);
API.updateAvailability(
this.state.user.email,
this.state.user.calendar,
this.state.user.schedule)
.then(r => {
console.log(r);
}).catch(e => {
console.log(e);
})
}
handleChange = newSchedule => {
this.setState({ schedule: newSchedule.map(date => Moment(date).toISOString()) })
}
validateCookie() {
API.validateCookie()
.then(res => res.json())
.then(res => {this.setState({ user: res})})
.then(res => {
console.log(this.state) // coming back with loading data aka empty
console.log(this.state.user) // coming back with all appropriate data
})
.catch(err => console.log(err));
console.log(this.state.user) // coming back empty
}
render() {
return (
<div>
<form ref="form" onSubmit={this.handleSubmit}>
<ScheduleSelector
selection={this.state.schedule}
numDays={7}
minTime={0}
maxTime={23}
onChange={this.handleChange}
/>
<Row>
<Col>
<Button type="submit" className="float-right">Submit Availability</Button>
</Col>
</Row>
</form>
</div>
)
}
}
export default Availability;
I think the problem is that in your validateCookie method, you are expecting the state to change as soon as you call the setState function. It is important to know that setState() does not immediately mutate this.state but creates a pending state transition.
Refer to this answer for more information.
One solution could be to check when this.state actually gets updated before you render anything in your render function.
Just like Swanky said, the setState() doesn't update immediately and you can listen for state change and re-render the UI. I have done some cleaning up to your setState below;
validateCookie = () => {
API.validateCookie()
.then(res => res.json())
.then(res => {
this.setState({...this.state, user: res.user})
console.log(this.state.user);
})
.catch(err => console.log(err));
}
Related
I am trying to fetch data from firebase. I am able to get the data and update the state, but state returns undefined after render in my React context Provider. I have tried to use some of the Life cycle method like componentWillMount or calling my fetchData function my the constructor function , since it get called before render, but none is working. Below is my code.
import React, { Component } from 'react';
import { dataDB, productDetail } from './data';
import { db } from './config/fbConfig'
import { TimerSharp } from '#material-ui/icons';
const ProductContext = React.createContext();
class ProductProvider extends Component {
constructor(props) {
super(props)
this.state = {
products: []
}
this.fetchData()
}
fetchData = () => {
db.collection("projects")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data); //successfully returns the data
// this.setState({ projects: data });
this.setState(() => {
return {
projects: data
}
})
console.log(this.state.products) // successfully returns the data and update the state
});
}
render() {
console.log(this.state.products) // returns empty arr and I need it to return the updated state with data
return (
<ProductContext.Provider value={{
...this.state
}}>
{this.props.children}
</ProductContext.Provider>
)
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
The issue is this.state.products get called before calling data in firebase. Please how can I be able to get data after render.
In fetchData() you set the attribute this.state.projects but in render you log this.state.products
I'm trying to make an API call with a User's input and store the response to display it.
The steps I'm taking:
storing the user's input through onChange="handleLocationChange"
sending the input through the form's onSubmit={this.handleSubmit}
then calling const weatherData = GetAPIData(API_KEY, this.state.cityID); to fetch API data
in the GetAPIData component I create the hooks const [weatherData, setWeatherData] = useState([]); and then return the json API response in the weatherData hook
I'm breaking a hooks rule (I believe my error is in trying to call a hook after rendering) and getting the following error, but I haven't been able to figure out how to solve for this error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component
Class component (main file)
import GetAPIData from "./GetAPIData";
class IOData extends Component {
constructor(props) {
super(props);
this.state = {
country: "",
city: "",
cityID: "",
APIData: "",
};
}
handleLocationChange = (event) => {
this.setState({
city: event.target.value,
});
};
handleSubmit = (event) => {
event.preventDefault();
const API_KEY = "*insert key value*";
this.setState({cityID: "6533961"}); // I think this is also incorrect since it is an event
const response = GetAPIData(API_KEY, this.state.cityID);
this.setState({APIData: response});
};
render() {
const { country, city } = this.state;
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>City:</label>
<input
type="text"
name="city"
value={city}
onChange={this.handleLocationChange}
/>
<button type="submit">Go!</button>
</form>
{APIData}
</div>
);
}
}
Function Component that retrieves the API response (second file, breaks here...)
const GetAPIData = (API_KEY, cityID) => {
const [weatherData, setWeatherData] = useState([]); // I believe it breaks here
const endpoint = `http://api.openweathermap.org/data/2.5/weather?id=${cityID}&appid=${API_KEY}`;
useEffect(() => {
fetch(endpoint)
.then(response => response.json())
.then(data => {
setWeatherData(data);
})
}, [])
return weatherData;
};
Does the solution have to do with having to re-render the class component to make the hooks work? How can I make the API response go through without an error?
I am new to react and I am trying to make a POST request using text field data, can anyone help me with how to store that input and make a request after a button is pressed.
I attempted to use useRef() which allowed me to obtain the data however I was not able to store it as a data object to then persist.
Currently my data persists, however it persists an empty object and the state is not being updated.
If anyone can help, I will really appreciate that.
Below is my App.js class
import React, { useState, useEffect, useRef, Component } from 'react';
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists"
});
class App extends Component {
state = {
artists: [],
theArtistName: ""
}
constructor(props){
super(props);
this.getArtists()
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () =>{
let data = await api.get("/").then(({ data }) => data);
this.setState({artists: data}) //setting our artists to be the data we fetch
}
createArtist = async () =>{
let response = await api.post('/', {name: this.state.theArtistName})
console.log(response)
this.getArtists()
}
deleteArtist = async (id) =>{
let data = await api.delete('/${id}')
this.getArtists();
}
handleAddArtist = (event) =>{
event.preventDefault()
this.setState({
theArtistName: event.target.value
})
const data = this.state.theArtistName
console.log(data)
}
componentDidMount(){
this.createArtist()
}
render(){
// const {theArtistName} = this.state
return(
<>
<input type={Text} placeholder="Enter Artist Name" name="theArtistName"></input>
<button onClick={this.createArtist}>Add Artist</button>
{this.state.artists.map(artist => <h4 key={artist.id}>{artist.name}
<button onClick={() =>this.deleteArtist(artist.id)}>Delete artist</button></h4>)}
</>
)
}
}
export default App;
this.setState is an async function, it takes second argument as callback. This should solve your problem. i.e.
import React, { useState, useEffect, useRef, Component } from "react";
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists",
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
artists: [],
theArtistName: "",
};
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () => {
let data = await api.get("/").then(({ data }) => data);
this.setState({ artists: data }); //setting our artists to be the data we fetch
};
createArtist = async () => {
let response = await api.post("/", { name: this.state.theArtistName });
console.log(response);
this.getArtists();
};
deleteArtist = async (id) => {
let data = await api.delete("/${id}");
this.getArtists();
};
handleAddArtist = (event) => {
event.preventDefault();
this.setState(
{
theArtistName: event.target.value,
},
() => {
this.createArtist();
}
);
};
componentDidMount() {
this.getArtists();
}
render() {
// const {theArtistName} = this.state
return (
<>
<input
type={Text}
placeholder="Enter Artist Name"
name="theArtistName"
></input>
<button onClick={this.handleAddArtist}>Add Artist</button>
{this.state.artists.map((artist) => (
<h4 key={artist.id}>
{artist.name}
<button onClick={() => this.deleteArtist(artist.id)}>
Delete artist
</button>
</h4>
))}
</>
);
}
}
export default App;
Let me know if it helps.
because react update state asynchronously so when you are invoking handleAddArtist function which update state the event might be gone so you need to store the value from the event in variable like this :
handleAddArtist = (event) =>{
event.preventDefault()
const {value} = e.target
this.setState({
theArtistName: value
})
}
and to check state update there is a lifecycle method called componentDidUpdate for class component and useEffect for functional component.
[edit]:
call this.createArtist() in componentDidUpdate like this :
componentDidUpdate(prevProps,prevState){
if(prevState.theArtistName!==this.state.theArtistName)
this.createArtist()
}
so the createArtist will fire only when theArtistName state change.
First of all, useRef is a hook only meant for function components and not for class components. For using Refs in class components use React.createRef().
Usually, HTML input elements maintain their own state. The usual way to access the value of an input element from a React component that renders it is to control the input element's state via this component by adding an onChange listener and a value attribute to the input element:
class App extends Component{
constructor(props) {
super(props);
this.state = {artistName: ""};
this.handleArtistNameChange = this.handleArtistNameChange.bind(this);
}
handleArtistNameChange(event) {
this.setState({artistName: event.target.value});
}
render(){
return (
<input
type="text"
value={this.state.artistName}
onChange={this.handleArtistNameChange}
/>
);
}
}
Whenever the value of the input element changes the App component will rerender with the most up-to-date value of the input in its state.
Here is a working example:
You can read more on using form elements in React here.
I have a react component in a Redux enabled application that starts by loading a list of ID's in a 2D array. (Each "page" is represented by an element of the outer array [1rst dimension])
Here is the component:
import React, { Component, Fragment } from "react";
import { loadInsiderPage, loadInsiderInfo } from "../../actions/insider";
import { connect } from "react-redux";
import IndividualInsider from "./individual";
import Paginate from "../common/paginate";
class InsiderList extends Component {
componentDidMount() {
if (this.props.insiderIds.length > 0) {
this.props.loadInsiderPage(this.props.insiderIds[0]);
} else {
this.props.loadInsiderInfo();
}
}
render() {
let { insiderIds, insiders } = this.props;
let insiderFormat = insiders.map(x => {
return <IndividualInsider key={x._id} insider={x} />;
});
return (
<Fragment>
<div className="container">
<Paginate
pages={insiderIds}
changePage={this.props.loadInsiderPage}
/>
{insiderFormat}
</div>
</Fragment>
);
}
}
export default connect(
null,
{ loadInsiderPage, loadInsiderInfo }
)(InsiderList);
This component will load the ID list if it's not filled by running the loadInsiderInfo() action, and if the ID list is not empty, it will trigger the page to be populated by running the loadInsiderPage() action which takes in a page from the ID list.
How can I have this trigger properly after the ID list has been loaded?
I was thinking I could do it in componentWillReceiveProps() but I'm not sure where to go with the nextProps property.
My actions are as follows:
export const loadInsiderInfo = () => dispatch => {
Axios.get("insider/list/pages/25")
.then(list => {
dispatch({ type: LOAD_INSIDER_LIST, payload: list.data });
})
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
export const loadInsiderPage = page => dispatch => {
console.log(page);
Axios.post("insider/page", { page })
.then(res => dispatch({ type: LOAD_INSIDER_PAGE, payload: res.data }))
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
Both simply grab data from the API and load it into the reducer.
The big issue that I'm coming across is that the Component will sometimes have props passed that keep the loadInsiderPage action from being called with a page object passed in.
In your action creator loadInsiderInfo() you can accept a param for the current page ID. Now when the Info is loaded, within this action creator you can dispatch another action by calling loadInsiderPage(id) in it. This way your page info is loaded for the first time by the insider info action creator itself.
Something like this:
export const loadInsiderInfo = (id) => dispatch => {
Axios.get("insider/list/pages/25")
.then(list => {
dispatch({ type: LOAD_INSIDER_LIST, payload: list.data });
if(<your-data-loaded>){
loadInsiderPage(id)(dispatch);
}
})
.catch(err => dispatch({ type: GET_ERRORS, payload: err }));
};
Now only call loadInsiderInfo(id) once, when there is no info loaded yet. For every other time, directly dispatch the loadInsiderPage(id) action instead. This way you handle every case, after the insider info data has been loaded.
I'm trying to get a data that is returning from axios get method to a method call on aon object. Instead of returning the value, it's returning the promise. What am I missing here?
import React, { Component } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
class App extends Component {
state = {
totalResults: ""
};
componentDidMount() {
this.setState({
totalResults: this.fetchData()
.then(function(res) {
const r = res.data.totalResults;
return r;
})
.catch(err => console.log("error: ", err))
});
}
fetchData = () => {
return axios.get(
"https://newsapi.org/v2/top-headlines?country=us&apiKey=8d98dac05ec947d1b891832495641b49"
);
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => console.log(this.state.totalResults)}>
Click
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here's the link to codesandbox
Note: This is just a reference code. I can't do setState because I'm trying to call the method from array.map() iterator instead.
Edit:
This is what actually I'm trying to do: codesandbox.io/s/n5m3qyn65l
For some reason it's showing Network Error
Thanks for helping me out.
You should use Promise.all() to fetch all images before updating your articles. Like so:
const res = response.data;
Promise.all(res.map(article => (
this.fetchImages(article.featured_media)
.then(image => ({
title: article.title.rendered,
content: article.content.rendered,
featuredImage: image.guid.rendered
}))
.catch(err => console.log("Error fetching image: ", err))
))
)
.then(articles => this.setState({ articles }))
.catch(err => console.log("Error setting up articles: ", err))
You should setState after getting the response from fetchData() because fetchData() will return promise, which you're setting in the state.
Remember, setState will do assignment only, you can't expect it wait for asynchronous operation. In that case, try async/await
Updated answer:
https://codesandbox.io/s/1r22oo804q
Find the inline comments
// Our basic components state setup
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
currentSession: {
fullNames: [],
contactInfo: []
}
}}
// Here we make our function to fetch our API data
fetchData()
.then(res => {
let namesData = res.data.name;
let contactData = res.data.email;
this.setState(prevState => ({
currentSession: {
...prevState.currentSession,
fullNames: Object.assign(namesData),
contactInfo: Object.assign(contactData)
}
}));
});
// Since it's an async call, we'll put in in a 'componentDidMount' method
componentDidMount() {
this.fetchData();
}