React passing data or adding to state wrong - reactjs

When I leave this code as is, I will get the correct console.log (commented with "these appear correct") that I'm looking for. However when I replace the api_url with http://localhost:9000/ipdata/${this.state.inputValue} the console.log is blank. This is why I think I'm either passing the input value wrong or I'm adding it to the state wrong.
I would assume I'm adding it to the state wrong as the spans that I'm trying to render in order to output the data on the client aren't displaying anything either.
Heres my code ...
import React from 'react';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: '', inputValue: '', result: {} };
}
async callAPI() {
try {
console.log('called API...');
const api_url = `http://localhost:9000/ipdata/8.8.8.8`;
const res = await fetch(api_url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
const result = await res.json();
// these appear correct
console.log(result.city);
console.log(result.region_code);
console.log(result.zip);
this.setState({ result });
} catch (error) {
// handle errors
}
}
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.region_code}</span>
<span>{this.state.result.zip}</span>
</p>
</div>
);
}
}
export default App;
API call on the Node server...
const fetch = require('node-fetch');
app.get('/ipdata/:ipaddress', async (req, res, next) => {
console.log(req.params);
const ipaddress = req.params.ipaddress;
console.log(ipaddress);
const api_url = `http://api.ipstack.com/${ipaddress}?access_key=API_KEY`;
const response = await fetch(api_url);
const json = await response.json();
res.json(json);
});

The problem is not the way you set state, but the way you access it, because callAPI doesn't have access to this, so you get an error thrown inside the function and as you don't handle errors, it gets swollen. To make it work you either bind the function
onClick={this.callAPI.bind(this)}
or use arrow function instead
callAPI = async ()=> {

Related

React and Websocket messaging to server

So I'm having issues with a single component that displays a list pulled from a resource server. Then it uses Stompjs to establish a websocket and send messages. When I load the client, the Dev Console shows logs that it tries to call onConnected method() twice as my logs show two newUser messages sent from a single load of the component.
When I try to call the submitBid() method it throws a type error saying that
"TypeError: Cannot read properties of undefined (reading 'send')
at submitBid (AuctionList.js:76:1)"
Which I'm not sure why it would be undefined on that line when it's defined and running fine in the function on line 36 which runs before the method that fails. I've been stuck on this for several days so hopefully someone can tell me what I've got wrong in the code... Here is the component code....
import React from 'react'
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import {useState, useEffect } from 'react';
function AuctionList({user, authCredentials, token}) {
const [auctionItems, setAuctionItems] = useState([]);
const [userData, setUserData] = useState({
email: user.email,
name: user.name,
message: ''
});
const [bid, setBid] = useState(0.00);
let stompClient;
let socket;
const connect = async () => {
socket = new SockJS('http://localhost:8080/ws')
stompClient = Stomp.over(socket)
stompClient.connect({}, onConnected, onError)
}
const onConnected = async () => {
stompClient.subscribe('/topic/bids', onMessageReceived)
stompClient.send("/app/socket.newUser",
{},
JSON.stringify({
sender: user.name,
type: 'NEW_USER',
time: Date.now()
})
)
}
const onError = async (err) => {
console.log(err);
}
const handleChange = async (e) =>{
setBid(e.target.value);
}
const submitBid = async (item) => {
let newMessage = {
type: "BID",
newBid: {
itemId: item.id,
email: user.email,
bidPrice: bid,
bidTime: new Date().getTime()
},
sender: userData.email,
time: new Date().getTime()
};
try {
stompClient.send("/socket.send", {}, JSON.stringify(newMessage));
} catch(err){
console.log(err); }
}
const onMessageReceived = async (payload)=>{
console.log("onMessageReceived")
console.log(payload)
}
const getAuctionList = async () => {
const url = "http://localhost:8080/auctionlist";
const init = {
method: "GET",
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`, // notice the Bearer before your token
},
};
fetch(url, init)
.then(response => response.json())
.then(response => {setAuctionItems(response)})
};
useEffect( () => {
getAuctionList();
connect();
}, []);
return (
<ul>
{auctionItems.map( item => {
return( <div key={item.id} className = "auctionItemComponent">
<h3>{item.name}</h3>
<span>{item.desc}</span>
<span>Current Bid: ${item.itemStartingPrice}</span>
<span>Minimum Bid: {item.itemMinBid}</span>
<span>Time left</span>
<input type="number" id="bidInput_" name="bidInput" onChange={handleChange} ></input>
<button type='submit' onClick={submitBid}>Submit bid</button>
</div>)
})}
</ul>
)
}
export default AuctionList
Also I realize I have a bunch of async functions that don't have any awaits. I tried adding those in, but it was no change.
The issue here is not with stompjs but with the scoping. You have stompClient inside React Component but the one from submitBid is different. You can do it in different ways.
Put stompjs in global stage as in example here: https://playcode.io/972045
You can use useRef to have the client inside the React Component and have React do the tracking of any modifications.
I personally think something like a "connection" should stay away from inside a React Component. You should have the connection configs in a different file and import an instance to the JSX file.

json response from mock server not printing but is in the console

I am trying to learn react, and I am making a successful API call, but it only prints in the console. I found examples but many of them recommended to use setData(json) but I am not able to use it because the file is a list of export async function which was also recommended.
export async function GetHellWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
console.log(json)
})
.catch(error => (console.log(error)))
}
and the component
function Test(thisArg, argArray) {
const result = GetHellWorld.apply()
return (
<div className="App">
{JSON.stringify(result)}
</div>
);
}
export default Test;
In the console I see "Hello World" but in the browser is get just {}.
Two questions:
How can I bind the JSON response to an object so I can do something like result.name.
Is this the correct was to call the await function? const result = GetHellWorld.apply()
---- update ----
I decided to try axios because I want to make multiple calls in one file.
const axios = require('axios');
export class AppService {
public async GetHelloWorld(): Promise<any> {
const response = await axios.get(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).catch(() => console.log("Issue in GetHelloWorld"))
return response.data
}
}
component
import React from 'react';
import {AppService} from "../services/app.service";
function Movies() {
const api = new AppService()
const hello = async () => {
const response = await api.GetHelloWorld();
console.log("The response: " + response)
}
return (
<div className="App">
{JSON.stringify(hello)}
</div>
);
}
note I had to add typescript support.
For whatever reason I get
Module not found: Error: Can't resolve '../services/app.service' in '/Users/miketye/programming/test-react/src/components'
While the other answer about using a custom hook can work, I would not recommend it while you're still leaning React.
Look up how to use the "useEffect" hook, that's generally how you want to do any sort of loading logic in React.
First off, you need to fix your async function so it actually returns a value:
// style/convention note, but non-component functions should not start with a capital letter
export async function getHelloWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
return json // will cause this function to return a Promise of type "string", since we're in an async function
})
// better to just let the error get thrown here, for testing
}
Then use it like this:
function Test(thisArg, argArray) {
[fetchResult, setFetchResult] = useState(undefined) // look up useState. State is how you have values that change over time in a resct component
useEffect(() => {
async function fetchData() {
const data = await getHelloWorld()
setFetchResult(data)
}
fetchData()
}, [])
// look up useEffect. Since the second argument (the "dependency array") is empty, useEffect will fire only once, after the component loads
return (
<div className="App">
{result ? JSON.stringify(result) : "no result yet"}
</div>
);
}
export default Test;
You can use a custom hook for this purpose:
import { useState } from "react";
const useFetchData = () => {
const [data, setData] = useState(null);
const fetchData = () => {
fetch("http://localhost:8080/api", {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => { setData(json); })
.catch(error => { console.log(error); })
}
useEffect(() => {
fetchData();
}, []);
return { data, fetchData };
}
export default useFetchData;
And then call it in your component:
import useFetchData from "#/hooks/useFetchData";
const Test = () => {
const { data, fetchData } = useFetchData();
// CALL fetchData IF YOU WANT TO UPDATE THE CURRENT STATE
return (
<div className="App">
{data && JSON.stringify(data)}
</div>
);
}
export default Test;

reactjs empty data when mocking axios.get with jestjs mock

I am developing an application in react and for the first time I decided to implement tests. I started with the simpler components, which I still had trouble with, but now I would like to test more complex components, especially with API calls via axios.
My app looks like this:
import * as React from "react";
import Axios from "axios";
import { AnyData } from "../../interface/AnyDataInterface";
interface IProps {
}
interface IState {
loading: boolean | undefined,
myData: AnyData | undefined
}
export default class Home extends React.Component <IProps, IState> {
constructor (props: IProps){
super(props);
this.state = {
loading: true,
myData: {
field: undefined,
...
}
};
}
fetchData = ():void => {
this.setState({ loading: true });
Axios.get<AnyData>('my-url')
.then(res => {
if (res.status === 200 && res != null) {
this.setState({ myData:{... res.data} });
this.setState({ loading: false });
} else {
console.log('problem when fetching the logs from backend');
}
})
.catch(error => {
console.log(error);
});
}
render():JSX.Element {
return(
<div className="container">
<div>
<button role="button" onClick={this.fetchData}>Search</button>
</div>
{this.state.loading == true ? <p>Wait</p>:
<div role="composite">
{ Some child component that will render data }
</div>
}
</div>
);
}
}
My test looks like this:
import axios from 'axios';
import React from 'react';
import { render, screen, fireEvent, waitFor } from '#testing-library/react';
import Home from './Home';
jest.mock('axios');
describe('Home component', () => {
test('it can be clicked', async () => {
const fakeData = [{
some_field: some_data,
...
}];
axios.get = jest.fn().mockResolvedValue(() =>
Promise.resolve({data: fakeData}));
render(<Home />);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(axios.get).toBeCalledWith("my-url");
});
await waitFor(() => {
//Both expect should pass, if the title is there the data should also appear
expect(screen.getByText('some title text')).toBeInTheDocument(); //Pass without error
expect(screen.getByText('the data from fakeData that should be there')).toBeInTheDocument(); //Send me an error because jest is unable to find some_data
});
});
});
When I wrote this test it failed every time but the problem was not in my test, the problem was in my data fetch function. In the .then I had a condition which was the following: if (res.status === 200 && res != null) { and the res.status === 200 was a condition never satisfied.
So I removed this condition and the call goes through normally. But I had to change a part of my code that is normally not problematic, is there a way to mock the status of the response too ? So that I can put this condition back in my function.
I have another problem that appears now, in my test my component displays well the information "after click" but each field that appears has its corresponding data empty, the data seems not to pass although the .then passes and does not send me to the .catch.
What seems strange to me is that by removing the condition res.status === 200 in my data fetch function and leaving the condition res != null, the test still passes (with the same empty data problem). Finally, by adding a console.log("data: ", res); in my .then in my fetch data function of my component I get, when the test is executed, the following log: data: [Function (anonymous)].
I don't know if this could explain this behavior but my test is in javascript while my classes and functions are in typescript. But the data I fill in my test has the right form (the same as the type requested in my class) so typescript should see that the type matches and have no particular problem
If anyone knows how to answer any of these questions it would really help me, I've been stalling on this test for a while and it's starting to drive me crazy.
EDIT:
I just realized that in fetchData() in my component I am trying to access res.data but the fake data I am sending in my test is in the form fakeData = [{...}]. Could this be the reason why my object is empty?
I tried to change this fake data in my test to: fakeData = [{ data: { ...}}] but I still have the same problem of empty response when calling the get method of axios.
EDIT:
I've try to change test file to a .tsx file and change my "fakeData" in my test to:
jest.mock('axios');
describe('Home component', () => {
test('it can be clicked', async () => {
const fakeData: AnyData = {
"some_field": some_data,
...
};
const fakeConfig: AxiosRequestConfig<any> = {
"maxBodyLength": -1
};
const fakeHeaders: AxiosResponseHeaders= {
"access-control-allow-credentials": "true",
"access-control-allow-headers": "*",
"access-control-allow-methods": "*",
"access-control-allow-origin": "some-url",
"access-control-expose-headers": "scrollId",=
"content-type": "application/json; charset=utf-8",
....
"x-powered-by": "Express"
}
const res: AxiosResponse<AnyData, any> = {
config: fakeConfig,
data: fakeData,
headers: fakeHeaders,
status: 200,
statusText: "OK",
};
axios.get = jest.fn().mockResolvedValue(() =>
Promise.resolve({res: res}));
render(<Home />);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(axios.get).toBeCalledWith("my-url");
});
...
});
});
But even with this method it still doesn't work, yet I really have the impression that the problem comes from the formatting of the data I am sending and I don't see how my data differs from the one requested in fetchData(). As for the fields in my data, I'm sure they're good, so I guess the problem is with the reading of res.data.
When I make the new test by putting a console.log of myData in fetchData() I get the following data: { some_field: undefined, ... }
Thanks in advance if you take the time to help me.
I finally realized my mistake on my test. It was in the rewriting of the axios.get method.
The code that works is as follows:
jest.mock('axios');
describe('Home component', () => {
test('it can be clicked', async () => {
const fakeData: AnyData = {
"some_field": some_data,
...
};
const fakeConfig: AxiosRequestConfig<any> = {
"maxBodyLength": -1
};
const fakeHeaders: AxiosResponseHeaders= {
"access-control-allow-credentials": "true",
"access-control-allow-headers": "*",
"access-control-allow-methods": "*",
"access-control-allow-origin": "some-url",
"access-control-expose-headers": "scrollId",=
"content-type": "application/json; charset=utf-8",
....
"x-powered-by": "Express"
}
const res: AxiosResponse<AnyData, any> = {
config: fakeConfig,
data: fakeData,
headers: fakeHeaders,
status: 200,
statusText: "OK",
};
(axios.get as jest.Mock).mockResolvedValue(res); //THE LINE THAT CHANGE EVERYTHING
render(<Home />);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(axios.get).toBeCalledWith("my-url");
});
...
});
});

Await for completion redux-saga in react class component method

I'm sorry if this is a duplicate question, but I've searched for this question, and haven't found an answer that completely gives the solution for my issue. I want to fetch data in sagas and update localStorage after data is fetched. Then I want to this.forceUpdate() in my component method. But obviously this.forceUpdate() function is launched before data
is loaded. Can I wrap my handleSubmit class method into promise or async/await to make my this.forceUpdate() await for data to be fetched? Thank you.
FETCHING FUNCTION
export const fetchAuth = data => {
return axios({
method: "post",
url: "http://url/",
headers: {},
data: {
email: data.email,
password: data.password.toString()
}
}).then(response => {
return response.data;
});
};
REDUX-SAGA
export function* authRequestFlow(action) {
try {
const tokens = yield call(fetchAuth, action.payload);
if (tokens) {
yield call(save, "access", tokens.access);
yield call(save, "refresh", tokens.refresh);
yield call(save, "isAuthorized", true);
yield put(authSuccess());
}
} catch (error) {
yield put(authFailure(error.message));
}
}
COMPONENT
class Login extends PureComponent {
handleSubmit = () => {
authRequest();
this.forceUpdate();
};
render() {
if (localStorage.getItem("isAuthorized") === "true")
return <Redirect to="/products" />;
return (
<div className={styles.root}>
<Button
onClick={this.handleSubmit}
>
Submit
</Button>
</div>
);
}
}

Is there a way to get the state to update at the right time?

I successfully send a post request, and the entry enters the database. I want the app to re-render to show the new entry in the table. However the setState hits on the addForm Variable and the form vanishes but it does not re-render again to show the new character, I know this is due to asynchronicity, just unsure on how to make that work in the right order.
So far I have attempted:
- to make sure the promise is returned - no change in behaviour
adding a third .then to setState again to try and force a re-render - no change in behaviour
tried forcing with this.forceUpdate - no change in behaviour
All research shows setState as the way to fix this but having no success. I now wonder if my syntax is wrong or poorly constructed.
The Fetch request
handleSubmit = (character) => {
console.log(character);
const url = "http://localhost:3000//api/v1/basics"
const body = JSON.stringify(
{ name: character.name,
age: character.age,
sex: character.sex,
classs: character.classs,
race: character.race,
height: character.height
})
fetch(url,{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: body
}).then((res) =>{
return res.json();
}).then((res) => {
this.setState({ character: res })
}).then((res) => {
console.log(this.state);
this.setState({ addForm: false })
})
}
I am expecting the component to re-render with the new entry but no re-render happens after the form closes. If i refresh the page then it updates but not onclick.
EDIT 1. : FUlly component, I know its a mess this is a play with react and see what it does program.
import React, { Component } from 'react';
import Table from './Table'
import SimpleCharacterInfoForm from './SimpleCharacterForm'
import CharacterSkillsForm from './characterSkillsForm'
import './App.css'
export default class App extends Component {
state = {
addForm: false,
editForm: false,
character: []
}
addCharacter = () => {
this.setState({
addForm: true
})
}
removeCharacter = index => {
const url = `http://localhost:3000//api/v1/basics/${index}`
fetch(url,{
method: 'DELETE'
}).then((res) => {
res.json()
}).then((res) => {
this.setState({})
})
}
handleSubmit = (character) => {
console.log(character);
const url = "http://localhost:3000//api/v1/basics"
const body = JSON.stringify(
{ name: character.name,
age: character.age,
sex: character.sex,
classs: character.classs,
race: character.race,
height: character.height
})
fetch(url,{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: body
}).then((res) =>{
return res.json();
}).then((res) => {
this.setState({ addForm: false })
})
}
render() {
const {characters, addForm, editForm, character} = this.state;
let render = ''
if (addForm === true){
render = this.renderAddCharacter(characters)
} else if (editForm === true) {
render = this.renderEditCharacterSKills(character)
} else {
render = this.renderWithOutForms(characters)
}
return render
}
renderAddCharacter(characters){
return(
<div className="characterTable">
<Table
characterData={characters}
removeCharacter={this.removeCharacter}
editCharacter={this.editCharacter}
/>
< SimpleCharacterInfoForm
handleSubmit={this.handleSubmit}
/>
<button onClick={this.addCharacter}>Add Character</button>
</div>
)
}
renderEditCharacterSKills(character){
return(
<div className="characterSkillsForm">
<CharacterSkillsForm
handleEdit={this.handleEdit}
character={character}/>
</div>
)
}
renderWithOutForms(characters){
return(
<div className="characterTable">
<Table
characterData={characters}
removeCharacter = {this.removeCharacter}
editCharacter={this.editCharacter}
/>
<button onClick={this.addCharacter}>Add Character</button>
</div>
)
}
}

Resources