React: How to update the DOM with API results - node.js

My goal is to take the response from the Google API perspective and display the value into a div within the DOM.
Following a tutorial : https://medium.com/swlh/combat-toxicity-online-with-the-perspective-api-and-react-f090f1727374
Form is completed and works. I can see my response in the console. I can even store the response into an object, array, or simply extract the values.
The issue is I am struggling to write the values to the DOM even though I have it saved..
In my class is where I handle all the API work
class App extends React.Component {
handleSubmit = comment => {
axios
.post(PERSPECTIVE_API_URL, {
comment: {
text: comment
},
languages: ["en"],
requestedAttributes: {
TOXICITY: {},
INSULT: {},
FLIRTATION: {},
THREAT: {}
}
})
.then(res => {
myResponse= res.data; //redundant
apiResponse.push(myResponse);//pushed api response into an object array
console.log(res.data); //json response
console.log(apiResponse);
PrintRes(); //save the values for the API for later use
})
.catch(() => {
// The perspective request failed, put some defensive logic here!
});
};
render() {
const {flirty,insulting,threatening,toxic}=this.props
console.log(flirty); //returns undefined, makes sense upon initialization but does not update after PrintRes()
return (
<div className="App">
<h1>Please leave a comment </h1>
<CommentForm onSubmit={this.handleSubmit} />
</div>
);
}
}
When I receive a response from the API I use my own function to store the data, for use later, the intention being to write the results into a div for my page
export const PrintRes=() =>{
// apiResponse.forEach(parseToxins);
// myResponse=JSON.stringify(myResponse);
for (var i = 0; i < apiResponse.length; i++) {
a=apiResponse[i].attributeScores.FLIRTATION.summaryScore.value;
b=apiResponse[i].attributeScores.INSULT.summaryScore.value;
c=apiResponse[i].attributeScores.THREAT.summaryScore.value;
d=apiResponse[i].attributeScores.TOXICITY.summaryScore.value;
}
console.log("hell0");//did this function run
// render(){ cant enclose the return in the render() because I get an error on the { , not sure why
return(
<section>
<div>
<p>
Your comment is:
Flirty: {flirty}
</p>
</div>
<div>
<p>
Your comment is:
insulting: {insulting}
</p>
</div>
<div>
<p>
Your comment is:
threatening: {threatening}
</p>
</div>
<div>
<p>
Your comment is:
toxic: {toxic}
</p>
</div>
</section>
);
}
Variables and imports at the top
import React from "react";
//needed to make a POST request to the API
import axios from "axios";
import CommentForm from "../components/CommentForm";
var myResponse;
var apiResponse= [];
let a,b,c,d;
let moods = {
flirty: a,
insulting:b,
threatening:c,
toxic:d
}

If I understand correctly You need to create a state where you store data from api.
States in react works like realtime stores to refresh DOM when something change. this is an example to use it
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
apiData: undefined
};
}
fetchData() {
this.setState({
apiData: "Set result"
});
}
render() {
const { apiData } = this.state;
return (
<div>
<button onClick={this.fetchData.bind(this)}>FetchData</button>
<h3>Result</h3>
<p>{apiData || "Nothing yet"}</p>
</div>
);
}
}
you can check it here: https://codesandbox.io/s/suspicious-cloud-l1m4x
For more info about states in react look at this:
https://es.reactjs.org/docs/react-component.html#setstate

Related

useState data not working with .map function

I have this app that fetches the blog posts from an API. The API response with blog posts and I'm getting those blog posts to GetBlogState state. When I'm looping through GetBlogState using the .map I am getting the following error.
The following is the code that I'm currently working with.
import React, { useState, useEffect } from 'react';
import Head from 'next/head'
import axios from 'axios'
import HeaderComponent from '../components/HeaderComponent';
export default function Blog(){
const [GetBlogState, SetBlogState] = useState([]);
useEffect(() => {
axios.get('http://localhost:4000/blog').then(res => {
SetBlogState(res)
}).catch(errr => {
console.log(err)
})
}, []);
return (
<div className="MyApp">
{ GetBlogState.map(item => (
<div className="h-68">
<img className="w-full" alt="post" src='post.jpg' />
<div className="mt-3 mb-2 text-xs">May 10, 2018</div>
<h2 className="font-bold mb-5 text-xl">{ item.Title } </h2>
<p>{item.content}</p>
</div>
))}
</div>
)
}
I think you should check the output what you are getting in res from axios.
you are setting response object in state which is wrong.
You should do
useEffect(() => {
axios.get('http://localhost:4000/blog').then(res => {
//// console.log(res) Check whats returning in res \\\
SetBlogState(res.data)
}).catch(errr => {
console.log(err)
})
}, []);
Axios' response schema put server response in data. Hence set state like SetBlogState(res.data)

How can I take a value from an input tag in a TSX component, and use that value in a Node JS file in a different directory?

I'm working on a personal project where I'm pulling an API through Fetch; at the moment I can send the call from my index.js file to a TSX component that calls the API URL when my SearchButton component is clicked, but the search term needs to be declared in index.js.
Here's my SearchButton code (TSX):
import React, { useState } from 'react'
function SearchButton() {
const [ newsResponse, setNewsResponse ]= useState(null);
function queryOnClick() {
fetch(`http://localhost:4000/news-api`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
setNewsResponse(result);
})
.catch((ex) => {
console.log('error:', ex);
});
}
return (
<div className="theme--white">
<button className="search__button padding-1 margin-1 margin-left-6" onClick={queryOnClick}>
Click to search
</button>
{newsResponse && newsResponse.articles ? (
<div className="results__container padding-2 theme--mist">
{newsResponse.articles.map((article: {
title: React.ReactNode;
author: string;
content: string;
url: string;
}) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">{article.title}</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">Written by: {article.author || 'An uncredited author'}</h3>
<p className="article__content">
{article.content.length > 150 ?
`${article.content.substring(0, 150)}... [Article shortened - Click the URL below to read more]` : article.content
}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
) : null}
</div>
);
}
export default SearchButton;
I want to change that so a user can search for an article from the API by using a HTML input to submit a topic which would amend the API URL. For instance, if I searched Bitcoin, it would search https://API-${Bitcoin}.com. Due to CORS policy blocking, I can't just call the API in my TSX file as it has to go from localhost:3000 > localhost:4000 via the Node JS file.
At the moment, my input renders the user's query into the console, but I can't seem to get it over to my index.js file. How can I pass a value that's either in the console.log, or from the input's value, through to my Node JS index.js file?
Here's my SearchBar file that handles my Input (TSX):
import React, { Component } from 'react';
type SearchBarProps = {
searchNews: (text: string) => void;
}
type SearchBarState = {
searchString: string;
}
class SearchBar extends Component<SearchBarProps, SearchBarState> {
static defaultProps = {
searchNews: (text: string) => {}
}
state = {
searchString: ''
}
searchNews = (e: any) => {
const { searchString } = this.state
if(e.key === 'Enter' && searchString !== '') {
e.preventDefault();
e.stopPropagation();
this.props.searchNews(searchString)
console.log(searchString)
}
}
onSearchTextChange = (e: any) => {
this.setState({
searchString: e.target.value.trim()
})
}
render() {
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={this.state.searchString}
onChange={this.onSearchTextChange}
onKeyPress={e => this.searchNews(e)} placeholder="Search" />
</div>
</form>
</div>
);
}
}
export default SearchBar;
...And here's my index.js Node JS file (JS):
/*
* Libs
*/
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');
const app = express();
/*
* Constants
*/
const PORT = 4000;
const API_KEY = 'x';
const SEARCH_QUERY = "Bitcoin";
const SORT_BY = "popularity";
const PAGE_SIZE = 10;
/*
* Setup CORS - This is needed to bypass NewsAPI CORS Policy Blocking by rerouting request to localhost
*/
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
/*
* Setup to request NewsAPI data using Fetch API
*/
app.get('/news-api', function (req, res) {
fetch(`https://newsapi.org/v2/everything?q=${SEARCH_QUERY}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
headers: { 'Content-Type': 'application/json' }
})
.then((response) => response.json())
.then((result) => {
console.log('result:', result);
res.json(result);
})
.catch((ex) => {
console.log('error:', ex);
res.status(400).send({
message: 'This is an error!',
error: ex
});
});
})
/*
* Start Backend API Proxy server
*/
app.listen(PORT, () => {
console.log(`=================`)
console.log(`API Connected!`)
console.log(`Listening at http://localhost:${PORT}`)
console.log(`=================`)
})
TLDR:
I have a TSX component that is an input (A - value={this.state.searchString}).
I want that input's value to go to a Node JS file to append a URL via a const (B - const SEARCH_QUERY).
I know what to pull from A, and where to put it in B, but don't know how to do so.
Full tech stack
Using Fetch API, React, TypeScript, Node JS and Webpack.
File paths
SearchButton: project/frontend/src/components/SearchButton/SearchButton.tsx
SearchBar: project/frontend/src/components/SearchBar/SearchBar.tsx
Node JS handler: project/backend/index.js
Essentially what you are asking here is how to pass data from the frontend to the backend. The way to do this is by including the user's search term in your fetch request to the backend. You can either include it in the body of a POST request or include it as a query string in the URL. You would need to use the body for passing large amounts of data, but something as simple as a search term can be done with a query string.
Front End
Include the current search term as a query parameter of your fetch request. I am using encodeURIComponent to apply percent-encoding to special characters.
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
...
You are missing the communication between your SearchButton and SearchBar components. I am not sure where these two components are in relation to each other on your page. If they are siblings then you will need to lift the searchString state and the queryOnClick function up to a shared parent.
I rearranged all of your components so that you have access to the right state in the right places.
import React, { useState } from "react";
function SearchButton({ onClick }: { onClick: () => void }) {
return (
<button
className="search__button padding-1 margin-1 margin-left-6"
onClick={onClick}
>
Click to search
</button>
);
}
interface SearchBarProps {
searchNews: () => void;
searchString: string;
setSearchString: (s: string) => void;
}
function SearchBar({ searchNews, searchString, setSearchString }: SearchBarProps) {
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && searchString !== "") {
e.preventDefault();
e.stopPropagation();
searchNews();
}
};
const onSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchString(e.target.value.trim());
};
return (
<div>
<form>
<div>
<input
id="search"
type="search"
value={searchString}
onChange={onSearchTextChange}
onKeyPress={handleKeyPress}
placeholder="Search"
/>
</div>
</form>
</div>
);
}
interface Article {
title: string;
author: string;
content: string;
url: string;
}
interface NewsResponse {
articles: Article[];
}
function ArticleList({ articles }: NewsResponse) {
return (
<div className="results__container padding-2 theme--mist">
{articles.map((article) => (
<div className="article__container box-shadow padding-2 margin-4 margin-left-6 margin-right-6 theme--white">
<h2 className="article__title padding-bottom-2 margin-bottom-2">
{article.title}
</h2>
<h3 className="article__author padding-bottom-2 margin-bottom-2">
Written by: {article.author || "An uncredited author"}
</h3>
<p className="article__content">
{article.content.length > 150
? `${article.content.substring(
0,
150
)}... [Article shortened - Click the URL below to read more]`
: article.content}
</p>
<div className="article__url margin-top-2">
<p>
<p>Source:</p>
<a href={article.url}>{article.url}</a>
</p>
</div>
</div>
))}
</div>
);
}
function SearchPage() {
const [newsResponse, setNewsResponse] = useState<NewsResponse | null>(null);
const [searchString, setSearchString] = useState("");
function queryOnClick() {
// applies percent-encoding to special characters
const search = encodeURIComponent(searchString);
const url = `http://localhost:4000/news-api?search=${search}`;
fetch(url, {
headers: { "Content-Type": "application/json" }
})
.then((response) => response.json())
.then((result) => {
console.log("result:", result);
setNewsResponse(result);
})
.catch((ex) => {
console.log("error:", ex);
});
}
return (
<div className="theme--white">
<SearchBar
searchNews={queryOnClick}
searchString={searchString}
setSearchString={setSearchString}
/>
<SearchButton onClick={queryOnClick} />
{newsResponse && newsResponse.articles ? (
<ArticleList articles={newsResponse.articles} />
) : null}
</div>
);
}
export default SearchPage;
Back End
You need to access the search term from the search parameter of the request URL. We use the req.params property to get a dictionary of params. We can use your previous search term "Bitcoin" as the default value if there was no search param on the request.
I'm not certain if we need to encode again here or not -- you'll want to play with that.
app.get('/news-api', function (req, res) {
const searchQuery = req.params.search || "Bitcoin";
fetch(`https://newsapi.org/v2/everything?q=${searchQuery}&sortBy=${SORT_BY}&pageSize=${PAGE_SIZE}&apiKey=${API_KEY}`, {
...

how to solve this no-unused-vars error in NodeJS?

I am creating a todo app using MERN stack.I am new to MERN stack technology and I kindly neeed your help solving this error.I have provided the code for my app.js file and todo.js file.I can't clearly find the solution of this error anywhere on the internet.
I am getting this error while runnng the node js app using npm start command.
Compiled with warnings.
src\App.js
Line 4:8: 'Todo' is defined but never used no-unused-vars
Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Todo from './components/Todo.js';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Todo.js
import React, { Component } from 'react'
import axios from 'axios';
// eslint-disable-next-line
export class Todo extends Component {
constructor(props) {
super(props)
this.state = {
todos : [],
item : ""
}
}
changeHandler = (event) => {
this.setState({item: event.target.value})
}
clickHandler = (event) => {
event.preventDefault()
console.log(this.state.item)
axios({
method: 'post',
url: 'http://localhost:3000/',
data: {
todo: this.state.item,
}
});
this.setState({item:''})
}
componentDidMount() {
axios.get('http://localhost:3000/').then((response) => {
console.log(response.data.data)
let data = [];
console.log(response.data.data.length)
for(var i =0; i < response.data.data.length; i++){
data.push(response.data.data[i].todo)
}
this.setState({todos: data})
});
}
componentDidUpdate() {
axios.get('http://localhost:3000/').then((response) => {
console.log(response.data.data)
let data = [];
console.log(response.data.data.length)
for(var i =0; i < response.data.data.length; i++){
data.push(response.data.data[i].todo)
}
this.setState({todos: data})
});
}
render() {
return (
<div>
<input type="text" onChange={this.changeHandler}/>
<button type="submit" onClick={this.clickHandler}>add</button>
<div>
<ul>{this.state.todos.map((todo, index) => <li key={index}>{todo}</li>)}</ul>
</div>
</div>
)
}
}
export default Todo
That warning you are getting because even though you are importing Todo file in your App.js file but you aren't using it anywhere.Either try using it in App.js or remove the import(in case you don't need it).That should fix the warning.
Or add // eslint-disable-next-line just before the import Todo.. statement in App.js

API call in render method in React inside map

I have a userId array and I need to show the list of names related to that array. I want to call API call inside the render method and get the username. But this is not working. How can I fix this issue?
Below is my render method:
render(){
...
return(
<div>
{this.state.users.map(userId => {
return (
<div> {this.renderName(userId )} </div>
)
})}
</div>
)
...
}
Below is the renderName function:
renderName = (userId) => {
axios.get(backendURI.url + '/users/getUserName/' + userId)
.then(res => <div>{res.data.name}</div>)
}
Basically you cannot use asynchronous calls inside a render because they return a Promise which is not valid JSX. Rather use componentDidMount and setState to update the users array with their names.
Generally, you do not change state or fetch data in the render method directly. State is always changed by actions/events (clicks, input or whatever). The render method is called everytime a prop/state changes. If you change the state within the render method directly, you end up having an infinite loop.
You should use the lifecycle methods or hooks to load data from an api. Here's an example from the official React FAQ: https://reactjs.org/docs/faq-ajax.html
This will not render anything as the API calls are asynchronous and since renderName function isn't returning anything, it'll return undefined.
You should create a function, which will call api for all the userIds and update in state
getNames = () => {
const promises = [];
this.state.users.forEach((userId) => {
promises.push(axios.get(backendURI.url+'/users/getUserName/'+userId));
})
// Once all promises are resolved, update the state
Promise.all(promises).then((responses) => {
const names = responses.map((response) => response.data.names);
this.setState({names});
})
}
Now you can call this function in either componentDidMount or componentDidUpdate, whenever users data is available.
And finally, you can iterate over names directly and render them
<div>
{this.state.names.map((name) => {
return <div> {name} </div>;
})}
</div>
You could make user name it's own component:
const request = (id) =>
new Promise((resolve) =>
setTimeout(resolve(`id is:${id}`), 2000)
);
const UserName = React.memo(function User({ userId }) {
const [name, setName] = React.useState('');
React.useEffect(() => {
//make the request and set local state to the result
request(userId).then((result) => setName(result));
}, [userId]);
return <div> {name} </div>;
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [1, 2],
};
}
render() {
return (
<ul>
{this.state.users.map((userId) => (
<UserName key={userId} userId={userId} />
))}
</ul>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
export default ()=> {
let [users,setUsers] = useState([]);
useEffect(()=>{
let fetchUsersInfoRemote = Promise.all([...Array(10)].map(async (_,index)=>{
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${index+1}`);
return response.data;
}
catch(error) {
return ;
}
}));
fetchUsersInfoRemote.then(data=> setUsers(data));
},[]);
return (
<div className="App">
<ul>
{
users.map(user=>(<li><pre>{JSON.stringify(user,null,2)}</pre></li>))
}
</ul>
</div>
);
}

Display response object from GET request on backend in react component

I am still figuring React out and have a question. I want to display some data that I am getting back from my mLab database. When I make the request in Postman to test request i get back the object full of data and now I want to display that data in my component.
Backend/server.js:
//this is tested and works in postman
app.get('/logs', function(req, res) {
user: req.user;
res.json();
});
React action:
export const GET_DATA_SUCCESS = 'GET_DATA_SUCCESS';
export const GET_DATA_TRIGGERED = 'GET_DATA_TRIGGERED';
export const GET_DATA_FAILURE = 'GET_DATA_FAILURE';
export function getData() {
const promise = fetch('http://localhost:8080/logs');
return {
onRequest: GET_DATA_TRIGGERED,
onSuccess: GET_DATA_SUCCESS,
onFailure: GET_DATA_FAILURE,
promise,
};
}
Component where I want to display:
import React from 'react';
import {Router, Route, Link, Redirect, withRouter} from 'react-router-dom';
import { getData } from '../actions/memory';
import { connect } from 'react-redux';
export class oldMemory extends React.Component {
oldSearch(e) {
e.preventDefault();
this.props.getData();
}
render() {
return(
<div className="old-info">
<Link to="/main"><h3 className="title-journey">Travel Journal</h3></Link>
<h4>Retrieve a Memory</h4>
<p className="get-info">Look back on an old place you have visited and
reminisce.</p>
<input className="search" type="text" name="search" placeholder="Search"
/>
<button onClick={this.oldSearch.bind(this)}>Get</button>
// would like data to show up here
</div>
)
}
}
export default connect(null, { getData })(oldMemory)
I would use a state to store the data and set the state after the getData promise is resolved. Then, in the render method, i map the state data to div elements and display them in the the component.
// I assume your result of get Data is an array of
// objects {id: number,date: string, memory: string}
// and that getData is a promise
class OldMemory extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
oldSearch = (e) => {
e.preventDefault();
this.props.getData().then(data => {
// if data is null, or undefined set it to an empty array
this.setState({ data: data || [] });
})
}
render() {
// build data to div elements for display
const memories = this.state.data.map(d => <div>{d.date} - {d.memory}</div>)
return(
<div className="old-info">
<Link to="/main"><h3 className="title-journey">Travel Journal</h3></Link>
<h4>Retrieve a Memory</h4>
<p className="get-info">Look back on an old place you have visited and
reminisce.</p>
<input className="search" type="text" name="search" placeholder="Search"
/>
<button onClick={this.oldSearch}>Get</button>
// would like data to show up here
<div id="display-data">
{ memories }
</div>
</div>
</div>
);
}
}
export default connect(null, { getData })(OldMemory)

Resources