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>
);
}
}
Related
I'm trying to grab json from my backend to fill a table on the front end. Nothing is loading and in the react debugging tools it says the table prop is empty.
I've added async to the function that is doing the fetching, but it still seems to pass the json to the prop before its finished (not totally sure).
EDIT: lines are missing in the code because I cut out what was irrelevent
in app.js
import React, { Component } from 'react'
import Table from './Table'
class App extends Component {
render() {
const repos = getGitHubRepos()
return (
<div className="container">
<Table repoData={repos} />
</div>
)
}
}
async function getGitHubRepos() {
const response = await fetch('valid url i'm hiding')
return await response.json()
}
export default App
in table.js
import React, { Component } from 'react'
class Table extends Component {
render() {
const { repoData } = this.props
return (
<table>
<TableHeader />
<TableBody repoData={repoData} />
</table>
)
}
}
const TableBody = props => {
const rows = props.repoData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.lang}</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
export default Table
I expect the output to map each bit of json into the table but it isn't doing that because the prop is empty when it gets to table.js
You can perform async tasks in an async componentDidMount method:
class App extends Component {
constructor() {
super();
this.state = { repos: [] };
}
async componentDidMount() {
const repos = await getGitHubRepos();
this.setState({ repos });
}
render() {
return (
<div className="container">
<Table repoData={this.state.repos} />
</div>
);
}
}
async function getGitHubRepos() {
const response = await fetch("valid url Im hiding");
return response.json();
}
export default App;
Be adviced async/await syntax is not covered by all browsers
Move your fetching logic in one of Reacts life-cycle methods I would suggest componentDidMount, you should never fetch anything in the render method, you should even avoid extensive calculations there.. after you get the data I would save it in the local component state with this.setState({someState: data}) .. when the state is changed your component will automatically re render. You can read you data from you component state this this.state.someState
You get a thenable promise from fetch you can use .then(function () {}) to define what happens when the data is fetched
function getGitHubRepos() {
fetch('valid url i'm hiding')
.then(function (res) {
this.setState({data: res.data}); // or something similar
});
}
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);
How can I pass data I receive from a get request pass over to a component? Whatever I tried wouldn't work but my thinking was as the code below shows..
Thanks!
export function data() {
axios.get('www.example.de')
.then(function(res) {
return res.data
})
.then(function(data) {
this.setState({
list: data
})
})
}
import {data} from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ""
};
}
componentWillMount() {
data();
}
render() {
return <p > this.state.list < /p>
}
}
You call this.setState inside of data()->then callback, so this is context of the then callback function. Instead you should use arrow functions (it does not have its own context) and pass component's this to data function using call
export function data() {
axios.get('www.example.de')
.then(res => res.data)
.then(data => {
this.setState({
list: data
})
})
}
import {data} from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ""
};
}
componentWillMount() {
data.call(this);
}
render() {
return <p > this.state.list < /p>
}
}
However, your data services must not know about setState and, event more, expect passing this from react component. Your data service must be responsible for retrieving data from server, but not for changing component state, see Single responsibility principle. Also, your data service can be called from another data service. So your data service should return promise instead, that can be used by component for calling setState.
export function data() {
return axios.get('www.example.de')
.then(res => res.data)
}
and then
componentWillMount() {
data().then(data=>{
this.setState({
list: data
})
});
}
your api shouldn't know anything about your component, you can easily do this with callback, like so -
export function data(callback) {
axios.get('www.example.de')
.then(res => callback({ data: res.data }))
.catch(err => callback({ error: err }));
}
By doing this you can easily unit test your api
So in your Test component, you simply do -
componentWillMount() {
data(result => {
const { data, error } = result;
if (error) {
// Handle error
return;
}
if (data) {
this.setState({ list: data });
}
});
}
Your request is a promise so you can simply return that from the imported function and use the eventual returned result of that within the component. You only want to be changing the state of the component from within the component.
export function getData(endpoint) {
return axios.get(endpoint);
}
Note I've changed the name of the function to something more "actiony".
import { getData } from './api.js';
class Test extends React.Component {
constructor(props) {
super(props);
// Your state is going to be an array of things, so
// initialise it with an array to spare confusion
this.state = { list: [] };
}
// I use ComponentDidMount for fetch requests
// https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
componentDidMount() {
// We've returned a promise from `getData` so we can still use the
// promise API to extract the JSON, and store the parsed object as the
// component state
getData('www.example.de')
.then(res => res.data)
.then(list => this.setState({ list }))
}
}
Your external function doesn't have the correct context of this, so you'll need to call it with the correct context from within the component:
componentWillMount() {
data.call(this);
}
However, inside the API call, it still won't have the correct this context, so you can set a variable to point to this inside the data() function:
export function data() {
let that = this;
axios('http://www.url.com')
.then(function(res) {
return res.data
})
.then(function(data) {
that.setState({
list: data
})
})
}
Details of the this keyword
However, it's generally considered better practice to only handle your state manipulation from with the component itself, but this will involve handling the asynchronous nature of the GET request, perhaps by passing in a callback to the data() function.
EDIT: Updated with asynchronous code
//api.js
data(callback){
axios.get('www.url.com')
.then(res => callback(res));
}
//component.jsx
componentWillMount(){
data(res => this.setState({list: res}));
}
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.
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}/>);
}
}