Can't render parent props - javascript

I'm making a React app using openweathermap API. Right now I receive the list of weather data. I'm trying to highlight the weather if I click it.
To make this happen, I wrote on App.js to pass a prop to WeatherDetail.js, but so far seems like WeatherDetail.js doesn't recognize props from its parent.
class App extends React.Component {
constructor(props) {
super(props);
}
state = { forecasts: [], selectedWeather: null }
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState ({
forecasts: response.data.list,
city: response.data.city.name,
selectedWeather: response.data.list[0]
})
}
}
onWeatherSelectFunction = (item) => {
this.setState({ selectedWeather: item });
};
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<WeatherDetail itemToChild={this.state.selectedWeather} />
<WeatherList
onWeatherSelect={this.onWeatherSelectFunction}
weathers={this.state.forecasts}
city={this.state.city}
/>
</div>
);
}
}
export default App;
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
const WeatherItem = ({item, onWeatherSelectFromList, humidity, city, temp }) => {
return (
<div>
<div onClick={() => onWeatherSelectFromList(item)} >
{city}<br /> <-- Appears on screen
{humidity}<br /> <-- Appears on screen
</div>
</div>
);
};
const WeatherList = ({weathers, onWeatherSelect, city}) => {
const renderedList = weathers.map((item) => {
return (
<div>
<WeatherItem
city={city}
temp={item.main.temp}
humidity={item.main.humidity}
temperature={item.weather.icon}
onWeatherSelectFromList={onWeatherSelect}
/>
</div>
);
});
return (
<div className="flex">
{renderedList}
</div>
);
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
How do I connect App.js and WeatherDetail.js using props?

In your App.js file you are passing only one props called itemToChild
<WeatherDetail itemToChild={this.state.selectedWeather} />
In your WeatherDetail file from where you're getting forecasts? do you get forecasts from redux store?
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
change your code with this.
const WeatherDetail = (props) => {
console.log("props.itemToChild", props.itemToChild) // check this console that do you get data as you want.
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}

You have already destructured the props so there is no need to mention props in WeatherDetail component
and also there is an extra parenthesis after the return statement you should remove that also...
Old:
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
New:
const WeatherDetail = ({ forecasts, itemToChild }) => {
const weather = itemToChild;
if (!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div>;
};

Related

How do I pass a function as a property to a React component?

In an effort to figure out the problem I explain in my (unanswered) question "How do I update a react-bootstrap-table2 cell value after it's edited so a button component in a different column has it?", I attempted to pass a function that returns the cell value into the button component:
class NominationQueueBootstrapTable extends Component {
...
getInitialBid = (row) => {
console.log('getInitialBid');
return this.state.data.find(r => r.rank === row.rank).initialBid;
}
render() {
const { auctionId } = this.props;
const { teamId } = this.props;
function buttonFormatter(cell, row) {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
getInitialBid={ this.getInitialBid }
/>
);
}
...
My NominateButton component returns another button wrapper component that calls a mutator:
class NominateButton extends Component {
render() {
const { row } = this.props;
const { auctionId } = this.props;
const { teamId } = this.props;
const playerId = parseInt(this.props.row.player.id, 10);
return (
<Query
query={TEAM_NOMINATIONS_OPEN_QUERY}
variables={{ team_id: teamId }}>
{({ data, loading, error, subscribeToMore }) => {
if (loading) return <Loading />;
if (error) return <Error error={error} />;
return (
<NominateButtonMutator
auctionId={ auctionId }
teamId={ teamId }
playerId={ playerId }
row={ row }
nominationsOpen={ data.team.nominationsOpen }
subscribeToNominationsOpenChanges={ subscribeToMore }
getInitialBid={ this.props.getInitialBid }
/>
);
}}
</Query>
);
}
}
And because I need to invoke the mutator when the button is pressed, my onClick function first calls the getInitialBid function passed in as a property, and then invokes the mutator:
class NominateButtonMutator extends Component {
...
handleButtonPressed = (submitBid) => {
this.setState({bidAmount: this.props.getInitialBid(this.props.row)});
submitBid();
};
render() {
const { auctionId } = this.props;
const { teamId } = this.props;
const { playerId } = this.props;
const { nominationsOpen } = this.props;
return (
<Mutation
mutation={SUBMIT_BID_MUTATION}
variables={{
auction_id: auctionId,
team_id: teamId,
player_id: playerId,
bid_amount: this.state.bidAmount
}}
>
{(submitBid, { loading, error }) => (
<div>
<Error error={error} />
<Button
disabled={ loading || !nominationsOpen }
onClick={() => this.handleButtonPressed(submitBid) }
variant="outline-success">
Nominate
</Button>
</div>
)}
</Mutation>
);
}
}
(The onClick= code was updated from azium's comment.)
When I run this, I get:
"TypeError: this.props.getInitialBid is not a function"
Is this a workable strategy? Why isn't this.props.getInitialBid a function?
You are using the old function syntax, so this is not bound correctly.
change:
function buttonFormatter(cell, row) {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
// scoped to your local function not your class
getInitialBid={ this.getInitialBid }
/>
);
}
to
const buttonFormatter = (cell, row) => {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
// this is scoped "lexically" aka to your class
getInitialBid={ this.getInitialBid }
/>
);
}

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

Why my react-redux component doesn't render when the api requisition returns?

I'm studying React-Redux and creating a store frontend to test my skills. However, I'm having a problem that I can not solve.
My API was returning all products that I have on the database, so I changed it to filter by category. So I changed my ProductList component and SingleProduct component to reflect that change. After that, ProductList doesn't render when the API call returns. My App.jsx is the main component that renders the entire page, the ProductsList was rendered using {this.props.children} but to send a category id to the component, I changed it to__ as can be seen below:
App.jsx
class App extends Component {
render() {
let category = 600;
return (
<div className="page">
<Helmet titleTemplate="Ecommerce - %s" />
<Header />
<Navbar />
<MainBanner />
<Tabs />
<ProductList category={category} />
<Footer />
</div>
)
}
}
ProductList.jsx
class ProductList extends Component {
addToCart(id) {
this.props.addToCart(id);
}
addToWishlist(id) {
this.props.addToWishlist(id);
}
removeFromCart(id) {
this.props.removeFromCart(id);
}
removeFromWishlist(id) {
this.props.removeFromWishlist(id);
}
componentDidMount() {
this.props.fetchProducts(this.props.category);
}
render() {
return (
<div className="tab1">
<div className="category-products slider-home">
<ul className="home_slider products-grid products-grid--max-2-col slick-initialized slick-slider">
{this.props.products.map((product) => {
<ProductItem key={product.id}
product={product}
addToCart={this.addToCart.bind(this)}
addToWishlist={this.addToWishlist.bind(this)}
removeFromWishlist={this.removeFromWishlist.bind(this)}
removeFromCart={this.removeFromCart.bind(this)}
wishlist={this.props.wishlist}
cart={this.props.cart} />
})}
</ul>
</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch, ownProps) => ({
addToCart: (id) => dispatch(addToCard(id)),
addToWishlist: (id) => dispatch(addToWishlist(id)),
removeFromCart: (id) => dispatch(removeFromCart(id)),
removeFromWishlist: (id) => dispatch(removeFromWishlist(id)),
fetchProducts: () => dispatch(fetchProducts(ownProps.category)),
});
const mapStateToProps = (state) => {
return {
products: state.ProductsReducer.data,
wishlist: state.WishlistReducer.data,
cart: state.CartReducer.data
}
}
fetchProducts.js
const requestProducts = () => {
return {
type: 'REQUEST_PRODUCTS',
}
}
const receiveProducts = (data) => {
return {
type: 'RECEIVE_PRODUCTS',
payload:data
}
}
export const fetchProducts = (data) => {
return dispatch => {
dispatch(requestProducts());
return axios.get('http://localhost/api/products/getProducts?category_id='+data, {})
.then(response => response)
.then(json => {
dispatch(receiveProducts(json.data))
})
}
}
ProductsReducer.js
const ProductsReducer = (state = {fetching:false,data:[]}, action) => {
switch(action.type){
case 'REQUEST_PRODUCTS':
return Object.assign({}, state,{
fetching:true,
data:[]
})
break
case 'RECEIVE_PRODUCTS':
return Object.assign({}, state,{
fetching:false,
data:action.payload
})
break
default:
return state
}
}
Finally, the errors i'm getting and the logging results:
See here
After editing, got no errors but component not rendering yet.See here
Edit 0:
Changed ProductList componentWillMount() to componentDidMount
Added another console image
Edit 1:
- Changed ProductList.jsx to most recent version. (still not rendering the list)

Wait for react-promise to resolve before render

So I have a large set of data that I'm retrieving from an API. I believe the problem is that my component is calling the renderMarkers function before the data is received from the promise.
So I am wondering how I can wait for the promise to resolve the data completely before calling my renderMarkers function?
class Map extends Component {
componentDidMount() {
console.log(this.props)
new google.maps.Map(this.refs.map, {
zoom: 12,
center: {
lat: this.props.route.lat,
lng: this.props.route.lng
}
})
}
componentWillMount() {
this.props.fetchWells()
}
renderMarkers() {
return this.props.wells.map((wells) => {
console.log(wells)
})
}
render() {
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)
}
}
function mapStateToProps(state) {
return { wells: state.wells.all };
}
export default connect(mapStateToProps, { fetchWells })(Map);
You could do something like this to show a Loader until all the info is fetched:
class Map extends Component {
constructor () {
super()
this.state = { wells: [] }
}
componentDidMount() {
this.props.fetchWells()
.then(res => this.setState({ wells: res.wells }) )
}
render () {
const { wells } = this.state
return wells.length ? this.renderWells() : (
<span>Loading wells...</span>
)
}
}
for functional components with hooks:
function App() {
const [nodes, setNodes] = useState({});
const [isLoading, setLoading] = useState(true);
useEffect(() => {
getAllNodes();
}, []);
const getAllNodes = () => {
axios.get("http://localhost:5001/").then((response) => {
setNodes(response.data);
setLoading(false);
});
};
if (isLoading) {
return <div className="App">Loading...</div>;
}
return (
<>
<Container allNodes={nodes} />
</>
);
}
Calling the render function before the API call is finished is fine. The wells is an empty array (initial state), you simply render nothing. And after receiving the data from API, your component will automatically re-render because the update of props (redux store). So I don't see the problem.
If you really want to prevent it from rendering before receiving API data, just check that in your render function, for example:
if (this.props.wells.length === 0) {
return null
}
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)
So I have the similar problem, with react and found out solution on my own. by using Async/Await calling react
Code snippet is below please try this.
import Loader from 'react-loader-spinner'
constructor(props){
super(props);
this.state = {loading : true}
}
getdata = async (data) => {
return await data;
}
getprops = async (data) =>{
if (await this.getdata(data)){
this.setState({loading: false})
}
}
render() {
var { userInfo , userData} = this.props;
if(this.state.loading == true){
this.getprops(this.props.userData);
}
else{
//perform action after getting value in props
}
return (
<div>
{
this.state.loading ?
<Loader
type="Puff"
color="#00BFFF"
height={100}
width={100}
/>
:
<MyCustomComponent/> // place your react component here
}
</div>
)
}

Categories