React passing data into Async Function from Input - reactjs

So, I'm trying to pass data from an input element into an async function within my React App.js file. I'm having trouble understanding how to push the input value into the callAPI function.
At the moment I just have a dummy/placeholder ipaddress within the callAPI inorder to test the button is working and calling the function onClick. Here's my code..
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: '' };
}
async callAPI() {
const ipaddress = '8.8.8.8';
const api_url = `http://localhost:9000/ipdata/${ipaddress}`;
const res = await fetch(api_url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
const json = await res.json();
console.log(json);
document.getElementById('city').textContent = json.city;
document.getElementById('state').textContent = json.region_code;
document.getElementById('zip').textContent = json.zip;
}
render() {
return (
<div className="App">
<h1>IP Search</h1>
<input type="text"></input>
<button onClick={this.callAPI}>Search IP</button>
<p>
<span id="city" /> <span id="state" /> <span id="zip" />
</p>
</div>
);
}
}
export default App;

There are two issues:
To get the input value, use a controlled component: put the input value into state and add a change handler.
To set the city, state, zip sections, don't use vanilla DOM methods (which should be avoided in React in 95% of situations) - instead, put the response into state.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: '', inputValue: '', result: {} };
}
async callAPI() {
try {
const api_url = `http://localhost:9000/ipdata/${this.state.inputValue}`;
const res = await fetch(api_url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
const result = await res.json();
this.setState({ result });
} catch (error) {
// handle errors - don't forget this part
}
}
render() {
return (
<div className="App">
<h1>IP Search</h1>
<input
type="text"
value={this.state.inputValue}
onChange={e => this.setState({ inputValue: e.target.value })}
/>
<button onClick={this.callAPI}>Search IP</button>
<p>
<span>{this.state.result.city}</span>
<span>{this.state.result.state}</span>
<span>{this.state.result.zip}</span>
</p>
</div>
);
}
}

you can store the value of input field inside state and use it directly inside async call.
Plus you need a onchange handler as every time you update input text, state should know the updted value.
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
// HERE.........................
this.state = { apiResponse: '', text : null };
}
// HERE ...........................
handleChnage = (e) => this.setState({text : e.target.value})
async callAPI() {
// Checking the input value and pass to api..................
console.log(this.state.text)
const ipaddress = '8.8.8.8';
const api_url = `http://localhost:9000/ipdata/${ipaddress}`;
const res = await fetch(api_url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
const json = await res.json();
console.log(json);
// Don't use it..............use state to pass the data
document.getElementById('city').textContent = json.city;
document.getElementById('state').textContent = json.region_code;
document.getElementById('zip').textContent = json.zip;
}
render() {
// Here on Input element .................
return (
<div className="App">
<h1>IP Search</h1>
<input type="text" value={this.state.text} onChange={this.handleChange}></input>
<button onClick={this.callAPI}>Search IP</button>
<p>
<span id="city" /> <span id="state" /> <span id="zip" />
</p>
</div>
);
}
}
export default App;
Note - don't use imperative methods like getElementById and others in React.

Please avoid using DOM methods in Reactjs, here is an example of what you might want to do with your application.
`
import React,{useState} from 'react';
function App(){
const [apiRes,setApiRes]= useState('');
const [loading,setLoadng]= useState(false);
const callAPI= async()=>{
// supose this is your api response in json
const hello={
city:"city1",
region_code:"region#123",
zip:"00000"
}
// loading while city and zip are not available
setLoadng(true)
await setTimeout(()=>{setApiRes(hello)},5000)
}
return (
<div className="App">
<h1>IP Search</h1>
<input type="text"></input>
<button onClick={callAPI}>Search IP</button>
{!apiRes && loading && <p>loading count till 5...</p>}
<p>
{apiRes &&
(
<>
<span> {apiRes.city}</span>
<span> {apiRes.region_code}</span>
<span> {apiRes.zip}</span>
</>
)}
</p>
</div>
);
}
export default App;
`
link to sandbox: [sandbox]: https://codesandbox.io/s/priceless-mclaren-y7d7f?file=/src/App.js/ "click here to run above code"

Related

Setting state of a 'Loading' boolean in react doesn't update

I have a page which allows a user to submit a url from which data is scraped. The user is subsequently presented with the filtered data.
Because the scraping takes some time I would like to implement a loader. While the loader class will (hopefully) be relatively straight forward, it's the state for loading which I'm having issues with. The state itself is never updated. Although other state values are such as setFilters.
Body.js
const [searchState, setSearchState] = useState({
searchCriteria: "https://en.wikipedia.org/wiki/2020_Central_Vietnam_floods",
headers:[],
references: []
});
const [filterState, setFilters] = useState({
languageFilter: ""
});
const [loadingState, setLoadingState] = useState({
loading: false
});
The above are all passed into Search with a context
<>
<SearchContext.Provider value={{searchState, setSearchState,filterState, setFilters, loadingState, setLoadingState}} >
<Search />
<DonateButton />
<WikiHeaderGroup />
</SearchContext.Provider>
</>
And then I have a handleSubmit inside the Search component.
Search.js
import React, {useContext} from "react";
import {SearchContext} from "../../contexts/SearchContext"
import "../../App.css"
export function Search (){
const {searchState, setSearchState, filterState, setFilters, loadingState, setLoadingState} = useContext(SearchContext);
const handleSubmit = (event) => {
setFilters({languageFilter:""})
setLoadingState({loading:true})
console.log("Loading State : " + loadingState.loading)
event.preventDefault();
event.persist(); //persists the event object into the function
const fetchReferences = async () => {
fetch('http://127.0.0.1:8080/search/', {
method: 'POST',
body: JSON.stringify({
url: searchState.searchCriteria
}),
headers: {"Content-type": "application/json; charset=UTF-8"}
}).then(response => {
console.log(response)
return response.json()
}).then(json => {
console.log(json)
setSearchState({
headers:json.headers,
references:json.references
})
setLoadingState({loading:false})
console.log("Loading State : " + loadingState.loading)
});}
fetchReferences();
}
return (
<div className="search container">
<div className="input-group input-group-sm mb-3 center">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">Wikipedia URL:</span>
</div>
<form onSubmit={(event) => handleSubmit(event)}>
<input
type="text"
id="searchBox"
className="form-control center"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm"
value={searchState.searchCriteria}
onChange={(event) => setSearchState({searchCriteria:event.target.value, resultId:0})}
placeholder="Add a url" />
</form>
</div>
</div>
);
}
export default Search;
don't use object for booleans, just
const [loadingState, setLoadingState] = useState(false);
....
setLoadingState(true)
btw looks like a closure problem. you see loadingState always false cause the closure.
take a look at this Be Aware of Stale Closures when Using React Hooks
A way to solve it is using refs
const loadingStateRef = useRef(loadingState);
//then inside the function u can access
latestValue.current

draft.js: text editor populate value from other component's state

I'm using draft.js to make a text editor and I have two components: CreatePost.js which gets the post fields from the back end and populates the state with the user input and TextEditor.js which contains a text editor which I am using in CreatePost.js. The text editor should populate the body field in the state of CreatePost.js onChange.
My question is how can I get the text editor to populate the state in the other component? Would I need to use props instead?
Before, I had a text area in CreatePost.js which populated the body. I want the text editor in the other component to populate it instead. I've tried using
<TextEditor onChange={this.changeHandler} value={body} /> in CreatePost.js but it didn't work.
console.log(body):
posts.js (controller)
exports.create = (req, res) => {
const { title, body, date } = req.body;
const post = new Post({
title,
body,
date,
"author.id": req.profile._id,
"author.name": req.profile.name,
});
post
.save()
.then((response) => {
res.send(response);
})
.catch((err) => {
return res.status(400).json({
error: errorHandler(err),
});
});
};
CreatePost.js
class CreatePost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
createdPost: "",
error: "",
};
}
changeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
submitHandler = (e) => {
e.preventDefault();
const {
user: { _id },
} = isAuthenticated();
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: this.state,
})
.then((response) => {
this.setState({ createdPost: this.state.title });
return response;
})
.catch((error) => {
if (!this.state.title || !this.state.body) {
this.setState({
error: "This post must contain a title and a body.",
});
}
console.log(error);
});
};
...
render() {
const { title, body } = this.state;
return (
<>
<Navbar />
<Tabs>
<TabList className="tabs">
<Tab className="tab">Draft</Tab>
<Tab className="tab">Preview</Tab>
</TabList>
<TabPanel>
<div className="newpost_container">
<form className="newpost_form" onSubmit={this.submitHandler}>
<div className="form-group">
<input
type="text"
placeholder="Title"
name="title"
className="newpost_field newpost_title"
onChange={this.changeHandler}
value={title}
/>
</div>
<div className="form-group newpost_body">
<TextEditor />
</div>
<button className="btn publish-post-btn" type="submit">
Publish
</button>
{this.showSuccess()}
{this.showError()}
</form>
</div>
</TabPanel>
<TabPanel>
<div>
<h1>{title}</h1>
<div>{body}</div>
</div>
</TabPanel>
</Tabs>
</>
);
}
}
export default CreatePost;
TextEditor.js
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.plugins = [addLinkPlugin];
}
toggleBlockType = (blockType) => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(
this.state.editorState,
command
);
if (newState) {
this.onChange(newState);
return "handled";
}
return "not-handled";
};
// onClick for format options
onAddLink = () => {
const editorState = this.state.editorState;
const selection = editorState.getSelection();
const link = window.prompt("Paste the link -");
if (!link) {
this.onChange(RichUtils.toggleLink(editorState, selection, null));
return "handled";
}
const content = editorState.getCurrentContent();
const contentWithEntity = content.createEntity("LINK", "MUTABLE", {
url: link,
});
const newEditorState = EditorState.push(
editorState,
contentWithEntity,
"create-entity"
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
this.onChange(RichUtils.toggleLink(newEditorState, selection, entityKey));
};
toggleBlockType = (blockType) => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
render() {
return (
<div className="editorContainer">
<div className="toolbar">
<BlockStyleToolbar
editorState={this.state.editorState}
onToggle={this.toggleBlockType}
/>
// format buttons
</div>
<div>
<Editor
placeholder="Post Content"
blockStyleFn={getBlockStyle}
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
</div>
</div>
);
}
}
export default TextEditor;
It looks like you've been quite close to solving this actually. You were on the right path when sending down the change handler using props to the TextEditor. One solution to your problem is to move up the editorState to your CreatePost component and then pass the value and the change handler downwards. If you are doing this you should remove the editorState and the change handler for it from the TextEditor file. Just by continuing on your example something like this should work, I haven't tried the code out but it should help you in the right direction.
In CreatePost.js
constructor(props) {
super(props);
this.state = {
title: "",
body: EditorState.createEmpty(),
createdPost: "",
error: "",
};
}
....
<TextEditor onChange={(value) => this.setState({ body: value })} editorState={body} />
In TextEditor.js
<Editor
placeholder="Post Content"
blockStyleFn={getBlockStyle}
editorState={this.props.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.props.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
When posting the data we need to access the content of the editor instead of the EditorState. We can do this through draft.js API (see more here: https://draftjs.org/docs/api-reference-editor-state/#getcurrentcontent). And that's not enough unfortunately. We also need to to convert the content to a format that's easier to handle. We can do this with draft.js convertToRaw which you also need to import from the library (https://draftjs.org/docs/api-reference-data-conversion/#converttoraw). Convert to raw returns a JS object so we also need to convert that to a string before being able to send it to the server by using JSON.stringify().
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: {
...this.state,
body: JSON.stringify(convertToRaw(this.state.body.getCurrentContent()))
}
})

Password show/hide using Eye/EyeSlash in React

I am trying to implement eye/eyeslash in on my Register form in React.
This is a function that's is responsible for changing visibility type and eye icon changing.
import React, { useState } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
export const usePasswordToggle = () => {
const [visible, setVisibility] = useState();
const Icon = <FontAwesomeIcon icon={visible ? "eye-slash" : "eye"} />;
const InputType = visible ? "text" : "password";
return [InputType, Icon];
};
I am trying to implement it in component responsible for registering.
import React, { Component, createRef } from "react";
import { usePasswordToggle } from "./usePasswordToggle";
class Register1 extends React.Component {
EmailR = createRef();
UsernameR = createRef();
PasswordR = createRef();
PasswordConfirmR = createRef();
constructor(props) {
super();
this.state = {
message: "",
password: "",
confirmPassword: "",
};
}
handleSubmit = (event) => {
// alert(this.PasswordR.current.value);
// alert(this.PasswordConfirmR.current.value);
if (this.PasswordR.current.value !== this.PasswordConfirmR.current.value) {
alert("The passwords doesn't match");
return false; // The form won't submit
} else {
alert("The passwords do match");
return true; // The form will submit
}
};
onCreateAccount = () => {
let loginInfo = {
Username: this.UsernameR.current.value,
Email: this.EmailR.current.value,
Password: this.PasswordR.current.value,
};
fetch("http://localhost:5000/api/authenticate/register", {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify(loginInfo),
})
.then((r) => r.json())
.then((res) => {
if (res) {
this.setState({
message:
"New Account is Created Successfully. Check your email to verify Account.",
});
}
});
};
render() {
return (
<div>
<h2 className="FormDescription">
{" "}
Please enter Account details for registration
</h2>
<div className="Form">
<p>
<label>
Email: <input type="text" ref={this.EmailR} />
</label>
</p>
<p>
<label>
Username: <input type="text" ref={this.UsernameR} />
</label>
</p>
<div>
<label>
Password:{" "}
<input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">
{usePasswordToggle.Icon}
</span>
</div>
<p>
<label>
ReenterPassword:{" "}
<input type="password" ref={this.PasswordConfirmR} />{" "}
</label>
</p>
<button onClick={this.handleSubmit}> Create </button>
<p>{this.state.message}</p>
</div>
</div>
);
}
}
export default Register1;
My password is always visible, and eye icon is even not visible on the form (it should be inside my input field, but it is not).
Focus on this code snippet:
<div>
<label>
Password: <input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">{usePasswordToggle.Icon}</span>
</div>
Any suggestion what is the problem?
Change this
const [visible, setVisibility] = useState();
to this
const [visible, setVisible] = useState(true);
as the official documentation here
First, add a default value to your useState, either true or false depending on which icon you want to render first.
Then, you should add a onClick method to your icon which will toggle the visibility state. You're setting the icon based on visible value, but you never toggle the value.
onClick={() => setVisibility(!visible)}
UPDATE
You also need to execute your Hook inside your main component (because yes, you wrote what React call a Hook), like so :
const [inputType, icon] = usePasswordToggle();
But doing so, you'll get an error from React that say you cannot use a Hook within a class component due to how they work.
Basically you need to change your Register1 component to be a functional component, and not a class anymore. Look here for a quick overview on how to : https://reactjs.org/docs/components-and-props.html

React Loader - Trying to get a loader when api request is made and to stop it when response is fetched

What I want is that, when I click on search button, then a loader/spinner should appear on screen until the data is fetched, when the data is fetched it should disappear.
Container.jsx
import React from 'react';
import './container.css'
import Weather from './weather';
var Loader = require('react-loader');
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
location: "",
weather: [],
loaded:false
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
componentDidMount() {
this.setState.loaded=false;
}
continue = (e) => {
this.setState({loaded:true});
const { location } = this.state;
const rawurl = 'http://api.weatherstack.com/current?access_key=d8fefab56305f5a343b0eab4f837fec1&query=' + location;
const url = rawurl;
e.preventDefault();
if (location.length < 1) {
return alert('Enter the details');
}
else {
fetch(url)
.then(response => response.json())
.then(data =>{
this.setState({weather:[data],loaded:false});
})
.catch(err => console.log("error ",err))
}
};
render() {
console.log(this.state.weather);
const weather =
this.state.weather.length> 0 ?
this.state.weather.map(item => (<Weather location={item.location.name} temperature={item.current.temperature} weather={item.current.weather_descriptions[0]} windSpeed={item.current.wind_speed} windDegree={item.current.wind_degree} windDir={item.current.wind_dir} humidity={item.current.humidity} visibility={item.current.visibility} />
))
:<span></span>
return (
<div id="container">
<div class="searchicon">
<input type="search" placeholder="Enter City !!" type="text" name="location" value={this.state.location} onChange={this.handleChange}></input>
<label class="icon">
<button onClick={this.continue} id="btn"><span class="fa fa-search"></span></button>
</label>
</div>
<div>
<Loader loaded={this.state.loaded}>
{weather}
</Loader>
</div>
</div>
);
}
}
export default Container;
What I am using here is react-loader
But right now,its not happening in the way I want, sometime before clicking the serach button it appears and when data is fetched it stops, i want to start it when the api req is made after click on search button and to stop when data is fetched.
first of all you should in the setState after fetching the data to make
this.setState({weather:[data],loaded:true});
second there's another way to do it you can separate the code in the return function like
{ !this.state.loaded ? <Loader loaded={false} options={options} className="spinner" />:{weather}}
as per the Doc in npm you can check it react-loader

Access class function from rendered class object

I'm building a web application to manage some inputs from the user where I want to execute a function on every object in list that is rendered in react. The rendered objects are a different class than the one it is executed in.
import React, { Component } from "react";
import UserInput from "./UserInput";
class Layout extends Component {
objList = []
state = {
update: ""
}
anotherOne = async () => {
this.objList.push(<UserInput key={this.objList.length} />);
this.setState({update: ""});
}
submitCase = async () => {
for (var testCase in this.objList){
this.objList[0].submitInfo();
}
}
removeLatest = async () => {
this.list.pop();
this.setState({update: ""});
}
render(){
return(
<div id="container">
<div>
{ this.objList }
</div>
<div>
<button onClick={ this.anotherOne }>Another One</button>
<button onClick={ this.submitCase }>Submit</button>
</div>
</div>
);
}
}
export default Layout;
import React, { Component } from "react";
class UserInput extends Component {
state = {
name: "",
hairColor: "",
age: ""
}
submitInfo = async () => {
let path = '/dbmanager';
let apiName = "myApi"
let myInit = {
body: {categoryId: "Person", type: this.state.hairColor, data: JSON.stringify(this.state)},
contentType: 'application/json',
}
await API.post(apiName, path, myInit)
.then(response => {
// Add your code here
console.log(response);
})
.catch(error => {
console.log(error.response);
})
}
handleStateUpdate = (event) => {
var eName = event.target.name;
var eValue = event.target.value;
this.setState({[eName]: eValue});
console.log(event.target.value, event.target.name);
}
render(){
return(
<div id="container">
<div>
<label>Name: </label>
</div>
<textarea
type="text"
id="name"
name="name"
value={this.state.name}
onChange={this.handleStateUpdate}/>
<div>
<label>Hair Color: </label>
</div>
<textarea
type="text"
id="hairColor"
name="hairColor"
value={this.state.hairColor}
onChange={this.handleStateUpdate}/>
<div>
<label>Age: </label>
</div>
<textarea
type="text"
id="age"
name="age"
value={this.state.age}
onChange={this.handleStateUpdate}/>
</div>
);
}
}
export default UserInput;
I want the access to the class functions of UserInput so that I could submit the data from all of them on the same button press. Instead the objects are considered functions and are not executable in any means.

Resources