3
\$\begingroup\$

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;
\$\endgroup\$
1
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ There were a couple bugs in what you gave me but overall this is just what I was looking for. Thanks! \$\endgroup\$ – durandamien1997 Jan 9 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 Jan 9 at 8:55
  • \$\begingroup\$ ah nuts... I just scanned the filter function and believed I saw an equals sign. nevermind :/ \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jan 9 at 19:50
  • \$\begingroup\$ I made the getInfos function even shorter if you are interested ;) \$\endgroup\$ – Treycos Jan 14 at 10:52
0
\$\begingroup\$

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!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.