Undefined State when pulling data for mount - javascript

I'm pulling data from my my database which needs to be available prior to the mounting of the component in order for the page to be populated with the componentDidMount() lifecycle method. I've verified that if i remove the setState and console.log my data, it does fetch from the DB as expected, but when I try to assign the data to my state variable, it return a error stating Unable to get property 'setState' of undefined or null reference within my componentWillMount() lifecycle method. I've listed my ReactJS code below.
import React, { Component, PropTypes } from 'react';
import Picture from '../../components/picture.jsx';
import { browserHistory } from 'react-router';
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {clothingData: ''};
}
componentWillMount(){
fetch('/t')
.then(function(result){
return result.json();
})
.then(function(re){
this.setState({ clothingData: re });
console.log(this.state.clothingData);
})
.catch(function(error){
console.log(error);
});
}
componentDidMount(){
//empty for now
}
render(){
var MyArray = ['justin','tiffany','joe','john','karissa','pam','joseph','sean','kim'];
var imageSrc = ['http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373',
'http://placehold.it/249x373', 'http://placehold.it/249x373', 'http://placehold.it/249x373'];
return (
<div>
<Picture src = {imageSrc} onClick = { () => {browserHistory.push('/Product'); }} name = {MyArray} amount = {8} />
</div>
);
}
}

The problem is that this is being reassigned from the component instance to the function instance/global object.
componentWillMount() {
fetch('/t')
.then((result) => {
return result.json();
})
.then((re) => {
this.setState({ clothingData: re });
console.log(this.state.clothingData);
})
.catch(function(error){
console.log(error);
});
}
will work just fine since the arrow function will ensure that the this is bound to the component instance so this.setState will actually be defined. Whereas what you have the this is being set to the global object which does not have a property of setState

Related

How to get state in react after render

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

How to make a POST request with input text as data React

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.

basic reactjs, how to get REST data and render it

I have a basic rect component and I already figured out how to get data from a protected rest api, however I am not sure how to render it in the component and how to call that function, or in which lifecycle I should call the function.
import React, { Component } from 'react';
import LayoutContentWrapper from '../components/utility/layoutWrapper';
import LayoutContent from '../components/utility/layoutContent';
var q = require('q');
var Adal = require('../adal-webapi/adal-request');
function getValues() {
var deferred = q.defer();
Adal.adalRequest({
url: 'https://abc.azurewebsites.net/api/values'
}).then(function(data) {
console.log(data);
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
}
export default class extends Component {
render() {
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
<LayoutContent>
<h1>Test Page</h1>
</LayoutContent>
</LayoutContentWrapper>
);
}
}
The lifecycle method you choose to fetch the data in will largely depend on whether or not you need to update the data at any point and re-render, or whether that data depends on any props passed to the component.
Your example looks as though it is a one time API call that doesn't depend on any props, so placing it in the constructor would be valid.
I would move the getValues code to within the class, and do something like this. Note: I've used async/await, but you could use promise callbacks if you prefer.
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
this.fetchData();
}
async fetchData() {
try {
const data = await this.getValues();
!this.isCancelled && this.setState({ data });
} catch(error) {
// Handle accordingly
}
}
getValues() {
// Your API calling code
}
componentWillUnmount() {
this.isCancelled = true;
}
render() {
const { data } = this.state;
return (
<ul>
{data && data.map(item => (
<li>{item.name}</li>
))}
</ul>
);
}
}
If you needed to fetch the data again at any point, you might use one of the other lifecycle hooks to listen for prop changes, and call the fetchData method again.
Note the inclusion of a failsafe for the component un-mounting before the async call has finished, preventing React from throwing an error about setting state in an unmounted component.
something like this...
export default class extends React.Component {
constructor(props) {
super(props);
// initialize myData to prevent render from running map on undefined
this.state = {myData: []};
}
// use componentDidMount lifecycle method to call function
componentDidMount() {
// call your function here, and on promise execute `setState` callback
getValues()
.then(data => {
this.setState({myData: data})
}
}
render() {
// create a list
const items = this.state.myData.map((datum) => {
return <LayoutContent>
<h1>{datum}</h1>
</LayoutContent>
});
// return with the list
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
{items}
</LayoutContentWrapper>
);
}
}

React defaultValue not working axios deliverd dynamic data

Hello im new in React and im trying to play a little with React but heres one point i dont understand.
at first, fetch with axios data who return my data, the following, then i try to put them into the input fields, value(and is readonly), defaultValue is better, now i have the problem, i see nothing, the value exists when i view with firebug, the strange thing is, when i add a unneed character the input get filled by my wanted but not by default.
The very strange thing is, when i put everything in a Array and does a map function over it i have the value
the json code
{"firma":"hallo","strasse":"musterweg 7","plz":"01662"}
the js code
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class Testx extends React.Component {
constructor(props) {
super(props);
this.state = {
data:[]
};
}
componentDidMount(){
var self = this;
axios.get('http://localhost/index.php')
.then(function (response) {
self.setState({ data: response.data});
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div>
<input type="text" defaultValue={this.state.data.firma}/>
</div>
);
}
}
ReactDOM.render(<Testx/>, document.getElementById('hello'));
You need to wait until the data comes by showing something loading.
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class Testx extends React.Component {
constructor(props) {
super(props);
this.state = {
data:{}
};
}
componentDidMount(){
var self = this;
axios.get('http://localhost/index.php')
.then(function (response) {
self.setState({ data: response.data});
})
.catch(function (error) {
console.log(error);
});
}
render() {
const { data }= this.state;
if(data.firma) {
return (<div>
<input type="text" defaultValue={data.firma}/>
</div>);
}
return <div>loading...</div>;
}
}
ReactDOM.render(<Testx/>, document.getElementById('hello'));
Initially, your data state is in Array format. So this.state.data.firma doesnt work. Instead make it as empty object {}.
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class Testx extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
};
}
componentDidMount() {
var self = this;
axios.get('http://localhost/index.php')
.then(function (response) {
self.setState({ data: response.data});
})
.catch(function (error) {
console.log(error);
});
}
render() {
return <div>
<input type="text" defaultValue={this.state.data.firma}/>
</div>
}
}
ReactDOM.render(<Testx/>, document.getElementById('hello'));
The "code style" is outdated. Try to work with arrow functions which bind your functions, such as setState. Or bind your functions once in your constructor like this.myFunction = myFunction.bind(this) so you are able to access this. I already commented that this.state.data is declared as an array. Either change it to be an object or access an object by a specific index.
class Testx extends React.Component {
constructor(props) {
super(props);
this.state = {
data:{}
};
}
componentDidMount = () => { //Note the arrow function to bind this function
//Functions like componentDidMount are usually already bound
axios.get('http://localhost/index.php')
.then((response) => {
this.setState({ data: response.data});
})
.catch((error) => {
console.log(error);
});
}
render() {
return (
<div>
<input type="text" defaultValue={this.state.data.firma}/>
</div>
);
}
}
If your response is an array instead of an object, then try to access firma like this: this.state.data[index].firma
thanks all, special for the tips and tricks and how i can do thinks better, my questions is solved, big thanks to all for helping me in under 15 min happy
im now also found a way playing with https://facebook.github.io/react/docs/forms.html and set my state with
handleChange(event) {
var tmp = this.state.data;
tmp[event.target.id] = event.target.value
this.setState({data: tmp});
}
with modding my render
<input type="text" id="firma" value={this.state.data.firma} onChange={this.handleChange} />

Firebase data to React component

I have a React component that I'm trying to pass some props but I get an Uncaught Error: App.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object. when I try to return it inside the snapshot.
// cache settings data
fire.settings = {};
fire.settings.ref = fire.database.ref('settings');
// main app build
class App extends Component {
render() {
// get values from firebase
fire.settings.ref.on('value', function(data) {
return (<Home settings={data.val()} />);
});
}
}
So I started messing around with generators and I get the component to render, but I just get an empty object in my settings prop.
// main app build
class App extends Component {
render() {
// get values from firebase
function* generator() {
fire.settings.ref.on('value', function(data) {
fire.settings.snapshot = data.val();
});
yield fire.settings.snapshot;
}
// init generator and return homepage
let promise = generator();
return (<Home settings={promise.next()} />);
}
}
As well as using componentDidMount()
// main app build
class App extends Component {
componentDidMount() {
this.fire.settings.ref.on('value', function(snapshot) {
this.props.settings = snapshot.val();
}, (error) => console.log(error), this);
}
render() {
return (<Home settings={this.props.settings}/>);
}
}
SOLVED
Pass the value through the render to the component
// init render
fire.settings.ref.on('value', function(data) {
ReactDOM.render(
<App settings={data.val()}/>, document.getElementById('app'));
});
export default App;
You are trying to return your element inside callback of a listener which is asynchronous. Instead of that you should set listener inside componentDidMount and call setState inside the callback.
// cache settings data
fire.settings = {};
fire.settings.ref = fire.database.ref('settings');
// main app build
class App extends Component {
constructor(props) {
super(props);
this.state = { data: null };
this.onSettingsChanged = this.onSettingsChanged.bind(this);
}
onSettingsChanged(data){
this.setState({data: data.val()});
}
componentDidMount() {
fire.settings.ref.on('value', this.onSettingsChanged);
}
render() {
return (<Home settings={this.state.data}/>);
}
}

Categories