Trying to learn react, following a tutorial. I am having difficulty getting data from my API (mongodb using whatwg-fetch") and rendering the items. The database is running, and no errors in react. The developer tools reports "props object empty", did some research and narrowed it down to the asynchronous nature of react. Apparently, it is rendering before data comes in. However, I have implemented a promise and yet no change in result. Any help will be greatly appreciated.
I have added a promise.
// App.js file
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Product from "../components/product/product"
import HttpService from "../services/http-service";
const http = new HttpService();
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
// Bind functions
this.loadData = this.loadData.bind(this);
this.productList = this.productList.bind(this);
this.loadData()
};
loadData = () => {
let self = this;
http.getProducts().then(data => {
self.setState({products: data})
}, err => {
});
}
productList = () => {
const list = this.state.products.map((product) =>
<div className="inItems" key={product._id}>
<Product title={product.title} price={product.price} imgUrl=
{product.imgUrl} />
</div>
);
console.log(list)
return (list)
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Welcome
</p>
<div className="container App-main">
<div className="items">
<h1>List Of Products</h1>
{this.productList()}
</div>
</div>
</header>
</div>
);
}
}
export default App;
// service.js file
import "whatwg-fetch";
class HttpService {
getProducts = () => {
let promise = new Promise((resolve, reject) => {
fetch("http://localhost:7500/product-list", {mode: "no-cors",
credentials: 'include' })
.then(res => {
resolve(res.json())
}).catch(err => {
if (err) throw err;
reject(err)
})
});
return promise;
}
}
export default HttpService;
Expecting items to display on screen. Nothing displays and no errors in console.
you need to await Data (const data = await http.getProducts()) or simply use axios https://www.npmjs.com/package/axios
.then mean once you receive response, you gonna do something, still the outer scope will keep running and still data is empty
Related
I'm tasked with using react to create our online store. So far I've succesfully called our products using the data from the API we're developing, and I've also been able to pass the data from the mapped product list to a single product page using a link.
Only issue now is that we'd like the single product to appear on the same page as the product list when it's clicked on by the user, perhaps as a component that appears above the product list (as opposed to linking to a separate page). For the life of me I cannot seem to find a method of doing this that doesn't result in an error or the app reading parameters as undefined. Admitedly, I am quite new to React, so it's possible I'm missing something very obvious.
This is the ProductList.js
import React, { useState, useEffect } from 'react';
import SingleProduct from './SingleProduct';
import { Link } from 'react-router-dom';
const API_URL = "http://exampleapiurl/ExampleCollection/Examplecollectionid";
const Products = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
getProducts().then((products) => setProducts(products))
}, []);
const getProducts = () =>
fetch(API_URL)
.then((res) => res.json());
// OnClick Handler
const [isShown, setIsShown] = useState(false);
const handleClick = (e) => {
setIsShown(current => !current);
};
return (
<div className="GetProducts">
<h1> Fetch Products from a Collection </h1>
<div className="container">
<div className="row">
{/* 👇️ Ideally, we'd like the single product item to appear here on button click, as opposed to a separate page */}
{
isShown &&
<SingleProduct/>
}
{products.map((frame) => (
<div>
{/* 👇️ Current link to separate page for product*/}
<Link to={`/SingleProduct/${frame.frameId}`}>
{/* 👇️ Button to show single item on same page as product list.*/}
<button onClick={handleClick} value={frame.frameId} > View {frame.frameName}</button>
<div key={frame.frameId}>
<img src={`https://exampleimageurl/${frame.thumnail}`} />
<li>Frame Name: {frame.frameName}</li>
<li>Gender: {frame.gender}</li>
</div>
</Link>
</div>
))
}
</div>
</div>
</div>
)
}
export default Products;
This is the SingleProduct.js
class SingleProduct extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
item: [],
frameId: this.props.match.params.frameId
}
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(`http://exampleapiurl/${this.state.frameId}`)
.then(response => response.json())
.then(json => {
this.setState({
item: json,
isLoading: false
})
})
}
render() {
const { item } = this.state;
return (
this.state.isLoading ?
(<h1>Loading {this.state.frameId}...</h1>)
:
(
<div>
<div className="col border text-center" key={item.frameId}>
<img src={`https://exampleimageurl/${item.framePic}`} />
<li>Frame Name: {item.frameName}</li>
<li>Gender: {item.gender}</li>
</div>
</div>
)
)
}
}
export default SingleProduct
App.js
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Home } from './components/Home';
import { Layout } from './components/Layout';
import Products from './components/ProductList';
import SingleProduct from './components/SingleProduct';
export default class App extends Component {
static displayName = App.name;
render() {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/ProductList' component={Products} />
<Route path='/SingleProduct/:frameId' component={SingleProduct} />
</Layout>
);
}
}
So if I understand correctly you don't want to use route for passing the data instead of that you can then pass props to the SingleProduct component.
With props getting passed it should look
{
isShown &&
<SingleProduct frameId = {selectedFrameId}/>
}
Declare a new state variable to store the selected frameid
const [selectedFrameId, setSelectedFrameId] = useState<Number>();
Your onclick event will need adjustment, because you will need to pass the frameid in map function.
onClick={() => this.handleClick(frame.frameId)}
and then set the state via handleClick event
const handleClick = (frameId) => {
setIsShown(current => !current);
setSelectedFrameId(frameId);
};
With this in your SingleProduct component the fetch call can directly use the props(frameid)
fetch(`http://exampleapiurl/${this.props.frameId}`)
Also I would recommend to change SingleProduct to a functional component instead of class component.
I have been studying React for past few days. In my blog project, I am using Axios to get data from API. Here is my component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState({});
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
Above code works fine though I noticed the first time it tries to render the component with empty ({}) post object (Due to the default value in 'useState'). However, it causes issues in my child component cause it is directly using 'post' object properties. For example: 'post.content'. Here is my 'SinglePost' component's code:
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>{post.content}</div>
);
};
It returns undefined error for {post.content} object. To resolve the issue I had to use something like {post && <SinglePost post={post} />}, But it doesn't feel right. Is there any better way to handle such scenarios.
Consider revising the PostContent component's rendering logic to account for the case where no post data is present during the network request.
You could for instance initialise your post state to null, and then update the rendered result to prevent the SinglePost component from being rendered while post is null.
Once the network request is completed and the post state is set, the component will re-render causing SinglePost to be rendered with the non-null post state:
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState(null); /* Update: "Initial data" set to null */
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
{ /* <React.Fragment> Not needed */ }
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{ /* Update: If post is null, assume network request is
still in progress so rendering loading message instead
of single post component */ }
{ post === null ? <p>Loading</p> : <SinglePost post={post} /> }
</div>
</div>
</div>
);
};
export default PostContent;
This approach is generally the simplest and more common pattern to async requests and rendering.
There are some other approaches that you might want to consider such as this declarative approach to data fetching, or the use of Reacts suspense feature for asynchronous rendering
You need to make post's initial value an array:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
and in single post map through the array
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>
{post.map((post, key) => (
<div key={key}>{post.content}</div>
))}
</div>
);
};
You can do something like
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPosts(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{this.state.posts.map(post => (<SinglePost post={post} key={post.id} />))
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
I am using google contact api for fetching the contact.
In fetch method's response I am getting all the contacts. But While setting the state using setState its giving an error of Cannot read property 'setState' of undefined. Here is my code.
I had also gone through tutorials for his but not able to find the extact issue in this.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.auth = this.auth.bind(this);
this.state = {
userarray: []
}
}
auth() {
var config = {
'client_id': 'my-client-id',
'scope': 'https://www.google.com/m8/feeds'
};
window.gapi.auth.authorize(config, function () {
alert("Dd")
// this.fetchtt(window.gapi.auth.getToken());
fetch("https://www.google.com/m8/feeds/contacts/default/thin?alt=json&access_token=" + window.gapi.auth.getToken().access_token + "&max-results=700&v=3.0", {
method: 'GET'
})
.then((result) => {
result.json().then((result) => {
// display all your data in console
console.log(result.feed);
result.feed.entry.map((entry, index) => {
console.log(entry.title.$t)
const user = [
name => entry.title.$t
]
this.setState({
userarray: "ss"
});
})
})
}
)
});
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<button onClick={() => this.auth()}>GET CONTACTS FEED</button>
</div>
);
}
}
export default App;
window.gapi.auth.authorize(config, function () { will change the context in the callback use window.gapi.auth.authorize(config, () => { instead.
PS you don't need to nest your then -
.then(result => result.json())
.then(result => { ... })
I'm following this online tutorial on using Fetch to consume API data; I was able to get the application to Fetch data using the JSON url provided in the tutorial. However, I made some modifications to the code and attempted to fetch data from a different JSON file and got an error:
Here is the code:
import React, { Component } from 'react';
import {render} from "react-dom";
import Navbar from '../components/Navbar.jsx';
import Footer from '../components/Footer.jsx';
import './ClientInfo.css';
class ClientInfo extends Component {
constructor(){
super();
this.state= {
titles: []
};
}
componentWillMount(){
fetch('https://jsonplaceholder.typicode.com/todos')
.then(results => {
return results.json();
}).then(data => {
let titles = data.results.map((til) => {
return(
<div key={til.results}>
<p>{til.title} </p>
</div>
)
})
this.setState({titles: titles});
console.log("state", this.state.titles);
})
}
render() {
return (
<div>
<Navbar />
<div className="container">
<div className="clientContainer">
{this.state.titles}
</div>
</div>
<Footer />
</div>
);
}
}
export default ClientInfo
The error occurs at this line:let titles = data.results.map((til) => {. could I get some help as what am doing wrong?
data.results - not array. Array data.map
I'm new to React and Redux, and im trying to load posts from the WordPress REST API, and display them in my React App.
I'm using this as an example: https://github.com/jackreichert/a-wp-react-redux-theme
My action to get the posts look like this:
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export function fetchPosts(post_type = 'posts') {
return function (dispatch) {
axios.get(`http://localhost:8080/wp-json/wp/v2/${post_type}`)
.then(response => {
dispatch({
type: FETCH_POSTS,
payload: response.data
});
});
}
}
It's passed to the reducer, which looks like this:
import { FETCH_POSTS } from '../actions';
export default (state = [], action) => {
switch (action.type) {
case FETCH_POSTS:
return action.payload;
default :
state = '';
break;
}
return state;
}
And (though it's only one reducer) I'm combining it, because there are more reducers to follow (just like in the example), and then I'm storing it. Also pretty much the same way as the example.
Im loading everything in my Home.jsx file, which looks like this right now:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import { Link } from 'react-router-dom';
import Header from '../Header';
import {fetchPosts} from '../../actions/';
class Home extends Component{
componentDidMount(){
document.title = 'Test';
}
componentWillMount() {
this.getPosts(this.props, true);
}
componentWillReceiveProps(nextProps) {
this.getPosts(nextProps);
}
getPosts(props, willMount = false) {
if (willMount) {
this.props.fetchPosts();
}
}
render() {
return(
<div>
<Header/>
<main>
<h1>Home</h1>
</main>
</div>
)
}
}
function mapStateToProps({posts}) {
return {posts};
}
export default connect(mapStateToProps, {fetchPosts})(Home);
I think my code above is right. Redux can also 'find' my posts, and logs in my console:
action FETCH_POSTS # 11:11:56.393
prev state Object {posts: ""}
action Object {type: "FETCH_POSTS", payload: Array(2)}
next state Object {posts: Array(2)}
Now I want to know: How can I simply display the posts which are loaded by Redux, in my Home.jsx file.
And after that: How can I configure the route and data, to go to a single post (but that will come later, for now I only want to know an easy but right way how to display the posts)
I have no idea about the structure of your Post object. But it should be something like this.
renderPosts() {
return _.map(this.props.posts, post => {
return (
<li className="list-group-item" key={post.id}>
{post.title}
</li>
);
});
}
Then in your render method merely call it like this.
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
Also notice that I am using map helper from lodash library here. So you need to have following import statement in your component.
import _ from 'lodash';
Hope this helps. Happy coding !
If you implemented you combineReducers correctly then mapStateToPosts should be called with set of posts when it is changed.
import posts from './posts_reducer';
combineReducers({ posts });
To render posts in your Home component you need to modify the render function, see raw implementation below:
render() {
let postsElements = this.props.posts.map(p => {
return <div>p.title</div>
});
return(
<div>
<Header/>
<main>
<h1>Home</h1>
</main>
<div>
{postsElements}
</div>
</div>
)
}
You need to use this.props.posts to access your posts because react-redux maps properties from state to props object of the component using mapStateToProps function that is provided as a 1st argument when calling connect function.
To improve this you can implement you Blog component and replace div with that.
Also, review how it is implemented in the example you provided https://github.com/jackreichert/a-wp-react-redux-theme/blob/master/src/components/main.js in this file posts are rendered.
import React, { Component } from 'react';
import { connect } from 'react-redux'; // glue between react and redux
import Parser from 'html-react-parser';
import { LinkContainer } from 'react-router-bootstrap';
import { ListGroup, ListGroupItem, Image } from 'react-bootstrap';
class BlogPosts extends Component {
render() {
if (!this.props.posts) {
return (<div></div>);
}
return (
<div className="blog-items" >
<ListGroup>
{this.renderBlogPosts()}
</ListGroup>
</div>
)
};
renderBlogPosts() {
if (this.props.posts.posts) { //v1
const posts = this.props.posts.posts; // DRY
return posts.map((post, index) => {
const excerptText = JSON.stringify(post.excerpt);
const excerpt = excerptText.substring(1, excerptText.length-3);
return (
<LinkContainer className="blog-posts-link" key={post.ID} to={ "/blog/post/" + post.ID + '/'}>
{ this.getHtml(
post.ID,
(post.featured_image) ? post.featured_image : '',
post.title,
excerpt
)}
</LinkContainer>
);
});
};
const posts = this.props.posts; // DRY
return posts.map((post, index) => { // v2
return (
<div key={post.id}>
{this.getHtml(
post.id,
(post._embedded['wp:featuredmedia'])
? post._embedded['wp:featuredmedia'][0].media_details.sizes.thumbnail.source_url : '',
post.title.rendered,
post.excerpt.rendered
)}
</div>
);
});
};
getHtml(id, imageSrc, title, excerpt) {
return (
<ListGroupItem className="blog-posts-list-item">
{this.getImage(imageSrc, title)}
<h2 className="blog-posts-title">{ Parser(title) }</h2>
<div className="blog-posts-excerpt">{Parser(excerpt)}</div>
</ListGroupItem>
);
};
getImage(imageSrc, title) {
return (imageSrc === "")
? (<div></div>)
: (<div className="blog-posts-image-div">
<Image className="blog-posts-image" src={imageSrc} alt={title} />
</div>
);
};
};
const mapStateToProps = (state) => ({ posts: state.posts });
export default connect(mapStateToProps) (BlogPosts);