Fetch API Console data on webpage using axios - javascript

I created a simple date picker react js, after that I call API and get some data from API in the console, now I want to fetch API data on the web page.
Here is the code i used to call API function, I want map response data on a web page
import React, { Component } from 'react'
import axios from 'axios'
class PostForm extends Component {
constructor(props) {
super(props)
this.state = {
key: '',
}
console.log(this.state)
}
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value })
}
submitHandler = e => {
e.preventDefault()
axios
.get(`http://127.0.0.1:8000/hvals_hash?key=${this.state.key}`)
.then(response => {
console.log(response.data)
})
.catch(error => {
console.log(error)
})
}
render() {
const { key } = this.state
return (
<center><div>
<form onSubmit={this.submitHandler}>
<div>
<h2> DATE PICKER</h2><br></br>
<input
type="text"
name="key"
value={key}
onChange={this.changeHandler}
/>
</div>
<br></br>
<button type="submit">Submit</button>
</form>
</div></center>
)
}
}
export default PostForm

Here is the explanation for this issue you can refer here,
import React, { Component } from 'react'
import axios from 'axios'
class PostForm extends Component {
constructor(props) {
super(props)
this.state = {
key: '',
// Where data will be saved.
data: [],
}
console.log(this.state)
}
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value })
}
submitHandler = e => {
e.preventDefault()
axios
.get(`http://127.0.0.1:8000/hvals_hash?key=${this.state.key}`)
.then(response => {
// Updating the state to trigger a re-render
this.setState({data: response.data});
console.log(response.data)
})
.catch(error => {
console.log(error)
})
}
render() {
const { key } = this.state
return (
<center><div>
<form onSubmit={this.submitHandler}>
<div>
<h2> DATE PICKER</h2><br></br>
<input
type="text"
name="key"
value={key}
onChange={this.changeHandler}
/>
</div>
<br></br>
<button type="submit">Submit</button>
</form>
<div>
{this.state.data.map((dataObjectToDisplay) => {
return (
<div>
{
<ol>{this.state.data}</ol>
}
</div>
);
})}
</div>
</div></center>
)
}
}
export default PostForm
add a state (add an object property to this.state) and inside the submit handler, after getting the response in Axios, use setState({[name of state]: response.data }).
That takes care of updating the state. As for where to display them, it is up to you.
You can copy the code anywhere inside the render method. If u want to display it under the form, paste it inside the div container after the form.
For better controlling of the state, you can check one of the state-management tools like redux or simply use global context and implement routing using react-router –
Credits to #MrCeRaYA

First you have to save the data somewhere inside the app. Create a state and use setState to update it. Now that you have the latest data, you can use JSX's magic to display them using map.
Example:
<div>
{this.state.data.map((dataObjectToDisplay) => {
return (
<div>
{/* Include your data in a way you want here, you can also replace the div with any other element (li) */}
</div>
);
})}
</div>

<div>
{this.state.data.map((dataObjectToDisplay) => {
return (
<div>
{/* Include your data in a way you want here, you can also replace the div with any other element (li) */}
</div>
);
})}
</div>

Related

How to solve Cannot read property 'map' of undefined error in React js?

I am building a react js application with dummy backend using json-server.I am implementing a dropdown list but I am getting Cannot read property 'map' of undefined error on fetching from api.The api is working fine in browser but while fetching in react it is giving me this error.
My component:
import React from 'react';
var values;
class ApiDropDown extends React.Component {
constructor(){
super();
this.state = {
options: []
}
}
componentDidMount(){
this.fetchOptions()
}
fetchOptions(){
fetch('http://localhost:5000/groups')
.then((res) => {
return res.json();
}).then((json) => {
values = json;
this.setState({options: values.groups})
console.log(values);
});
}
render(){
return <div>
<select>
{ this.state.options.map((option, key) => <option key={key} >{option}</option>) }
</select>
</div>;
}
}
export default ApiDropDown;
My db.json for dummy backend:
{
"groups":[
{
"key":"version",
"apis":"system_info"
},
{
"key":"admin",
"name":"patients"
},
{
"id":"admin2",
"name":"doctors"
}
]
}
Here's how I am rendering my ApiDropDown component:
return (
<form className="add-form" onSubmit={onSubmit}>
<div>
<ApiDropDown/> //ApiDropDown Component
</div>
<div >
<input className="clientparams"
type="text"
placeholder="Add Client"
value={client_name}
onChange={(e) => setText(e.target.value)}
/>
</div>
</form>
Can someone help me with this error?
Two things that should be correct.
{ this.state.options.map((option, key) => <option key={key} >{option}</option>) } In this you are trying to bind the object as react child, so instead of this it should be {{ this.state.options.map((option, key) => <option key={key} >{option["apis"]}</option>) }
You no need to declare a variable to store the fetch values you can directly set it to state like this. this.setState({options: json.groups})
Here is the working example of your code(fetch call is different): https://codesandbox.io/embed/nifty-lamport-mzmro?fontsize=14&hidenavigation=1&theme=dark
You're not using fetch right:
fetch('http://localhost:5000/groups').then((res) => {
res.json().then((json) => {
values = json;
this.setState({options: values.groups})
console.log(values);
})
})

Confusion on React Callback's on Forms

So I am attempting to learn react along with rails (Using rails purely as an API). Im making a simple to-do app and getting stuck when attempting to "Create" a list.
I have a "New List" component shown here, mostly taken from the react forms tutorial:
import React, { Component } from 'react';
import axios from 'axios';
class ListForm extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({[e.target.name]: e.target.value});
}
handleSubmit(e) {
console.log("Form submitted with: " + this.state.value)
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Title:
<input name="title" type="text" value={this.state.value} onChange={this.handleInputChange} />
</label>
<label>
Description:
<textarea name="description" value={this.state.value} onChange={this.handleInputChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default ListForm;
I have my ListContainer shown here
import React, { Component } from 'react';
import axios from 'axios';
import List from './List';
import ListForm from './ListForm'
class ListContainer extends Component {
constructor(props){
super(props)
this.state = {
lists: []
}
this.addNewList = this.addNewList.bind(this)
}
componentDidMount() {
axios.get('/api/v1/lists.json')
.then(response => {
console.log(response)
this.setState({
lists: response.data
})
})
.catch(error => console.log(error))
}
addNewList(title, description) {
axios.post('/api/v1/lists.json', {
title: title,
description: description
})
.then(function (response) {
console.log(response);
const lists = [ ...this.state.lists, response.data ]
console.log(...this.state.lists)
this.setState({lists})
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="lists-container">
{this.state.lists.map( list => {
return (<List list={list} key={list.id} />)
})}
<ListForm onSubmit={this.addNewList} />
</div>
)
}
}
export default ListContainer;
My issue comes from a misunderstanding at the callback on submit. I understand that when I do an "onSubmit" on the form it's using addNewList function as a callback....but I really am not understanding how the connection from state in the ListForm is getting into that callback function. I obviously am doing something wrong because it does not work and currently console shows "Form submitted with: undefined" so it's not passing parameters correctly at all.
Im still pretty new to React and very rusty with JS (It's been a bit since i've used it so im sure there are some newbie mistakes here). Also axios is basically a "better" fetch fwiw.
I won't lie either, I don't exactly understand why we do this.handleSubmit = this.handleSubmit.bind(this); for example (along with the other similar ones)
You're so close! We just need to make a few adjustments.
First let's lose the bind statements, and just use arrow functions. Arrow functions have no this object, and so if you call on this inside that function, you will actually access the this object of your class instance. Neat.
Second, let's fix the typo on your handleChange function, so that your inputs are updating the component state properly.
Now, the real solution to your problem. You need to call the addNewList function in your parent component, ListContainer. How do we do that? Lets pass it down to the child component as a prop! You're almost there, but instead of using the keyword onSubmit={this.addNewList}, lets use something like handleSubmit instead. This is because onSubmit is actually a special keyword that will attach an event listener to the child component for submit, and we don't want that.
Now that your child component is taking in your function as a prop. We can call it inside the handleSubmit function. We then pass in the arguments, title and description. Now your child component is able to call the addNewList function in the parent component!
import React, { Component } from 'react';
import axios from 'axios';
import List from './List';
import ListForm from './ListForm'
class ListContainer extends Component {
constructor(props) {
super(props)
this.state = {
lists: []
}
this.addNewList = this.addNewList.bind(this)
}
componentDidMount() {
axios.get('/api/v1/lists.json')
.then(response => {
console.log(response)
this.setState({
lists: response.data
})
})
.catch(error => console.log(error))
}
addNewList(title, description) {
axios.post('/api/v1/lists.json', {
title: title,
description: description
})
.then(function (response) {
console.log(response);
const lists = [...this.state.lists, response.data]
console.log(...this.state.lists)
this.setState({ lists })
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="lists-container">
{this.state.lists.map(list => {
return (<List list={list} key={list.id} />)
})}
<ListForm handleSubmit={this.addNewList} />
</div>
)
}
}
export default ListContainer;
import React, { Component } from 'react';
import axios from 'axios';
class ListForm extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: ''
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
handleSubmit = (e) => {
this.props.handleSubmit(this.state.title, this.state.description);
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Title:
<input name="title" type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<label>
Description:
<textarea name="description" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
I think you have a typo as well. For the change event you have
<input name="title" type="text" value={this.state.value} onChange={this.handleInputChange} />
So the callback for change is this.handleInputChange. But in your code its called handleChange
But even if you've used the right naming, it will not work, because you need to bind that function as well.
That brings me to your question about this.handleSubmit = this.handleSubmit.bind(this);
The problem here is that when you pass a function as a callback it loses its context. Consider the following
const x = {
log: function() { console.log(this.val) },
val: 10
}
Now you can do
x.log(); // -> print 10
But when you do
y = x.log;
y(); // -> prints undefined
If you pass only the function around it looses its context. To fix this you can bind
x.log = x.log.bind(x);
y = x.log
y(); // -> prints 10
Hope this makes sense :)
Anyway, to come back to you question, you don't have to use bind, there is a better way
class ListForm extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: ''
};
}
handleChange = (e) => {
this.setState({[e.target.name]: e.target.value});
}
handleSubmit = (e) => {
console.log("Form submitted with: " + this.state.value)
e.preventDefault();
}
Although not tests, it might work right now!
Change value property of the inputs with this.state.title & this.state.description:
<input name="title" type="text" value={this.state.title} onChange={this.handleInputChange} />
<textarea name="description" value={this.state.description} onChange={this.handleInputChange} />
try printing info using
console.log("Form submitted with: ", this.state)
Regards to the .bind(this) :
When any event fires, an event object is assigned to callback function. So callback function or event handlers loses reference to class and this points to whichever event has been called.
Bind creates a new function that will have this set to the first parameter passed to bind()
Apart from .bind(this) arrow function takes care for this. but it's not preferable for long hierarchy.

How to use setState with fetch API on form submit in React?

I'm new to React and wanted to play around with the fetchAPI. I want to use the GoogleBooks API to display a list of books that matches a query string which the user can enter in an input field.
I've managed to make the call to the Google API and have used setState but I can't get the state to stick. If I want to render the state after fetching the data, the state appears as undefined.
I have a feeling that React renders the HTML first and then sets the state after making the API call.
I'd appreciate if you can have a look at my component.
Thank you!
import React, {Component} from 'react'
class Search extends Component{
constructor(props){
super(props)
this.state = {
books: []
}
this.books = this.state.book
this.title = ''
this.handleChange = (e) => {
this.title = e.target.value
}
this.handleSubmit = (e) => {
let results = '';
e.preventDefault();
const apiKey = 'AIzaSyBuR7JI6Quo9aOc4_ij9gEqoqHtPl-t82g'
fetch(`https://www.googleapis.com/books/v1/volumes?q=${this.title}&key=${apiKey}`)
.then(response => response.json()).then(jsonData => {
this.setState({
books: jsonData
})
console.log(jsonData)
})
}
}
render(){
return(
<div className="col-md-12">
<form onSubmit={this.handleSubmit} className="form-group">
<label>Enter title</label>
<input onChange={this.handleChange} className="form-control" ref="title" type="text" placeholder="Enter title"></input>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
<div>
<li>this.state.books</li>
</div>
</div>
You have:
<li>this.state.books</li>
This is just rendering a string.
Instead, you can use map to show data from book you get from the response.
Here's a working example:
<div>
{
this.state.books.items &&
this.state.books.items.map(book => <li>{book.etag}</li>)
}
</div>

React passing fetched data to another component

Hy!
I am having an issue with my react code. My task is to call from iTunes API which i do with fetch then I process the data. But I cannot save it as a variable to be able to pass it around later.
import React, { Component } from 'react';
class SearchField extends Component{
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
fetch(`https://itunes.apple.com/search?media=music&term=${this.state.value.toLowerCase()}`)
.then((resp) => resp.json())
.then(searchRes => searchRes.results[0].artistName)
.catch(err => console.log(err));
}
render() {
return(
<section className="hero is-primary">
<div className="hero-body">
<div className="container">
<form onSubmit={this.handleSubmit}>
<input className="input is-primary" type="text" value={this.state.value} onChange={this.handleChange} placeholder="Search for artist" />
<input className="button is-info" type="submit" value="Search" />
</form>
</div>
</div>
</section>
)
}
}
export default SearchField;
I'd have to use the fetched data later, i just need the artist name first.
If I log the value (searchRes.results[0].artistName, i get the correct value, but if i want to save it for later use i only got empty console.log back.
I've tried several approaches but I never get my result back.
Help me out please.
Remember that data flow in React is unidirectional. If you want to share the data around your app the search component should not be the component that fetches the data. That should be left to a parent component (maybe App). That component should have a function that handles the fetch request, and you can then pass a reference to that function down to the search component to call when the button is clicked. Then, once that data is loaded, the parent (App) component can pass all the relevant data down to the child components.
Here's a quick mock-up based on your existing code:
class Search extends {
constructor(props) {
super(props);
this.state = { url: '' };
this.handleKey = this.handleKey.bind(this);
}
handleKey(e) {
const url = e.target.value;
this.setState({ url });
}
render() {
const { url } = this.state;
// grab the function passed down from App
const { fetchData } = this.props;
return (
<input onKeyUp={this.handleKey} value={url} />
// Call that function with the url when the button is clicked
<button onClick={() => fetchData(url)}>Click</button>
)
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.fetchData = this.fetchData.bind(this);
}
// App contains the fetch method
fetchData(url) {
fetch(url)
.then(res => res.json())
// Update the App state with the new data
.then(data => this.setState({ data });
}
render() {
const { data } = this.state;
// Sanity check - if the state is still empty of data, present
// a loading icon or something
if (!data.length) return <Spinner />
// otherwise return the rest of the app components
// passing in the fetch method as a prop for the search component
return (
<OtherComponent data={data} />
<Search fetchData={this.fetchData} />
)
}
}
Please specify what you mean by
but if i want to save it for later use i only got empty console.log back
I think the correct way to handle your problem is by passing a callback function to your component's props that gets called whenever you press search and a search result is found, like this: https://codesandbox.io/s/xpq171n1vz
Edit: Note that while this answer has been accepted and is a way to solve your problem, Andy's answer contains solid and elaborate advice on how to actually structure your components.

(ReactJS) List of items doesn't get updated on page, even though database gets updated

I have this single page application that uses firebase and reactjs/nodejs that updates/removes/adds html code (body and description). The functions work fine and the data gets updated accordingly in the database. However, the list doesn't refresh once I do any function (only if I press F5 manually). I want the list to dynamically change as I add/edit/delete any of the content. How can I do this?
Here's my code:
HTML.js:
const updateByPropertyName = (propertyName, value) => () => ({
[propertyName]: value,
});
class HTML extends Component {
constructor(props) {
super(props);
this.state = {
html: []
};
}
componentDidMount() {
db.onceGetHTML().then(snapshot =>
this.setState(() => ({ html: snapshot.val() }))
);
}
render() {
const { html } = this.state;
const { description } = this.state;
const { body } = this.state;
return (
<div>
<h1>Home</h1>
<p>The Home Page is accessible by every signed in user.</p>
<input value={description}
onChange={event => this.setState(updateByPropertyName('description', event.target.value))}
type="text"
placeholder="Description..."
/>
<input value={body}
onChange={event => this.setState(updateByPropertyName('body', event.target.value))}
type="text"
placeholder="Body..."
/>
<button onClick={() => addHTML(description, body)}>Add Content</button>
{!!html && <HTMLList html={html} />}
</div>
);
}
}
These are all in one file, I just split them to make it easier to read (HTML.js):
function addHTML(description, body, callback) {
addAnHTML(description, body);
}
And here's the 2nd class in the same file that is responsible for displaying the list of items:
class HTMLList extends Component {
constructor(props) {
super(props);
this.state = {
BODY: '',
desc: '',
html: ''
};
}
render() {
const { html } = this.props;
const { desc } = this.state;
const { BODY } = this.state;
return (
<div>
<h2>List of HTML available:</h2>
{Object.keys(html).map((key, index) =>
<div>
{index + 1}.
{html[key].description}
<img src="http://www.stilltimecollection.co.uk/images/english/b_delete.gif" onClick={() => deleteHTML(key)} />
<Popup trigger={<img src="https://www.faktorzehn.org/de/wp-content/uploads/sites/2/2015/03/f10-org-new_3_6_0-edit.gif" />
} position="right center">
<div>
<input value={desc}
onChange={event => this.setState(updateByPropertyName('desc', event.target.value))}
type="text"
placeholder="Descripton.."
/>
<input value={BODY}
onChange={event => this.setState(updateByPropertyName('BODY', event.target.value))}
type="text"
placeholder="Body..."
/>
<button onClick={() => updateHTML(key, desc, BODY)}>Update Content</button>
</div>
</Popup>
<br></br>
</div>
)}
</div>
);
}
}
The function addAnHTML is in a different file where the database is synchronized:
export const addAnHTML = (description, body) => {
var html =
{
description: description,
body: body,
created_at: format.asString(),
updated_at: ""
}
db.ref('Content').push(html);
alert("Content Added");
}
My page looks like this:
https://preview.ibb.co/fTwiaT/Untitled.png
My database looks like this (added in db but not dynamically):
https://image.ibb.co/nNEapo/database.png
EDIT: so here's the edit on the functions I use:
export const onceGetHTML = () =>
db.ref('Content').once('value');
export const addAnHTML = (description, body) => {
console.log(description);
console.log(body);
var html =
{
description: description,
body: body,
created_at: format.asString(),
updated_at: ""
}
db.ref('Content').push(html);
}
and the edited add function in my class looks like this:
addContent(description, body) {
this.setState({
html: [
...this.state.html,
{
description: this.state.description,
body: this.state.body
}
]
});
addAnHTML(this.state.description,this.state.body);
}
Snapshot.val() contains all the child values of my "Content" parent:
https://preview.ibb.co/jetOc8/edit.png
I will showcase how to correctly map your data to the DOM for the "add" functionality.
Here is the modified HTML class
export class HTML extends Component {
constructor(props) {
super(props);
this.state = {
html: [],
// if you keep your user inputted data in the DOM state, it's good to initialize them first, otherwise your component will suddenly change from an uncontrolled component to a controlled component.
description: "",
body: ""
};
}
componentDidMount() {
// EDIT: code here for initializing `html`
db.onceGetHTML().then(snapshot =>
// EDIT: it's simpler to just call setState with the new state object you want
this.setState({ html: snapshot.val()})
);
}
// use class methods to update your DOM state, because inside these methods
// also don't forget to call your API here
addContent(description, body) {
// EDIT: make sure to make your call to the db for adding an entry
this.setState({
// please keep in mind that you should NEVER mutate your state, so in here I'm using the spread operator to create a new array instance
// this is appending a new entry to the old "html"
html: [
...this.state.html,
{
description: this.state.description,
body: this.state.body
}
]
});
}
// use this to update the state for each input field
updateByPropertyName(property, e) {
this.setState({
[property]: e.target.value
});
}
render() {
const { html } = this.state;
const { description } = this.state;
const { body } = this.state;
return (
<div>
<h1>Home</h1>
<p>The Home Page is accessible by every signed in user.</p>
<input
value={description}
onChange={this.updateByPropertyName.bind(this, "description")}
type="text"
placeholder="Description..."
/>
<input
value={body}
onChange={this.updateByPropertyName.bind(this, "body")}
type="text"
placeholder="Body..."
/>
// this onClick event will call "this.setState(...)" which will trigger the re-render (or the "refresh" you are looking for)
<button onClick={this.addContent.bind(this)}>Add Content</button>
{!!html && <HTMLList html={html} />}
</div>
);
}
}
This will be enough for your successful re-render.
On a side note, it's also useful to index your elements in the list. It's really useful for React to render efficiently. For that, you can modify your HTMList class like so:
//...
<h2>List of HTML available:</h2>
{Object.keys(html).map((key, index) => (
// using the index parameter supported in the .map callback it's okay
// because it makes your DOM list nodes predictable and easy for React to re-render
<div key={index}>
{index + 1}.
{html[key].description}
<img
//...
For the update and delete you can follow the same pattern, only change the state (in which you hold the html object) accordingly.
You can use these readings for the stuff I just explained above
controlled vs uncontrolled components
handling lists components in React

Categories

Resources