how to distinguish which component called a callback function? - javascript

I'm new to react, sorry if that is newbe question. I have a component Dropdown which returns a value via a callback function. I would like to render that twice to choose two different values and then simply render chosen values below. How can I allow your two different components to send different data to the component. Below is my code.
index.js
import { Dropdown } from './components/dropdown'
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
componentDidMount() {
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => {console.log(err)});
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
calculateRate = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
export default App;
dropdown.js
export class Dropdown extends React.Component {
constructor(props){
super(props);
this.state = {
list: [],
selected: ""
};
}
componentDidMount(){
fetch('https://api.fixer.io/latest')
.then(response => response.json())
.then(myJson => {
this.setState({ list: Object.keys(myJson.rates) });
});
}
render(){
var selectCurr = (curr) =>
<select
onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}

I'm not exactly sure what you're trying to achieve, but hopefully the following shows how you can allow your two different components to send different data to the <App> component.
The important changes are: we need to bind methods to the <App> component in the constructor() function, then we can use the .bind() method in the Dropdown component to specify the data to pass into the callback function:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
componentDidMount() {
/*
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => {console.log(err)});
*/
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
calculateRate = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
const Dropdown = props => (
<select onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}>
<option value='cats'>Cats</option>
<option value='dogs'>Dogs</option>
</select>
)
export default App;

Related

React js control componentDidUpdate() method

I want to change a API parameter by click function and render new data. When I trigger componentDidUpdate by onclick event listener,the api data changed first and worked fine for first click. But When click second time the api call ran completely. The parameter currentPage is assigned to this.state.count and this this.state.count valued in incremented on click.
My code below:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
products: [],
count: 1,
};
}
componentDidMount() {
this.ProductList();
}
componentDidUpdate() {
let change = document.getElementById("change");
change.addEventListener("click",(e)=>{
this.changeParams();
this.ProductList();
})
}
changeParams = (e) =>{
this.setState({count: this.state.count + 1})
}
ProductList() {
var myHeaders = new Headers();
myHeaders.append("Cookie", "PHPSESSID=822cu5ctftcpo8f98ehklem4k9");
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("http://192.168.31.236/magento/rest/V1/products?searchCriteria[filterGroups][0][filters][0][field]=category_id& searchCriteria[filterGroups][0][filters][0][value]=2& searchCriteria[filterGroups][0][filters][0][conditionType]=eq&searchCriteria[sortOrders][0][field]=price& searchCriteria[sortOrders][0][direction]=ASC& searchCriteria[pageSize]=20& searchCriteria[currentPage]="+this.state.count, requestOptions)
.then(response => response.text())
.then(result => this.setState({products:result}),)
.catch(error => console.log('error', error));
}
render() {
const productsList = () =>{
let pro = [];
if(typeof this.state.products === 'string') {
pro = JSON.parse(this.state.products)
console.log(pro)
}else{
pro = []
}
if(pro.items && typeof pro.items !== "undefined"){
return pro.items.map((item, i) => (
<div>
<h1>{ item.name }</h1>
</div>
));
}
}
return(
<div>
{productsList()}
<button id="change">Change</button>
</div>
);
}
}
export default App;
Rather than manually attaching event listeners, do it through React. In pretty much most cases you shouldn't be doing DOM operations directly.
class App extends React.Component {
// ...
/* You don't need this
componentDidUpdate() {
}
*/
handleChangeClick = () => {
this.changeParams();
this.ProductList();
}
// ...
render() {
// ...
return(
<div>
{productsList()}
<button id="change" onClick={this.handleChangeClick}>Change</button>
</div>
);
}
}
The reason why your approach doesn't work is because React may be producing and destroying DOM elements in ways you don't expect, so making sure you manually attach and detach event listeners to the right elements is difficult to get right.

Is setState running again and again in this React code?

I have created three react components and I don't know why I am getting an infinite network request and this warning: index.js:1375 Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
in MenuCategory (at App.js:19)
in App (at src/​index.js:5)
also a network request in MenuItems.js is getting called in a loop. I think it is due to setState but I don't know where is the error.
And here is my code :
import React from "react";
import MenuCategory from "./components/MenuCategory";
import MenuItems from "./components/MenuItems";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { shortName: "" };
}
handleProps = ss => {
if (this.state.shortName === "") {
this.setState({ shortName: ss });
}
// console.log(ss, ".../PP");
};
render() {
return (
<div className="App">
<MenuCategory callback={this.handleProps} />
<MenuItems shortNameProp={this.state.shortName} />
</div>
);
}
}
export default App;
import React from "react";
class MenuCategory extends React.Component {
constructor(props) {
super(props);
this.state = { category: "", selectedCat: "" };
}
async UNSAFE_componentWillMount() {
const url = "http://stream-restaurant-menu-svc.herokuapp.com/category";
await fetch(url)
.then(data => data.json())
.then(element => {
this.setState({ category: element });
});
}
menuCat = () => {
let cat = this.state.category;
// console.log(cat, "...Cat", this.state.selectedCat, "...Cat");
if (this.state.selectedCat !== "") {
this.props.callback(this.state.selectedCat);
}
return cat.map(items => {
return (
<li
key={items.short_name}
onClick={() => this.setState({ selectedCat: items.short_name })}
>
{items.name}
</li>
);
});
};
render() {
return <div>{this.state.category.length > 0 ? this.menuCat() : null}</div>;
}
}
export default MenuCategory;
import React from "react";
class MenuItems extends React.Component {
constructor(props) {
super(props);
this.state = { catItems: "", items: "" };
}
renderItems = () => {
let shortName = this.props.shortNameProp;
if (shortName !== "") {
const url =
"https://stream-restaurant-menu-svc.herokuapp.com/item?category=" +
shortName;
fetch(url)
.then(data => data.json())
.then(element => {
this.setState({ items: element });
});
}
if (this.state.items !== "") {
let selectedMenu = this.state.items;
console.log(selectedMenu);
return selectedMenu.map(item => {
return <div key={item.name}> {item.name}</div>;
});
}
};
render() {
return <div>{this.renderItems()}</div>;
}
}
export default MenuItems;
Let's call App a parent and MenuCategory a child.
Let's denote a function call as the '->' sign.
There is an infinite loop formed like that:
child.render -> child.menuCat -> child.props.callback -> parent.handleProps -> parent.setState -> parent.render -> child.render.

setState not setting when called from child component

I have a simple app which fetches some weather JSON and displays it. The user can either enter a location or they can hit a "Get lucky" button, which fetches a random city. the initial state is set in App.js
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
Next, I have my main App class, then I have a child component called that contains the form gubbins. I call it in app render as follows:
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
I use callback functions in there to set the query type (location or ID). An example of one of the call back functions in App.js is:
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
This is called in the form JS using:
props.setQueryType(e.target.attributes.query.value)
Now, here is the crux of the issue: the state doesn't update the first time, but DOES on the second click? In fact, other vars like queryString set in the fetch are not set until the second click.
App.js
import React, { Component } from 'react';
import './css/App.css';
import WeatherCard from './components/WeatherCard'
import Header from './components/Header'
import SearchForm from './components/SearchForm'
import cityData from './json/city.list'
const config = {
API: 'https://api.openweathermap.org/data/2.5/forecast',
API_KEY: process.env.REACT_APP_OPEN_WEATHER_MAP_API_KEY
}
class App extends Component {
constructor() {
super()
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
this.getWeatherData()
}
getWeatherData = (searchValue="london,gb") => {
let URL
URL = config.API + '?' + this.state.queryType + '='
URL += this.state.queryType === 'q' ? searchValue : this.state.queryID
URL += '&units=metric&APPID=' + config.API_KEY
console.log(URL)
fetch(URL)
.then( result => result.json() )
.then (
(result) => {
if ( result.cod === '200') {
this.setState({
status: result.cod,
weatherData: result,
queryString: result.city.name,
isLoaded: true
})
} else {
this.setState({
status: result.cod,
error: result.message,
isLoaded: false
})
}
},
(error) => {
this.setState({
isLoaded: false,
error: error
})
}
)
console.log(this.state.queryString)
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
getWeatherCards = () => {
let cards = []
for (let i = 0; i < this.state.weatherData.cnt; i++) {
cards.push(
<WeatherCard
key={i}
weatherList={this.state.weatherData.list[i]}
/>
)
}
return cards
}
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
setQueryID = () => {
let randomID = Math.floor(Math.random() * this.state.cityData.length)
let randomCityID = this.state.cityData[randomID].id
this.setState({
queryID: randomCityID
})
}
getlocationForm = () => {
return(
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
)
}
render = () => {
if (this.state.status !== '200') {
return (
<div className='App'>
<Header
status={this.state.status}
error={this.state.error}
/>
{this.getlocationForm()}
</div>
)
} else {
return (
<div className='App'>
{
this.state.isLoaded && (
<Header
cityName={this.state.weatherData.city.name}
countryName={this.state.weatherData.city.country}
status={this.state.status}
error={this.state.error}
/>
)
}
{this.getlocationForm()}
{
this.state.isLoaded && (
<div className='weather-cards'>
{this.getWeatherCards()}
</div>
)
}
</div>
)
}
}
}
export default App;
SearchForm.js
import React from 'react'
const SearchForm = (props) => {
let handleChange = function(e) {
props.handleChange(e)
}
let handleClick = function(e) {
e.preventDefault()
props.setQueryType(e.target.attributes.query.value)
if (e.target.attributes.query.value === 'id') {
props.setQueryID()
}
props.getWeatherData()
}
return (
<div>
<form className="search-form">
<input
type="text"
id="query"
name="query"
placeholder="Enter a location..."
onChange={handleChange}
/>
<button
type="submit"
query="q"
onClick={handleClick}
>
Submit
</button>
<button
type="submit"
query="id"
onClick={handleClick}
>
I'm feeling lucky...
</button>
</form>
</div>
)
}
export default SearchForm
In your App.js constructor add this.setQueryType = this.setQueryType.bind(this)
That line will bind the context of this to the current component, so when called from a child, will update parent state.
I think the problem comes from the fact that when you call getWeatherData,
you don't know if the setState will be over as it is an asynchronous method. (as you can see in the documentation)
So the best way, to ensure that the setState is done before calling your method without being certain of the state of your component, would be to use the callBack parameter of the setState to ensure it runs after the setState method has been finished.
try to put your this.getWeatherData() into the componentDidMount and remove it from the constructor
componentDidMount() {
this.getWeatherData()
}

Automatically render child component when state has been updated in parent component

The parent component Dashboard holds the state for every ListItem I add to my Watchlist. Unfortunately, every time I am adding an Item, it gets added to the DB, but only shows up when I refresh the browser.
class UserDashboard extends React.Component {
state = {
data: []
}
componentWillMount() {
authService.checkAuthentication(this.props);
}
isLoggedIn = () => {
return authService.authenticated()
}
getAllCoins = () => {
//fetches from backend API
}
addWishlist = () => {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
console.log("CHILD WAS CLICKED")
}
componentDidMount() {
this.getAllCoins()
.then(things => {
this.setState({
data: things
})
})
}
render() {
return (
<div className="dashboard">
<h1>HI, WELCOME TO USER DASHBOARD</h1>
<SearchBar
addWishlist={this.addWishlist}
/>
<UserWatchlist
data={this.state.data}
/>
</div>
);
}
}
The User Watchlist:
class UserWatchlist extends React.Component {
constructor(props) {
super(props)
}
// componentDidUpdate(prevProps) {
// if (this.props.data !== prevProps.data) {
// console.log("CURRENT", this.props.data)
// console.log("PREVs", prevProps.data)
// }
// }
render() {
return (
<div>
<h2>These are tssssyou are watching:</h2>
<ul className="coin-watchlist">
{
this.props.data.map((coin, idx) => {
return <ListItem key={idx}
coin={coin.ticker}
price={coin.price}
/>
})
}
</ul>
</div>
)
}
}
The search Bar that shows potential Items to watch over:
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
coins: [],
searchValue: ""
}
}
searchHandler = e => {
e.preventDefault()
const value = e.target.value
this.setState({
searchValue: value
});
if (value === "") {
this.setState({
coins: []
})
} else {
this.getInfo()
}
}
getInfo = () => {
// Searches the API
}
addWishlist = () => {
this.props.addWishlist();
}
render() {
const {coins, searchValue} = this.state
return (
<div className="coin-search">
<form>
<input
type="text"
className="prompt"
placeholder="Search by ticker symbol"
value={searchValue}
onChange={this.searchHandler}
/>
</form>
<ul className="search-suggestions">
{
coins.filter(searchingFor(searchValue)).map( coin =>
<Currency
coin={coin}
addWishlist={this.addWishlist}
/>
)
}
</ul>
</div>
);
}
}
And the actual Currency that gets clicked to be added:
class Currency extends React.Component {
addToWatchlist = () => {
// POST to backend DB to save
};
fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist()
this.props.addWishlist()
}
render() {
return(
<div className="search-results">
<li>
<h3> { this.props.coin.currency } </h3>
<button
className="add-to-list"
onClick={this.clickHandler}
>
+ to Watchlist
</button>
</li>
</div>
)
}
}
As you can see, I am sending props down all the way down to child. When I click the button to Add to Watchlist, I see the console.log message appear, saying "CHILD WAS CLICKED". I've even tried just calling the method to fetch from backend API again.
Also, in UserWatchlist, I've tried a componentDidUpdate, but both prevProps and this.props show the very same array of data. Somewhere in the chain, my data is getting lost.
This is also my first time posting a question here, so if it can be improved, I am happy to add extra details and contribute something to this community
You probably forgot to wait for addToWatchlist to complete:
addToWatchlist = () => {
// POST to backend DB to save
return fetch("/api/add-coin", settings)
.catch(err => {
return err
})
}
clickHandler = () => {
this.addToWatchlist().then(() => {
this.props.addWishlist()
})
}

Populating React dropdown asyncrhonously with Redux

Background
I'm attempting to create a dropdown that retrieves State Codes (AZ, WI, WY, etc.) from a backend API and then populates an on-screen dropdown with the values.
I have a React component that looks like this (an ellipsis representing code that I'm omitting for clarity):
Person.jsx
export class Person extends React.Component {
constructor(props) {
super(props);
...
this.props.getStateCodes();
}
render(){
...
<select
id="personState"
name="personState"
className="form-control dropDownStyle"
onChange={this.handleChange}
value={this.props.person.personState}
>
{this.props.stateCodes && this.props.stateCodes.map((option) => (
<option key={option.id} value={option.data}>{option.data}</option>
))
}
</select>
...
}
}
I then have Redux action creators, including an excerpt like this:
personContract.js
export const actionCreators = {
...
getStateCodes: () => async (dispatch) => {
getStateCodesResponse(dispatch);
},
...
export function getStateCodesResponse(dispatch) {
const endpoint = window.location.origin + '/Home/GetStateCodes';
fetch(endpoint, {
credentials: 'same-origin'
})
.then(function (response) {
if (!response.ok) {
const errors = ['Unable to retrieve state codes.'];
dispatch({ type: "SET_ERROR", errors: errors });
document.body.style.cursor = 'default';
return;
}
return response.json();
}).then(function (data) {
if (data !== undefined) {
const stateCodes = data.stateCodes;
// const stateCodes = result.PayLoad.StateCodes;
document.body.style.cursor = 'default';
dispatch({ type: 'STATECODES', stateCodes });
}
});
}
...
}
Then a reducer that includes:
Contract.js
const initialState ={
...
stateCodes: [],
...
};
export const reducer = (state, action) => {
...
if (action.type == "STATECODES"){
const stateCodes = action.stateCodes;
return {
...state,
errors: [],
stateCodes: stateCodes
}
}
...
}
Problem
Initially, I did not include {this.props.stateCodes && in the Person.jsx file. The issue then, was that I'd get an error that this.props.stateCodes was not defined. I added in {this.props.stateCodes &&, however, now it never runs this.props.stateCodes.map at all. It's almost as if I need to render() to run again after the State Codes have been retrieved, but I don't know how to accomplish that.

Categories