ReactJS - adding .then to function makes "this.state.searchText" invalid - reactjs

Using axios, I am calling to an Mongo REST API. However, whenever I press the button which I have tied to it, I get this error:
TypeError: Object(...)(...) is undefined
onSearchButtonPressed
D:/foo/hello-world/src/RecipeBuilder.js:146
> 146 | search_elastic(this.state.searchText).then({
| ^ 147 |
148 | })
149 |
Why is this happening? If I call search_elastic without appending then, it seems to work, however no data is returned. Even more interesting, if I remove encapsulation of the search_elastic method and directly insert the axios.get code block into the onSearchButtonPressed() method, there is no issue.
My class is set up like so:
import React, { Component } from 'react'
import {search_elastic, shell} from './Backend.js'
class RecipeBuilder extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
term: '',
editDisabled: false,
ingredients: [],
candidates: [],
searchText: '',
searchResults: []
}
this.onSearchTextChange = this.onSearchTextChange.bind(this)
this.onSearchButtonPressed = this.onSearchButtonPressed.bind(this)
}
onSearchTextChange(filterText) {
console.log({filterText})
this.setState({
searchText: filterText
});
}
onSearchButtonPressed() {
search_elastic(this.state.searchText).then(data => {
//ideally would like to add data from this method to the RecipeBuilder state
})
}
render () {
return (
<div className="col-md-12">
<SearchBar
searchText={this.state.searchText}
onSearchTextChange={this.onSearchTextChange}
/>
<button onClick={this.onSearchButtonPressed}>Search</button>
</div>
)
}
}
export default RecipeBuilder
The SearchBar component is set up as such:
class SearchBar extends Component {
constructor(props) {
super(props);
this.handleSearchTextChange = this.handleSearchTextChange.bind(this);
}
handleSearchTextChange(e) {
this.props.onSearchTextChange(e.target.value);
}
render() {
return (
<div>
<form>
<input
type="text"
placeholder="Search..."
value={this.props.searchText}
onChange={this.handleSearchTextChange}
/>
</form>
</div>
);
}
}
And the Backend.js can be seen here:
import axios from 'axios'
export const search_elastic = term => {
axios
.get(`api/search/${term}`, {
headers: { 'Content-type': 'application/json' }
})
.then((response) => {
console.log(response)
return response
})
}
export const shell = () => {
console.log("In shell")
}
Working revision of onSearchButtonPressed() (however I have no clue why):
onSearchButtonPressed() {
axios.get(`api/search/${this.state.searchText}`, {
headers: { 'Content-type': 'application/json' }
}).then((response) => {
//console.log(response)
if (response != null) {
var data = response["data"]
var result = data["result"]
var hitsObj = result["hits"]
var hitsArray = hitsObj["hits"]
this.setState({searchResults: [...hitsArray]})
console.log(this.state.searchResults)
}
return response
})
}

Your search_elastic function doesn't return anything. It needs to return the promise from axios.get().
// either *return* axios.get or remove the curlies for an implicit arrow function return
export const search_elastic = term => {
return axios
.get(`api/search/${term}`, {
headers: { 'Content-type': 'application/json' }
})
.then((response) => {
console.log(response)
return response
})
}

Related

React Toastify - Not getting error notification

What I want is, when I get no data from the api, instead of this No data, I want A notification or toast.error to get displayed.
shops.jsx
import React from 'react';
import './shops.css';
import Shop from './shop'
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
require('dotenv').config()
const TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZjFiMjNlYTQxNmJhMjQ3YjQ5MDk4Y2IiLCJlbWFpbCI6Img1aW1icjQ0NGQ7QHR5cC5pbiIsImlhdCI6MTU5NjgxMTU5MSwiZXhwIjoxNTk2ODE1MTkxfQ.UyrUkbNWzenf50FL8AZE1iZaii11P7MwdXpKmoCB9nM";
class Shops extends React.Component {
constructor(props) {
super(props);
this.state = {
shops: []
};
}
componentDidMount() {
console.log(process.env.REACT_APP_BaseURL);
// replace with correct URL: http://localhost:5000/api/shops/allShops
fetch(process.env.REACT_APP_BaseURL, {
method: "get",
headers: new Headers({
Authorization: `Bearer ${TOKEN}`
})
})
.then(response =>response.json())
.then(data => {
this.setState({ shops: data.fetchedShops });
toast.success("API LOADED SUCCESSFULLY","SUCCESS");
})
.catch(err =>{
console.log("Error", err);
if(err){
toast.error("error occured");
}
});
}
render() {
const shops =
this.state.shops.length > 0 ?
this.state.shops.map(item => (
<Shop name={item.shopname} address={item.address} mobile={item.phoneNumber} />
))
: <span >No data</span>;
console.log(this.state.shops);
return <div id="container">{shops}</div>;
}
}
export default Shops;
In the 6th line you can see <span >No data</span> instead of this I want a toast.error notification, but when I write toast.error("No data"); instead of this span i got something like this instead of error notification
If you want to toast that there is no data when the array is empty it needs to be done in two steps since render is a pure function, i.e. without side effects
Issue toast side-effect in component lifecycle functions, i.e. componentDidMount and/or componentDidUpdate
Render null when toasting no data, or since the map can handle empty arrays without issue, just return the empty map result array
Code
class Shops extends Component {
state = {
shops: []
};
checkShops = () => {
const { shops } = this.state;
if (!shops.length) {
toast.error("No Data");
}
};
componentDidMount() {
this.checkShops(); // not really needed if fetch for data first
}
componentDidUpdate() {
this.checkShops();
}
render() {
const { shops } = this.state;
return (
<div id="container">
{shops.map((item) => <div>Data</div>)}
</div>
);
}
}

How to customize a fetch url in React?

I've finally figured out how to get data from the API :) but now I am stuck because I am trying to change the URL of a fetch request.
I can't figure out how to use my input in the URL as it brings up an error saying input not defined. Which I am assuming is because technically it is not linked. Am I overlooking something really simple?
Also a little background; I am trying to build a simple dictionary web application where you type in a word and it retrieves definitions. I am currently using the WordAPI API for my project.
import React from "react";
import "./App.css";
import ZipForm from "./ZipForm.js";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: null,
isLoaded: false,
input: "",
};
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onFormSubmit(input) {
this.setState({ input });
}
componentDidMount() {
const url = `https://wordsapiv1.p.rapidapi.com/words/${input}/definitions`
fetch(url, {
method: "GET",
headers: {
"x-rapidapi-host": "wordsapiv1.p.rapidapi.com",
"x-rapidapi-key": "58143f60a0msh9b238a4cf58ba29p1e28e0jsn9e523b0104ba",
},
})
.then((response) => response.json())
.then((json) => {
this.setState({
items: json,
isLoaded: true,
});
console.log(json.definitions[0].definition);
});
}
render() {
var { isLoaded, items } = this.state;
if (!isLoaded) {
return <div>Loading...</div>;
} else {
}
return (
<div>
<h1 className="tc">{items.definitions[0].definition}</h1>
<ZipForm onSubmit={this.onFormSubmit} />
</div>
);
}
}
I believe URL should be
`https://wordsapiv1.p.rapidapi.com/words/${this.state.input}/definitions`

Unable to change state when using redirection with history.push

I got the following method:
export const getPublication = async (id, props) => {
const idJSON = {"id": id,}
return await axios({
method: 'post',
url: 'users/getPublicationByID',
data: idJSON
})
.then(function (response) {
return response.data
})
.catch(function (error) {
alert(error.response.status); // Works fine
props.history.push({
pathname: '/error',
state: { hasError: true, coding: error.response.status }
})
});
}
Now, this method seems to work fine as it redirects me from the page I was to '/error' when an error is caught. However, the error page doesn't seem to update its coding variable.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
coding: props.coding
};
}
render(){
const { t } = this.props;
var codeMsg = t('errors.errorCode') + this.state.coding;
alert(codeMsg); // says it's undefined!
return (
...
);
}
}
export default withTranslation()(ErrorBoundary);
The state provided to routed components is available at location.state
coding: props.location.state.coding
docs

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>
)
}
}

React doesn't render data coming from an api response

I've seen a lot of questions and I couldn't get the solution
here is my code:
import React, { Component } from "react";
import axios from "axios";
import "./tree.css";
import "./mainTree";
class TablesTree extends Component {
constructor(props) {
super(props);
this.data = this.props.info;
this.state = {
fields: [],
data: [],
show: false
};
}
componentDidMount() {
var dataGet = [];
this.props.tables.forEach((name, i) => {
this.getFieldsTable(name.TABLE_NAME, (err, res) => {
if (res) {
dataGet.push({
TABLE_NAME: name.TABLE_NAME,
columns: res
});
}
});
});
this.setState({ data: dataGet });
}
getFieldsTable(table, callback) {
axios
.get(`table/columns?name=${this.data.user}&psw=${this.data.password}&schema=${this.data.schema}&table=${table}`)
.then(response => {
callback(null, response.data);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<div>
{this.state.data
? this.state.data.map((itm, i) => {
return (
<div>
<h1>{itm.TABLE_NAME}</h1>
</div>
);
})
: null}
</div>
);
}
}
export default TablesTree;
I've made console.log of the this.state.data
and the data is in there, but it doesn't renders anything
I've tried a lot of soutions, but I still without rendering the data, I will apreciate your help.
There's a few things I would change about your code, but most importantly you need to do this.setState after your push to dataGet (inside of your callback function).
Because your API call is asynchronous, you are only calling setState once when your component is initially mounted (and while dataGet is still empty).
getFieldsTable is asynchronous, so the dataGet array will be empty when you call setState.
You could return the promise from getFieldsTable and use Promise.all on all the promises, and use the data when all of them have resolved.
Example
class TablesTree extends Component {
// ...
componentDidMount() {
const promises = this.props.tables.map(name => {
return this.getFieldsTable(name.TABLE_NAME).then(res => {
return {
TABLE_NAME: name.TABLE_NAME,
columns: res
};
});
});
Promise.all(promises).then(data => {
this.setState({ data });
});
}
getFieldsTable(table) {
return axios
.get(`table/columns?name=${this.data.user}&psw=${this.data.password}&schema=${this.data.schema}&table=${table}`)
.then(response => {
return response.data;
})
.catch(error => {
console.log(error);
});
}
// ...
}

Resources