I'm currently having a problem trying to get UI to render with React. I'm using information I've received from ShopifyAPI and trying to render it to my component. I'm not sure what to do. Do I need to update the state with information returned from my API? Here's my code at the moment.
ShopifyCatalog.js
import React, { Component } from 'react';
import { Link } from 'react-router'
import styles from '../styles';
import ShopProducts from './ShopProducts'
import { getAllProducts } from '../utils/shopifyHelpers';
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: []
}
}
render() {
let allProducts
getAllProducts()
.then((products) => {
return allProducts = products
})
.then((allProducts) => {
allProducts.map((product) => {
<div className='col-sm-offset-1 col-sm-2'>
<Link to={'shop/${product.id}'}>
<img src={product.images[0].src} />
<h5>{product.title}</h5>
</Link>
</div>
})
})
return (
<div style={styles.productInfo}>
{allProducts}
</div>
)
}
}
I thought it might have something to do with using promises more extensively, but I'm pretty sure it's because my state isn't updating with the information that I'm grabbing from the API. I appreciate your time, thank you.
EDIT:
I've updated my code now and it looks like this
ShopCatalog.js Updated
import React, { Component } from 'react';
import { Link } from 'react-router'
import styles from '../styles';
import ShopProducts from './ShopProducts'
import { getAllProducts } from '../utils/shopifyHelpers';
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: [],
listAllProducts: []
}
}
componentDidMount() {
getAllProducts()
.then((products) => {
this.setState({
allProducts: products
})
})
}
render() {
return (
<div style={styles.productInfo}>
{this.state.allProducts.map((product) => {
<h1>{product.title}</h1>
})}
</div>
)
}
}
But it's still not rendering anything from the map of my state. Is it because map is called while there is nothing in the state? How do I work around this so map get's called and returns UI? Thank you.
Put your request in the componentDidMount lifecycle method, then update your state. Your render method is returning before your request has completed.
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: []
}
}
componentDidMount() {
const _this = this;
getAllProducts()
.then((products) => {
_this.setState({ allProducts: products });
});
}
render() {
return (
<div style={styles.productInfo}>
{this.state.allProducts.map((product) => {
<div className='col-sm-offset-1 col-sm-2'>
<Link to={'shop/${product.id}'}>
<img src={product.images[0].src} />
<h5>{product.title}</h5>
</Link>
</div>
})}
</div>
)
}
}
I assume something like this, not sure specifics to your case, just giving idea how this should look like.
export default class ShopCatalog extends Component {
state = {
allProducts: []
}
getAllProducts = () => {
fetch(...API).then(response => response.json()).then(products =>
this.setState({allProducts: products}));
}
componentDidMount() {
this.getAllProducts()
}
render() {
const {allProducts} = this.state;
return (
<div>
{allProducts.map((product,key) => <div key={key}>
<span>{product.title}</span>
</div>
)}
</div>
)
}
}
Related
inside componentDidMount Im calling the dispatched fetchReviews. but once mounted how does the fetched reviews get set in props?
business show component:
import React from "react";
import { Link } from 'react-router-dom';
import Star from "../star/star";
class BusinessShowIndex extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true
}
}
componentDidMount() {
this.props.fetchReviews(this.props.business.id)
// .then (() => this.setState({reviews: this.props.reviews}))
this.setState({loading: false})
console.log(this.props)
}
render() {
const { business, reviews } = this.props;
if (!this.props.reviews) return null;
if (this.state.loading === true) {
return <p>Loading...</p>
}
return (
<div className="business-show">
<Link to={`/business/${business.id}`} className='link-business-index'>
<img className="business-index-photo" src={business.photo_urls[0]} alt=""/>
<p className="business-index-name">{business.name}</p>
<Star reviews={reviews}/>
<p className="business-index-city">{business.city}</p>
<p className="business-index-cost">Cost: {business.cost}</p>
<p className="business-index-hours">Hours: {business.open} - {business.close}</p>
</Link>
</div>
)
}
};
export default BusinessShowIndex;
container:
import { connect } from 'react-redux';
import {fetchReviews} from '../../actions/review_actions';
import { withRouter } from 'react-router-dom';
import BusinessShowIndex from './business_show_index';
const mapStateToProps = (state, ownProps) => ({
business: state.entities.businesses[ownProps.business.id],
currentUser: state.entities.users[state.session.id],
reviews: Object.values(state.entities.reviews)
})
const mapDispatchToProps = dispatch => ({
fetchReviews: (businessId) => dispatch(fetchReviews(businessId))
})
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BusinessShowIndex));
let me know what else you need to see! thank you!
also any advice to clean code? taking any suggestions.
Inside componentDidMount Im calling the dispatched fetchReviews. but once mounted how does the fetched reviews get set in props?
You can get using this.props.review
Check your mapStateToProps function. You get the entire state there and it returns whatsever part of it you want to return.
I'm trying to call a dictionary from Django Rest Framework API to view on my frontend. Using Django backend & Reactjs frontend. Through some research looks like i'm getting this error due to the map() function only accepting arrays, while my API is returning a dictionary (I THINK SO).
How do I fix this? I'm new to javascript & apologies in advance for the messy code. Please see my App.js below:
App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todoList: [],
}
this.fetchTasks = this.fetchTasks.bind(this)
};
componentWillMount() {
this.fetchTasks()
}
fetchTasks() {
fetch('http://127.0.0.1:8000/api/api-overview')
.then(response => response.json())
.then(data =>
this.setState({
todoList: data
})
)
}
render() {
var tasks = this.state.todoList
return (
<div className="container">
{tasks.map(function (task, index) {
return (
<div className="center-column">
<div className="item-row">
<div key={index} className="centered">
<span>{task.bitcoin_symbol}</span>
</div>
</div>
</div>
)
})}
</div>
);
}
}
export default App;
API response:
You're fetching a single object, not an array. .map() is a method which run over iterables(arrays, strings, etc - objects, that can be iterated over) and creates a new output element from each input one. In react we mainly use it to convert an item to its JSX(react/html) representation. As you're working over a single object, you should access it directly:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
bitcoinData = null
}
this.fetchBitcoinData = this.fetchBitcoinData.bind(this);
};
componentWillMount() {
this.fetchBitcoinData();
}
fetchBitcoinData() {
fetch('http://127.0.0.1:8000/api/api-overview')
.then(response => response.json())
.then(data =>
this.setState({
bitcoinData: data
});
);
}
getBitcoinRepresentation() {
var bitcoinData = this.state.fetchBitcoinData;
if (!bitcoinData) {
return <div>Loading...</div>;
}
else {
return (
<div className="container">
<div>{bitcoinData.bitcoin_symbol}</div>
<div>{bitcoinData.bitcoin_price}</div>
<div>{bitcoinData.bitcoin_dailychangeinprice}</div>
</div>
);
}
}
render() {
return getBitcoinRepresentation();
}
}
export default App;
Yo guys, getting error 'contacts.map is not a function' not sure why is that ? just starting in react maybe missing something obvious. I'm getting the data when I console log all good.
code below:
import React, { Component } from 'react'
import axios from 'axios';
class Contacts extends Component {
constructor(){
super();
this.state = {
contacts: [],
}
}
componentDidMount(){
axios.get('url')
.then(response => {
this.setState({ contacts: response.data });
})
.catch(function (error) {
console.log(error);
})
}
render() {
const { contacts } = this.state
return(
<div>
{contacts.map(contact => (
<h1>contact.hello</h1>
))}
</div>
)
}
}
export default Contacts;
Apparently its an object not an array...
How can i render this object then?
It has one property for now but will have more later on: tried JSON.stringify(obj)
{hello: "test"}
The problem is that you set contacts to response.data, which evidently it's not an array.
componentDidMount fires after the component is mounted and tries to get the string 'url'. When state is updated, the component is redrawn and it gives the error.
Since the contacts is an object I would recommend you to do Object.keys and then .map on it so that you can get object keys and it’s values.
One more thing never forget to add unique key to the parent jsx element when you iterate array of data or an object like below.
<div>
{Object.keys(contacts).map((name, index) => (
<h1 key={'Key'+index}>{contacts[name]}</h1>
))}
</div>
From react docs:
Note:
These methods are considered legacy and you should avoid them in new code:
UNSAFE_componentWillMount()
When you want to wrap an object you can simply wrap it in brackets
class Contacts extends Component {
constructor() {
super();
this.state = {
contacts: [],
}
}
componentDidMount() {
axios.get('url')
.then(({ data }) => {
this.setState({ contacts: [data] });
})
.catch((error) => {
console.log(error);
});
}
render() {
const { contacts } = this.state;
return (
<div>
{contacts.map(contact => (
<h1 key={/* unique key */}>contact.hello</h1>
))}
</div>
);
}
}
Use async await to get the response before the component is mounted
import React, { Component } from 'react'
import axios from 'axios';
class Contacts extends Component {
constructor(){
super();
this.state = {
contacts: [],
}
}
async componentWillMount(){
const response = await axios.get('url')
this.setState({ contacts: response.data })
}
render() {
const { contacts } = this.state
return(
<div>
{contacts.map(contact => (
<h1>contact.hello</h1>
))}
</div>
)
}
}
export default Contacts;
topNavigation.js
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}> <button className="dropbtn ">{navList.name}</button>
<SubMenu below={this.props.navList.catalogGroupView}/>
</li>
</ul>
)
})
}
}
export default Navigation;
I'm new to react and trying to make an ecommerce website. I have designed the homepage. For the navigation, I have used an external api
http://149.129.128.3:3737/search/resources/store/1/categoryview/#top?depthAndLimit=-1,-1,-1,-1
and mapped the response.
If I use the below code in place of
SubMenu component it works
<ul>
{console.log(navList.catalogGroupView)}
{
navList.catalogGroupView.map(sub=> {
return <li key={sub.uniqueID}> <button>{sub.name}</button></li>
})
}
</ul>
But as per the url endpoint response there are more sub categories which I'm unable to map.
I thought of creating a separate component to display the sub menu items. But whenever I use this code it doesn't work.
Submenu.js component
import React, { Component } from 'react';
class SubMenu extends Component {
constructor(props) {
super(props);
this.state = {
subCategory: []
};
}
render() {
return subCategory.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
Can somebody please help me on this. I would be grateful. I'm not getting where I got wrong.
You're trying to access subCategory of state. Here is the working part:
import React, { Component } from 'react';
class SubMenu extends Component {
constructor(props) {
super(props);
this.state = {
subCategory: []
};
}
render() {
const { subCategory } = this.state; // you should've add this part
return subCategory.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
UPDATE
Over here:
<SubMenu below={this.props.navList.catalogGroupView}/>
You're trying to access prop, which doesn't exist, so you should do it that way:
<SubMenu below={navList.catalogGroupView}/>
And then In Submenu, you should map over the props, not state, because your state always will be empty array(because you're not setting it). And you don't need to use state in that case, because you just need to iterate over array. So here is the code of Submenu:
import React, { Component } from 'react';
class SubMenu extends Component {
render() {
const {below} = this.props;
return below.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
And theoretically working code of topNavigation.js:
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}> <button className="dropbtn ">{navList.name}</button>
<SubMenu below={navList.catalogGroupView}/>
</li>
</ul>
)
})
}
}
export default Navigation;
Hope this helps.
I am making a basic dropdown selector. I almost had it working when I realized I was setting the state in both the parent and the child so I refactored again to try to simplify it all and put most of the responsibility in one place.
My logic is in the MyDropDown component, then I have a Header component, then the Main which should render it all.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
Then I have my main component where I do the api call and pass the state into the dropdown component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} />;
}
}
export default UserHeader;
And finally my Main Component, where I want to show the value from the selected dropdown menu
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
What I tried doing is moving the statement
I should get the dropdown value here: {this.state.policies} .
into the UserHeader component. How do I get the value selected in the child back up to its parent?
Another thing I've tried is adding a handler to the child component
onChange = e => {
this.setState({ selectedUser: e.target.value });
};
and add it to the select... but again not sure how to get this value up to the parent.
return <select onChange={this.onChange}>{alphabetizeUsers}</select>;
The easiest way to pass the value back to the parent component is through a callback.
Try defining and passing in an onChange={this.onChange} to your Main component like so your Main component becomes:
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
this.state = {
user: null,
}
constructor(props) {
super(props);
this.onChangeUser = this.onChangeUser.bind(this);
}
onChangeUser(newUser) {
this.setState({ user: newUser });
}
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader onChangeUser={this.onChangeUser} />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
Now you are passing in a callback, you can do the same thing with your UserHeader component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} onChange={this.props.onChangeUser} />;
}
}
export default UserHeader;
And finally, you can now attach this callback to your <select> element.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select onChange={(ev) => this.props.onChange(ev.target.value)}>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
By defining the onChange on your select element like this, onChange={(ev) => this.props.onChange(ev.target.value)}, you can return the value to the main component and use it in your state.