I am an experienced developer but pretty new to React. I wanted to go on my own and build a simple little weather app that updates the image, tempature, and description of each day when you click on the + or - buttons.
I got it to work but I think it is pretty messy and was hoping for some advice. Firstly, is it bad practice to create a state on a component from props passed in by its parent? I know I could set the values within the state but just decided to keep it the way i designed it initially (at first WeatherCard was a stateless component so i was pulling in props either way). However, I could only imagine that there would be a couple instances that this would be applicable...?
Also, I ran into a problem with injecting images, I was trying to use
<img src={'./folder/image.extension'}>
and then update the src but couldn't figure out how to do that, so I made the function getImage() to help me out. Is there a way you guys can think of to update the src of the image so I can get rid of getImage() and just update the state to a string (file path) that the src pulls from?
Lastly I am really baffled about how the description was reacting. The description would update at a different time than the image, which was not what I was expecting. If you look at the code you will see that I had to adjust the if statements in handleClick to a -1 and a +1 to make up for some kind of what seemed to be like a delay. I don't really understand the issue here, but thats how I compensated for it. What are your guys' thoughts?
Overall, I know this is probably some pretty junk code here but I intend on improving it. Will probably start from scratch and maybe it will go better for me but hoping for some advice!
WeatherCard.js
import React, { Component } from 'react';
import sunny from '../img/sunny.png';
import partly from '../img/partly.png';
import cloudy from '../img/cloudy.png';
import rainy from '../img/rainy.png';
class WeatherCard extends Component {
state = {
day: this.props.data.day,
temp: this.props.data.temp,
description: this.props.data.description
}
getImage = () => {
if (this.state.temp >= 95 ) {
return <img src={ sunny } alt="sunny"></img>
} else if (this.state.temp >= 85) {
return <img src={ partly } alt="partly cloudy"></img>
} else if (this.state.temp >= 75) {
return <img src={ cloudy } alt="cloudy"></img>
} else {
return <img src={ rainy } alt="rainy"></img>
}
}
handleClick = (e) => {
if (e.target.innerHTML === '+') {
this.setState(function(prevState, props){
if (this.state.temp >= 94) {
return {description: 'Sunny'}
} else if (this.state.temp >= 84) {
return {description: 'Partly Cloudy'}
} else if (this.state.temp >= 74) {
return {description: 'Cloudy'}
} else {
return {description: 'Rainy'}
}
});
this.setState ({
temp: (this.state.temp+1)
});
} else {
this.setState(function(prevState, props){
if (this.state.temp >= 96) {
return {description: 'Sunny'}
} else if (this.state.temp >= 86) {
return {description: 'Partly Cloudy'}
} else if (this.state.temp >= 76) {
return {description: 'Cloudy'}
} else {
return {description: 'Rainy'}
}
});
this.setState ({
temp: (this.state.temp-1)
});
}
}
render() {
return (
<div className="card">
<div className="card-head">{ this.state.day }</div>
<div className="card-body">
<h1>{ this.state.temp }</h1>
{ this.getImage() }
<p>{ this.state.description }</p>
</div>
<div className="controls">
<div className="upButton" onClick={ this.handleClick }>+</div>
<div className="downButton" onClick={ this.handleClick }>-</div>
</div>
</div>
)
}
}
export default WeatherCard;
App.js
import React, { Component } from 'react';
import WeatherCard from './components/WeatherCard';
class App extends Component {
state = {
data : [
{
day : 'Monday',
temp : 100,
description : 'Sunny'
},
{
day : 'Tuesday',
temp : 100,
description : 'Sunny'
},
{
day : 'Wednesday',
temp : 100,
description : 'Sunny'
},
{
day : 'Thursday',
temp : 100,
description : 'Sunny'
},
{
day : 'Friday',
temp : 100,
description : 'Sunny'
},
{
day : 'Saturday',
temp : 100,
description : 'Sunny'
},
{
day : 'Sunday',
temp : 100,
description : 'Sunny'
}
]
}
renderCards = () => {
return this.state.data.map((card, i) => <WeatherCard key = {i} data = {this.state.data[i]}></WeatherCard>)
}
render() {
return (
<div className="App">
<div className="cards">
{ this.renderCards() }
</div>
</div>
);
}
}
export default App;
2 Answers 2
The initial data
Since every day shares the exact same default temperature, repeating it would be useless, you can instead map a list of days and create a JSON for each element using map.
The description should also be removed from your data, since it entirely depends on the temperature, there is no reason to store both temp
and description
.
this.state = {
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',].map(day => ({
day,
temp: 100
}))
}
Setting your initial state
The React documentation recommends using a class contructor instead of setting your state raw into your class. This solution may wokr for now, but it can cause unexpected behavior on the long term.
constructor(props) {
super(props)
this.state = {
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',].map(day => ({
day,
temp: 100
}))
}
}
Getting the description and image
Having blocks of if/else
nested statements is almost never necessary when using JSON objects. Just create an array containing every possible description/image combination and which temperature it represent.
When done, use a find function to get get the corresponding description/image. This function will execute a verification (weather => temp >= weather.value
) on every JSON of list until it finds a correct one, here, the first JSON that has a value lower than the given temperature.
Code :
getInfos = temp => {
return [
{
value: 95,
description: 'Sunny',
image: sunny
},
{
value: 85,
description: 'Partly Cloudy',
image: partly
},
{
value: 75,
description: 'Cloudy',
image: cloudy
},
{
value: -Infinity,
description: 'Rainy',
image: rainy
},
].find(weather => temp >= weather.value)
}
Changing the temperature
To change your temperature, simply add a parameter saying by how much you want to vary it :
<div className="upButton" onClick={this.changeTemp(1)}>+</div>
<div className="downButton" onClick={this.changeTemp(-1)}>-</div>
Then, make a function that receives both this parameter and the click event (even if you're not using it). this function should also update the information about the image and the description using getInfos
:
changeTemp = value => ev => {
this.setState(prevState => ({
temp: prevState.temp + value,
...this.getInfos(prevState.temp + value)
}))
}
Full code :
class App extends Component {
constructor(props) {
super(props)
this.state = {
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',].map(day => ({
day,
temp: 100
}))
}
}
cardClicked = card => event => {
this.setState({ data })
}
render() {
return (
<div className="App">
<div className="cards">
{this.state.data.map(card => <WeatherCard key={card.day} data={card}></WeatherCard>)}
</div>
</div>
);
}
}
import sunny from '../img/sunny.png';
import partly from '../img/partly.png';
import cloudy from '../img/cloudy.png';
import rainy from '../img/rainy.png';
class WeatherCard extends React.Component {
constructor(props) {
super(props)
const { temp } = props
this.state = {
temp
}
getInfos(temp)
}
getInfos = temp => {
return [
{
value: 95,
description: 'Sunny',
image: sunny
},
{
value: 85,
description: 'Partly Cloudy',
image: partly
},
{
value: 75,
description: 'Cloudy',
image: cloudy
},
{
value: -Infinity,
description: 'Rainy',
image: rainy
},
].find(weather => temp >= weather.value)
}
changeTemp = value => ev => {
this.setState(prevState => ({
temp: prevState.temp + value,
...this.getInfos(prevState.temp + value)
}))
}
render() {
const { day } = this.props
const { temp, image, description } = this.state
return (
<div className="card">
<div className="card-head">{day}</div>
<div className="card-body">
<h1>{temp}</h1>
<img src={image} alt={description}></img>
<p>{description}</p>
</div>
<div className="controls">
<div className="upButton" onClick={this.changeTemp(1)}>+</div>
<div className="downButton" onClick={this.changeTemp(-1)}>-</div>
</div>
</div>
)
}
}
EDIT :
Here's an even shorter version of your getInfos
function using array deconstruction, just because :
getInfos = temp =>
[
[95, 'Sunny', sunny],
[85, 'Partly Cloudy', partly],
[75, 'Cloudy', cloudy],
[-Infinity, 'Rainy', rainy]
].find(([value]) => temp >= value)
-
\$\begingroup\$ There were a couple bugs in what you gave me but overall this is just what I was looking for. Thanks! \$\endgroup\$durandamien1997– durandamien19972019年01月09日 06:20:07 +00:00Commented Jan 9, 2019 at 6:20
-
\$\begingroup\$ @SᴀᴍOnᴇᴌᴀ I do not understand how a Map will help us in that case. Since every result is a temperature range and not just :
95
. Can you show me your solution ? @user3158670 Sorry, the code is partly not tested, I hope the explanation made it up for you \$\endgroup\$Treycos– Treycos2019年01月09日 08:55:53 +00:00Commented Jan 9, 2019 at 8:55 -
\$\begingroup\$ ah nuts... I just scanned the filter function and believed I saw an equals sign. nevermind :/ \$\endgroup\$2019年01月09日 19:50:06 +00:00Commented Jan 9, 2019 at 19:50
-
\$\begingroup\$ I made the
getInfos
function even shorter if you are interested ;) \$\endgroup\$Treycos– Treycos2019年01月14日 10:52:57 +00:00Commented Jan 14, 2019 at 10:52
Here is the working code
--- App.js ---
import React, { Component } from 'react';
import WeatherCard from './components/WeatherCard';
class App extends Component {
constructor(props) {
super(props)
this.state = {
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',].map((day, i) => ({
day,
temp: (100 - (i*10))
}))
}
}
render() {
return (
<div className="App">
<div className="cards">
{this.state.data.map(card => <WeatherCard key={card.day} data={card}></WeatherCard>)}
</div>
</div>
);
}
}
export default App;
--- WeatherCard.js ---
import sunny from '../img/sunny.png';
import partly from '../img/partly.png';
import cloudy from '../img/cloudy.png';
import rainy from '../img/rainy.png';
import React, { Component } from 'react';
class WeatherCard extends React.Component {
constructor(props) {
super(props)
const { day, temp } = props.data
this.state = {
day,
temp,
...this.getData(temp)
}
}
getData = temp => {
return [
{
value: 95,
description: 'Sunny',
image: sunny
},
{
value: 85,
description: 'Partly Cloudy',
image: partly
},
{
value: 75,
description: 'Cloudy',
image: cloudy
},
{
value: -Infinity,
description: 'Rainy',
image: rainy
},
].find(weather => temp >= weather.value)
}
changeTemp = value => ev => {
this.setState(prevState => ({
temp: prevState.temp + value,
...this.getData(prevState.temp + value)
}))
}
render() {
const { day } = this.props.data
const { temp, image, description } = this.state
return (
<div className="card">
<div className="card-head">{day}</div>
<div className="card-body">
<h1>{temp}</h1>
<img src={image} alt={description}></img>
<p>{description}</p>
</div>
<div className="controls">
<div className="upButton" onClick={this.changeTemp(1)}>+</div>
<div className="downButton" onClick={this.changeTemp(-1)}>-</div>
</div>
</div>
)
}
}
export default WeatherCard;
If I remember correctly I think the biggest issues with what you provided (@Treycos) were reference bugs ('this' not placed where it should have been) - etc. But some really good stuff you provided, thanks again!