Firebase data to React component - javascript

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}/>);
}
}

Related

React MOBX Component Render

I just switched out this.setState to use mobx observable, because I have multiple GET requests that fetch data. This prevents the PieChart from being re-rendered every time this.setState is called.
However, now the child component does not ever get re-rendered and stays with the initial placeholder mobxState. How can I get the PieChart child component to re-render when the data for it comes in from the API.
class Device extends React.Component {
mobxState = {
customOptions: [],
rowData: []
};
//mount data
componentDidMount() {
//call the data loader
this.fetchData();
}
fetchData = () => {
axios
.get("/custom_options.json")
.then(response => {
this.mobxState.customOptions = response.data.custom_options;
})
.then(
//data for PieChart, need this portion to render the PieChart
axios.get("/devices.json").then(response => {
this.mobxState.rowData = response;
})
);
};
render() {
return <PieChart data={this.mobxState.rowData} />;
}
}
decorate(Device, {
mobxState: observable
});
export default Device;
You need to make sure your Device component is an observer, and if you are using a MobX version below 5 you have to slice() or peek() the array in the render method.
import { observer } from "mobx-react";
class Device extends React.Component {
// ...
render() {
return <PieChart data={this.mobxState.rowData.slice()} />;
}
}
decorate(Device, {
mobxState: observable
});
export default observer(Device);

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>
);
}
}

Update variable in React in class not extending component

I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}

Getting a JSON asynchronously and THEN rendering the component

I have a component, which has to download a JSON file and then iterate over it and display each element from the JSON on the screen.
I'm kinda new with React, used to be ng dev. In Angular, I used to do it with lifecycle hooks, e.g. ngOnInit/ngAfterViewInit (get some JSON file and then lunch the iteration func). How can I achieve it in React? Is it possible to reach it with lifecycle hooks, like ComponentWillMount or ComponentDidMount.
My code (it's surely wrong):
export default class ExampleClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentWillMount(){
getData();
}
render() {
return (
<ul>
{this.state.data.map((v, i) => <li key={i}>{v}</li>)}
</ul>
)
};
}
const getData = () => {
axios.get(//someURL//)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
};
How to force React to get the JSON before rendering the component?
Thank you so much.
Making an AJAX request in ComponentWillMount works. https://facebook.github.io/react/docs/react-component.html#componentwillmount
You could also just work that logic into your constructor depending on your exact needs.
https://facebook.github.io/react/docs/react-component.html#constructor
export default class ExampleClass extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
axios.get(/*someURL*/)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
}
}
You can do a simple if statement in your render function.
render () {
if (Boolean(this.state.data.length)) {
return <ul>{this.state.data.map((v, i) => <li key={i}>{v}</li>)}</ul>
}
return null
}
You can also use a higher order component to do the same thing.
const renderIfData = WrappedComponent => class RenderIfData extends Component {
state = {
data: []
}
componentWillMount() {
fetchData()
}
render() {
if (Boolean(this.state.data.length)) {
return <WrappedComponent {...this.state} />
}
return null
}
}
Then you can wrap the presentational layer with the HOC.
renderIfData(ExampleClass)
Not sure what version of React you are using but you may need to use <noscript> instead of null.
This is essentially preventing your component from rendering until it has all the data.

Undefined State when pulling data for mount

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

Categories