trying to set up a simple AJAX call with Axios in React.
I make the Axios call in my 'FetchRepos' component then require it in my 'Homepage' component passing a language argument.
It loads fine because I console.logged the data in Homepage component but when I put the function in set state I check in React dev tools and the 'repos' state is null and when I do the onClick method for 'activeLanguage' it simply removes 'repos' from state.
Help would be appreciated.
Homepage Component
import React from "react";
import {fetchPopularRepos} from "./FetchRepos";
class Homepage extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLanguage: 'all',
repos: null
}
this.activeLanguage = this.activeLanguage.bind(this);
}
activeLanguage(lang) {
this.setState({
activeLanguage: lang,
repos: fetchPopularRepos(lang)
})
}
render() {
var languages = ['all', 'ruby', 'javascript', 'python'];
return (
<div>
{languages.map((lang) => {
return (
<li key={lang} onClick={this.activeLanguage.bind(null, lang)} style={lang === this.state.activeLanguage ? {color: 'red'} : null}>{lang}</li>
)
})}
</div>
)
}
}
FetchRepos Component
var axios = require('axios');
export function fetchPopularRepos(language) {
axios.get('https://api.github.com/search/repositories?q=stars:>1+language:'+ language + '&sort=stars&order=desc&type=Repositories')
.then(function(res) {
return res.data.items;
})
}
You have 2 problems here:
1- The axios function return a promise, so even with the promise your state won't be set up correctly. You must use asyn/await or handle the respond as a promise and set up the state inside a then function.
async activeLanguage(lang) {
this.setState({
activeLanguage: lang,
repos: await fetchPopularRepos(lang)
})
}
2- You must return something from the fetchPopularRepos function because your function is not returning anything. See my code below
BAD (Does not return any value)
function some(){
var a = 1;
var b = 2;
var result = a + b
}
GOOD (Return a value)
function some(){
var a = 1;
var b = 2;
return a + b
}
So, you return something because you want to catch whatever is the result in other piece of your code. Like this:
ver result = some()
Related
I have a component that looks like this:
export default class Class1 extends Component {
render = async () => {
await AsyncStorage.getItem('someValue', (error, someValue) => {
return(
<Class2 prop1={someValue}/>
)
}
}
}
What's happening here is that I need to render Class1 based on the value of someValue that is returned from AsyncStorage. The problem is, you can't make the render() method async because async functions return a promise, and render() needs to return a React component.
Does anyone know how I can do this?
Thanks!
For this kind of tasks, you would put the value in your state. And based on the state, you will render the class as required.
In your componentDidMount of Class1, write:
componentDidMount() {
AsyncStorage.getItem('value').then((val) => {
this.setState({ value: val });
})
}
Then in your Class1 add a method which will generate the class based on state value:
createClass() {
// do something with the value
if (this.state.value === somevalue) {
return (
<Class2 />
)
}
return null;
}
And in your render method of Class1, type:
render() {
return (
{ this.createClass() }
)
}
You can set it to state, for example:
componentDidMount() {
AsyncStorage.getItem('someValue', (e, someValue) => {
this.setState({someValue})
}
}
Then you can use someValue from state in your render.
Currently, in addition to the async render issue, since you're already passing in a callback to AsyncStorage.getItem(), I'm not sure what using async/await would do.
I have two components, App and Child. My goal is that App passes an id down to Child and when Child receives it as props, I want Child to perform an Axios/Fetch request and then update itself with the result data. I will accept any answer that provides me with an example App/Child source code that completes my example or gives my insight if this is actually possible what I'm trying to implement.
I am a new to React.
// App.js
import React, { Component } from 'react';
import Child from './Child.js';
import './App.css';
class App extends Component {
state = {
id: 0
};
handleClick() {
this.setState({
id: this.state.id + 1
});
}
render() {
return (
<div>
<Child id={this.state.id} />
<div>
<button onClick={this.handleClick.bind(this)}>Pass new id down to Child component</button>
</div>
</div>
);
}
}
export default App;
// Child.js
import React, { Component } from 'react';
import axios from 'axios';
class Child extends Component {
state = {
data: null,
id: 0
};
loadData(q, cb) {
axios.get('http://localhost:3000/foo?q='+this.state.id)
.then(result => {
// ?
// this.setState would retrigger update and create an infinite updating loop
})
.catch(error => {
console.log('error: ' + error);
});
}
shouldComponentUpdate(nextProps, prevState) {
console.log('shouldComponentUpdate: nextProps = ' + JSON.stringify(nextProps) + ', prevState = ' + JSON.stringify(prevState));
// ??
}
getSnapshotBeforeUpdate(nextProps, prevState) {
console.log('getSnapshotBeforeUpdate: nextProps = ' + JSON.stringify(nextProps) + ', prevState = ' + JSON.stringify(prevState));
// ??
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps: nextProps = ' + JSON.stringify(nextProps) + ', prevState = ' + JSON.stringify(prevState));
return {
id: nextProps.id
};
// ??
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
render() {
return (
<div>
<div>data = {this.state.data}</div>
</div>
);
}
}
export default Child;
You should call check prev Id with current id to avoid recursive update. You just need to make sure you only derive state from props if your props have changed,
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.id !== prevState.id) {
return {
id: nextProps.id
};
}
return null;
}
Let me know if your issue still persists
This could be one way to go about your problem,
Firstly, you'll have to update all the state data inside your getDerivedStateFromProps
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.id !== prevState.id) {
// Returning this object is equivalent to setting state using this.setState
return {
id: nextProps.id
data: loadData(nextProps.id) // Something like this
};
}
return null; // Indicates no state change
}
As for the api call inside loadData method you can make use of the aync / await to return the data from api call
async loadData(id) {
try {
// Make your api call here
let response = await axios.get('http://localhost:3000/foo?q='+id);
return await response.json();
} catch(err) {
// catches errors both in axios.get and response.json
// Make sure to not update state in case of any error
alert(err);
return null;
}
}
Note:
This is not the complete solution as you might want to not update the state in the case where your api call in loadData catches any error.
Also, since you are using api calls, you might want to limit the times pass props to the child cause they all will trigger different api calls and you will run into unpredictable states. Try debouncing.
I am having trouble creating an array of titles from an Axios response. The method getTitles(props) receives data from the Axios response. How do I create an array of titles dynamically?
The functions I have tried in Javascript are for loops and EC6 mapping, nothing seems to work. Being new to react I could be missing something but I am not sure what it is.
React code
export default class Featured extends React.Component {
constructor() {
super();
this.state = {
data: null,
}
}
/**
* Received request from server
*/
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
this.setState(function(){
return {
data: data
}
})
}.bind(this));
}
getTitles(props){
//-- What code do I place here?
console.log(props.data)
return ['test title', 'test title 2'];
}
/**
* Render request
*/
render() {
let dataResponse = JSON.stringify(this.state.data, null, 2);
const Articles = this.getTitles(this.state).map((title, i) => <Article key={i} title={title}/> );
return (
<div class="row">{Articles}
<pre>{dataResponse}</pre>
</div>
);
}
}
Axios Code
var ApiCalls = {
articleData: function(id){
return axios.all([getArticles(id)])
.then(function(arr){
return arr[0].data.data;
})
.catch(function (error) {
console.log(error);
})
},
React setState behaves asynchronously . Your articles get rendered before the ajax was called and was not re rendered due to asynchrony in setState.
This is what doc(https://reactjs.org/docs/react-component.html#setstate) says
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
You can render the article after successful ajax call like below
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
render(<Article data={data}/>, document.getElementById('...'));
}.bind(this));
}
Because of the post above, I was able to fix the issue by the code example below To see a working example goto the git repo
ApiCalls.articleData()
.then(function(data){
const newData = data.map(c => {
return c.attributes.title;
})
const addElement = newData.map((title, i) => <ContentTiles key={i} title={title}/> );
const newState = Object.assign({}, this.state, {
newData: addElement
});
this.setState(newState);
}.bind(this));
I have the following component:
import React, { Component } from 'react';
import {Link, IndexLink} from 'react-router';
class Navbar extends Component {
renderLinks = (linksData) => {
return linksData.map((linkData) => {
if(linkData.to === '/') {
return(
<div className="navbar-link-container" key={linkData.to}>
<IndexLink activeClassName="navbar-active-link" to={linkData.to}>
<i className="navbar-icon material-icons">{linkData.icon}</i>
<span className="navbar-link-text">{linkData.text}</span>
</IndexLink>
</div>
)
}
else {
return(
<div className="navbar-link-container" key={linkData.to}>
<Link activeClassName="navbar-active-link" to={linkData.to}>
<i className="navbar-icon material-icons">{linkData.icon}</i>
<span className="navbar-link-text">{linkData.text}</span>
</Link>
</div>
)
}
})
};
render() {
return (
<div className={`navbar navbar-${this.props.linksData.length}`}>
{this.renderLinks(this.props.linksData)}
</div>
)
}
}
Navbar.propTypes = {
linksData: React.PropTypes.array.isRequired,
};
export default Navbar;
Now I am trying to write a unit test that will check the if condition (returning IndexLink or Link depending on the .to property):
But I can't seem to test for the exact jsx return of the function, since when I console.log one of the returns I get this:
{ '$$typeof': Symbol(react.element),
type: 'div',
key: '/',
ref: null,
props:
{ className: 'navbar-link-container',
children:
{ '$$typeof': Symbol(react.element),
type: [Object],
key: null,
ref: null,
props: [Object],
_owner: null,
_store: {} } },
_owner: null,
_store: {} }
This is the test I have written so far:
it('renderLinks should return a IndexLink', () => {
const wrapper = shallow(<Navbar linksData={mockLinksData}/>);
const renderLinksReturn = wrapper.instance().renderLinks(mockLinksData);
let foundIndexLink = false;
renderLinksReturn.map((linkHtml) => {
console.log(linkHtml);
});
expect(foundIndexLink).toBe(true);
})
Now I do not know what to test against to see if the function is running correctly. Is there a way to 'mount' the return of the function like a component? Or is there a simple method to return a html string of the actual return that I can check against?
Faced similar issue where we were passing a jsx component to another component as a prop.
You can shallow render the returned jsx since it's like a valid React Function/Stateless Component.
eg:
const renderLinks = shallow(wrapper.instance().renderLinks(mockLinksData))
And continue with your usual enzyme assertions.
To build on top of #Nachiketha 's answer, that syntax won't work when what's returned is a fragment, this can be solved by wrapping the result in a div like:
const renderLinks = shallow(<div>
{wrapper.instance().renderLinks(mockLinksData)
</div>
)}
as suggested in this tread.
I think you don't need to call
const renderLinksReturn = wrapper.instance().renderLinks(mockLinksData);
as it will be called when Navbar will be rendered.
Your solution is correct but in case you want some alternative robust ways to test it.
Since this test specifically tests for IndexLink and assumes that mockLinksData contains to = "/"
it('renderLinks should return a IndexLink when passed a link with to:\"/\"', () => {
const wrapper = shallow(<Navbar linksData={mockLinksData}/>);
// You can use both component name or component displayname
// IndexLink or "IndexLink"
expect(wrapper.find('IndexLink')).to.have.length(1);
// In case you want to test whether indexLink has appropriate props or classes.
const indexLink = wrapper.find(IndexLink).first();
// Check whether indexLink has pass prop
expect(indexLink.props().to).to.equal("/");
// Check whether indexLink has correct classes set.
expect(indexLink.hasClass('navbar-active-link')).to.equal(true);
// Check whether indexLink displays the link test correctly
expect(indexLink.find('navbar-link-text').text()).to.equal(mockLinksData.text);
});
Turns out that the type of the element is stored in the object. So the condition is:
props.children.type.displayName
And the final test I wrote looks like this for IndexLink:
it('renderLinks should return a IndexLink when passed a link with to:\"/\"', () => {
const wrapper = shallow(<Navbar linksData={mockLinksData}/>);
const renderLinksReturn = wrapper.instance().renderLinks(mockLinksData);
let foundIndexLink = false;
renderLinksReturn.map((linkHtml) => {
{linkHtml.props.children.type.displayName === 'IndexLink' ? foundIndexLink = true : null};
});
expect(foundIndexLink).toBe(true);
});
In the following code, when setState is called from campaignsUpdated, render gets logged to the console, but not renderRow:
var React = require('react-native'),
Bus = require('../Bus'),
styles = require('../Styles'),
CampaignsStore = require('../stores/Campaigns'),
CampaignItem = require('./CampaignItem'),
{
Component,
Text,
TextInput,
ListView,
View,
NavigatorIOS,
ActivityIndicatorIOS
} = React
class CampaignList extends Component {
constructor(props) {
super(props)
this.state = {
dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
}
}
componentDidMount() {
this.addListeners()
Bus.emit('campaigns:search', '')
}
componentWillUnmount() {
this.removeListeners()
}
render() {
console.log('render')
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
placeholder='Campaign Name'
value={this.state.campaignName}
onChange={this.campaignSearchChanged.bind(this)}/>
<ListView
dataSource = {this.state.dataSource}
renderRow = {this.renderRow.bind(this)}/>
</View>
)
}
renderRow(campaign) {
console.log('renderRow')
return <CampaignItem campaign={campaign}/>
}
addListeners() {
Bus.on({
'campaigns:updated': this.campaignsUpdated.bind(this)
})
}
removeListeners() {
Bus.off({
'campaigns:updated': this.campaignsUpdated.bind(this)
})
}
campaignsUpdated(event) {
var campaigns = event.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(campaigns)
})
}
campaignSearchChanged(event) {
var campaignName = event.nativeEvent.text
Bus.emit('campaigns:search', campaignName)
}
}
module.exports = CampaignList
What am I doing wrong here?
You are passing ListView a function renderRow that returns a component. You would have to call that function within ListView once it is passed, presumably during a map over campaigns.
By the looks of it the most likely case is that you have a classic React mutability issue here.
I.e. I suspect your 'campaignsUpdated' method is called with either the same Array instance it received last time, or the elements within the list are the same instances.
Try using:
campaignsUpdated(event) {
var campaigns = event.data.slice(); // <-- clone the array
this.setState({
dataSource: this.state.dataSource.cloneWithRows(campaigns)
})
}
If that doesn't work, then you either make the part that manages your list of compaigns create new copies when changes are made (e.g. const clone = {...campaign, title:"A new Title"}) or update your rowHasChanged method to see if the title (or whatever data you need) has actually changed.
Here are two really good videos about immutability in JavaScript here:
https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread
https://egghead.io/lessons/javascript-redux-avoiding-object-mutations-with-object-assign-and-spread