React API response through form onSubmit - javascript

I'm trying to make an API call with a User's input and store the response to display it.
The steps I'm taking:
storing the user's input through onChange="handleLocationChange"
sending the input through the form's onSubmit={this.handleSubmit}
then calling const weatherData = GetAPIData(API_KEY, this.state.cityID); to fetch API data
in the GetAPIData component I create the hooks const [weatherData, setWeatherData] = useState([]); and then return the json API response in the weatherData hook
I'm breaking a hooks rule (I believe my error is in trying to call a hook after rendering) and getting the following error, but I haven't been able to figure out how to solve for this error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component
Class component (main file)
import GetAPIData from "./GetAPIData";
class IOData extends Component {
constructor(props) {
super(props);
this.state = {
country: "",
city: "",
cityID: "",
APIData: "",
};
}
handleLocationChange = (event) => {
this.setState({
city: event.target.value,
});
};
handleSubmit = (event) => {
event.preventDefault();
const API_KEY = "*insert key value*";
this.setState({cityID: "6533961"}); // I think this is also incorrect since it is an event
const response = GetAPIData(API_KEY, this.state.cityID);
this.setState({APIData: response});
};
render() {
const { country, city } = this.state;
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>City:</label>
<input
type="text"
name="city"
value={city}
onChange={this.handleLocationChange}
/>
<button type="submit">Go!</button>
</form>
{APIData}
</div>
);
}
}
Function Component that retrieves the API response (second file, breaks here...)
const GetAPIData = (API_KEY, cityID) => {
const [weatherData, setWeatherData] = useState([]); // I believe it breaks here
const endpoint = `http://api.openweathermap.org/data/2.5/weather?id=${cityID}&appid=${API_KEY}`;
useEffect(() => {
fetch(endpoint)
.then(response => response.json())
.then(data => {
setWeatherData(data);
})
}, [])
return weatherData;
};
Does the solution have to do with having to re-render the class component to make the hooks work? How can I make the API response go through without an error?

Related

Waiting for async function in React component & Showing Spinner

Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};

How to make a POST request with input text as data React

I am new to react and I am trying to make a POST request using text field data, can anyone help me with how to store that input and make a request after a button is pressed.
I attempted to use useRef() which allowed me to obtain the data however I was not able to store it as a data object to then persist.
Currently my data persists, however it persists an empty object and the state is not being updated.
If anyone can help, I will really appreciate that.
Below is my App.js class
import React, { useState, useEffect, useRef, Component } from 'react';
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists"
});
class App extends Component {
state = {
artists: [],
theArtistName: ""
}
constructor(props){
super(props);
this.getArtists()
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () =>{
let data = await api.get("/").then(({ data }) => data);
this.setState({artists: data}) //setting our artists to be the data we fetch
}
createArtist = async () =>{
let response = await api.post('/', {name: this.state.theArtistName})
console.log(response)
this.getArtists()
}
deleteArtist = async (id) =>{
let data = await api.delete('/${id}')
this.getArtists();
}
handleAddArtist = (event) =>{
event.preventDefault()
this.setState({
theArtistName: event.target.value
})
const data = this.state.theArtistName
console.log(data)
}
componentDidMount(){
this.createArtist()
}
render(){
// const {theArtistName} = this.state
return(
<>
<input type={Text} placeholder="Enter Artist Name" name="theArtistName"></input>
<button onClick={this.createArtist}>Add Artist</button>
{this.state.artists.map(artist => <h4 key={artist.id}>{artist.name}
<button onClick={() =>this.deleteArtist(artist.id)}>Delete artist</button></h4>)}
</>
)
}
}
export default App;
this.setState is an async function, it takes second argument as callback. This should solve your problem. i.e.
import React, { useState, useEffect, useRef, Component } from "react";
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:8080/artists",
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
artists: [],
theArtistName: "",
};
}
//calling this method will allow artist array to be populated everytime an event occurs, e.g POST, PUT, DELETE
getArtists = async () => {
let data = await api.get("/").then(({ data }) => data);
this.setState({ artists: data }); //setting our artists to be the data we fetch
};
createArtist = async () => {
let response = await api.post("/", { name: this.state.theArtistName });
console.log(response);
this.getArtists();
};
deleteArtist = async (id) => {
let data = await api.delete("/${id}");
this.getArtists();
};
handleAddArtist = (event) => {
event.preventDefault();
this.setState(
{
theArtistName: event.target.value,
},
() => {
this.createArtist();
}
);
};
componentDidMount() {
this.getArtists();
}
render() {
// const {theArtistName} = this.state
return (
<>
<input
type={Text}
placeholder="Enter Artist Name"
name="theArtistName"
></input>
<button onClick={this.handleAddArtist}>Add Artist</button>
{this.state.artists.map((artist) => (
<h4 key={artist.id}>
{artist.name}
<button onClick={() => this.deleteArtist(artist.id)}>
Delete artist
</button>
</h4>
))}
</>
);
}
}
export default App;
Let me know if it helps.
because react update state asynchronously so when you are invoking handleAddArtist function which update state the event might be gone so you need to store the value from the event in variable like this :
handleAddArtist = (event) =>{
event.preventDefault()
const {value} = e.target
this.setState({
theArtistName: value
})
}
and to check state update there is a lifecycle method called componentDidUpdate for class component and useEffect for functional component.
[edit]:
call this.createArtist() in componentDidUpdate like this :
componentDidUpdate(prevProps,prevState){
if(prevState.theArtistName!==this.state.theArtistName)
this.createArtist()
}
so the createArtist will fire only when theArtistName state change.
First of all, useRef is a hook only meant for function components and not for class components. For using Refs in class components use React.createRef().
Usually, HTML input elements maintain their own state. The usual way to access the value of an input element from a React component that renders it is to control the input element's state via this component by adding an onChange listener and a value attribute to the input element:
class App extends Component{
constructor(props) {
super(props);
this.state = {artistName: ""};
this.handleArtistNameChange = this.handleArtistNameChange.bind(this);
}
handleArtistNameChange(event) {
this.setState({artistName: event.target.value});
}
render(){
return (
<input
type="text"
value={this.state.artistName}
onChange={this.handleArtistNameChange}
/>
);
}
}
Whenever the value of the input element changes the App component will rerender with the most up-to-date value of the input in its state.
Here is a working example:
You can read more on using form elements in React here.

Reactjs unable to pass data to parent object

I'm trying to pass data from my database to a page in my react project. The database stores the user data and the data is called with validateCookie() function. I'm getting data from the validateCookie function but I can't seem to get the data out of the function to the main page so I can use it to update the user's state and calendar and return that to update their information in the database.
The setState is not sending data to the page state. I've tried so much but I'm still new to react so I'm a bit out of my league
import ScheduleSelector from 'react-schedule-selector'
import React, { Component } from 'react';
import Moment from 'moment';
import { Row, Col, Button } from 'react-bootstrap';
import API from '../../utils/API';
class Availability extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
email: "",
calendar: [],
schedule: [],
}
// this.handleInputChange = this.handleInputChange.bind(this);
// this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.validateCookie();
console.log(this.state.user); // coming back empty because validate cookie is not passing data upstream
}
handleSubmit = (event) => {
event.preventDefault();
// let schedule = this.state.schedule;
// // alert("Your availability has been submitted successfully!");
// let ISOschedule = this.state.schedule.map(date => Moment(date).toISOString());
// let newCalendar = this.state.schedule
console.log(this.state.user);
API.updateAvailability(
this.state.user.email,
this.state.user.calendar,
this.state.user.schedule)
.then(r => {
console.log(r);
}).catch(e => {
console.log(e);
})
}
handleChange = newSchedule => {
this.setState({ schedule: newSchedule.map(date => Moment(date).toISOString()) })
}
validateCookie() {
API.validateCookie()
.then(res => res.json())
.then(res => {this.setState({ user: res})})
.then(res => {
console.log(this.state) // coming back with loading data aka empty
console.log(this.state.user) // coming back with all appropriate data
})
.catch(err => console.log(err));
console.log(this.state.user) // coming back empty
}
render() {
return (
<div>
<form ref="form" onSubmit={this.handleSubmit}>
<ScheduleSelector
selection={this.state.schedule}
numDays={7}
minTime={0}
maxTime={23}
onChange={this.handleChange}
/>
<Row>
<Col>
<Button type="submit" className="float-right">Submit Availability</Button>
</Col>
</Row>
</form>
</div>
)
}
}
export default Availability;
I think the problem is that in your validateCookie method, you are expecting the state to change as soon as you call the setState function. It is important to know that setState() does not immediately mutate this.state but creates a pending state transition.
Refer to this answer for more information.
One solution could be to check when this.state actually gets updated before you render anything in your render function.
Just like Swanky said, the setState() doesn't update immediately and you can listen for state change and re-render the UI. I have done some cleaning up to your setState below;
validateCookie = () => {
API.validateCookie()
.then(res => res.json())
.then(res => {
this.setState({...this.state, user: res.user})
console.log(this.state.user);
})
.catch(err => console.log(err));
}

How to pass data between two react sibling components?

I have two components: which takes value from an input field. Second component is which I fetch api data. The problem is that I want to get the value from GetSearch as the value i search the API in Pexels.
I have tried to change my code multiple times. I just cant understand how it is supposed to be done, and how should I actually communicate together with my components.
import React from "react";
class GetSearch extends React.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) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
var PassValue = React.CreateClass({
render: function() {
return (
<p>{this.state.value}</p>
);
},
});
return (
<form className="search-form">
<input
type="text"
placeholder="Search for images"
value={this.state.value}
onChange={this.handleChange}/>
<input type="submit"/>
</form>
);
}
}
export default GetSearch
import React, { Component } from 'react'
export default class Pexels extends Component {
componentDidMount(){
let query = "water"
const url = `https://api.pexels.com/v1/search?query=${query}e+query&per_page=15&page=1`
const api_key = "xxxxxxxxxxxx"
fetch(url, {
method: 'GET',
headers: new Headers({
'Authorization': api_key
})
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => console.error(error))
}
render() {
return (
<h1>Hello</h1>)
}
}
So as you can see now: Pexels sends a get request with the value of water: let query = "water", which works fine. But I need the value from
this.state.value in the GetSearch component
First, you need to create a parent class. Then You need to pass callback functions to the children as props. Here GetSearch component can be your child class. After you click search button your main class method will notify that change. Then create your logic as you want.
Follow this example code. thanks
Parent Component
var ParentComponent = React.createClass({
update: function() {
console.log("updated!");
},
render: function() {
<ChildComponent callBack={this.update} />
}
})
Child Component
var ChildComponent = React.createClass({
preupdate: function() {
console.log("pre update done!");
},
render: function() {
<button onClick={this.props.callback}>click to update parent</button>
}
})
You may need a store(just a function) to fetch url data rather than in a UI component Pexels.
In GetSearch invoke the store function with input as parameter and return a promise, and get data in callback.

Apollo Client Write Query Not Updating UI

We are building an offline first React Native Application with Apollo Client. Currently I am trying to update the Apollo Cache directly when offline to update the UI optimistically. Since we offline we do not attempt to fire the mutation until connect is "Online" but would like the UI to reflect these changes prior to the mutation being fired while still offline. We are using the readQuery / writeQuery API functions from http://dev.apollodata.com/core/read-and-write.html#writequery-and-writefragment. and are able to view the cache being updated via Reacotron, however, the UI does not update with the result of this cache update.
const newItemQuantity = existingItemQty + 1;
const data = this.props.client.readQuery({ query: getCart, variables: { referenceNumber: this.props.activeCartId } });
data.cart.items[itemIndex].quantity = newItemQuantity;
this.props.client.writeQuery({ query: getCart, data });
If you look at the documentation examples, you will see that they use the data in an immutable way. The data attribute passed to the write query is not the same object as the one that is read. Mutating this object is unlikely to be supported by Apollo because it would not be very efficient for it to detect which attributes you modified, without doing deep copies and comparisons of data before/after.
const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
};
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});
So you should try the same code without mutating the data. You can use for example set of lodash/fp to help you
const data = client.readQuery({...});
const newData = set("cart.items["+itemIndex+"].quantity",newItemQuantity,data);
this.props.client.writeQuery({ ..., data: newData });
It recommend ImmerJS for more complex mutations
Just to save someones time. Using the data in an immutable way was the solution. Agree totally with this answer, but for me I did something else wrong and will show it here. I followed this tutorial and updating the cache worked fine as I finished the tutorial. So I tried to apply the knowledge in my own app, but there the update didn’t work even I did everything similar as showed in the tutorial.
Here was my approach to update the data using the state to access it in the render method:
// ... imports
export const GET_POSTS = gql`
query getPosts {
posts {
id
title
}
}
`
class PostList extends Component {
constructor(props) {
super(props)
this.state = {
posts: props.posts
}
}
render() {
const postItems = this.state.posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
const PostListQuery = () => {
return (
<Query query={GET_POSTS}>
{({ loading, error, data }) => {
if (loading) {
return (<div>Loading...</div>)
}
if (error) {
console.error(error)
}
return (<PostList posts={data.posts} />)
}}
</Query>
)
}
export default PostListQuery
The solution was just to access the date directly and not using the state at all. See here:
class PostList extends Component {
render() {
// use posts directly here in render to make `cache.writeQuery` work. Don't set it via state
const { posts } = this.props
const postItems = posts.map(item => <PostItem key={item.id} post={item} />)
return (
<div className="post-list">
{postItems}
</div>
)
}
}
Just for completeness here is the input I used to add a new post and update the cache:
import React, { useState, useRef } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { GET_POSTS } from './PostList'
const ADD_POST = gql`
mutation ($post: String!) {
insert_posts(objects:{title: $post}) {
affected_rows
returning {
id
title
}
}
}
`
const PostInput = () => {
const input = useRef(null)
const [postInput, setPostInput] = useState('')
const updateCache = (cache, {data}) => {
// Fetch the posts from the cache
const existingPosts = cache.readQuery({
query: GET_POSTS
})
// Add the new post to the cache
const newPost = data.insert_posts.returning[0]
// Use writeQuery to update the cache and update ui
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [
newPost, ...existingPosts.posts
]
}
})
}
const resetInput = () => {
setPostInput('')
input.current.focus()
}
return (
<Mutation mutation={ADD_POST} update={updateCache} onCompleted={resetInput}>
{(addPost, { loading, data }) => {
return (
<form onSubmit={(e) => {
e.preventDefault()
addPost({variables: { post: postInput }})
}}>
<input
value={postInput}
placeholder="Enter a new post"
disabled={loading}
ref={input}
onChange={e => (setPostInput(e.target.value))}
/>
</form>
)
}}
</Mutation>
)
}
export default PostInput

Categories