最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - react expand and collapse just one panel - Stack Overflow

programmeradmin10浏览0评论

Need help with react... Trying to implement a collapsible list of cards with weather information. Already implemented the behavior of expand and collapse, but when i clicked on one panel the other panel open at the same time (i have 2 panels and need 7 to display weahter for 7 days of the week).

How can i open and close just one panel?

Code:

import React, { Component } from 'react';
import Moment from 'react-moment';

import RandomGif from './RandomGif.js';

const urlForCity = city => `://api.openweathermap/data/2.5/forecast/daily?q=${city}&units=metric&cnt=7&appid=1fba7c3eaa869008374898c6a606fe3e`

class OpenWapi extends Component {
  constructor(props) {
    super(props);
    this.state = {
      requestFailed: false,
      shown: false
    }
    thisponentDidMount = thisponentDidMount.bind(this);
    this.toggle = this.toggle.bind(this);
  }

  componentDidMount() {
    fetch(urlForCity(this.props.city))
      .then(response => {
        if(!response.ok) {
          throw Error("Network request failed")
        }
        return response;
      })
      .then(data => data.json())
      .then(data => {
        this.setState({
          weatherData: data
        })
      }, () => {
        this.setState({
          requestFailed: true
        })
      })
  }

  toggle() {
        this.setState({
            shown: !this.state.shown
        });
    }

  render() {

    if(this.state.requestFailed) return <p>Request Failed.</p>;
    if(!this.state.weatherData) return <p>Loading...</p>;

    return (
      <div>
        <p>City: {this.state.weatherData.city.name}</p>

        {/* Day 1 */}
        <div onClick={this.toggle} className="dayWeekItem">
          <div className="top-content">
            <div className="icon-weather"></div>
            <div className="date">
              <div className="weekday">Today</div>
              <div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[0].dt}</Moment></div>
            </div>
            <div className="temperature">
              <div className="temp-high">{parseInt(this.state.weatherData.list[0].temp.max)}º</div>
              <div className="temp-low">{parseInt(this.state.weatherData.list[0].temp.min)}º</div>
            </div>
          </div>
          <div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
            <div className="weather-gif" >
              <RandomGif keyword={this.state.weatherData.list[0].weather[0].description} />
            </div>
          </div>
        </div>



        {/* Day 2 */}
        <div onClick={this.toggle} className="dayWeekItem">
          <div className="top-content">
            <div className="icon-weather"></div>
            <div className="date">
              <div className="weekday">Tomorrow</div>
              <div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[1].dt}</Moment></div>
            </div>
            <div className="temperature">
              <div className="temp-high">{parseInt(this.state.weatherData.list[1].temp.max)}º</div>
              <div className="temp-low">{parseInt(this.state.weatherData.list[1].temp.min)}º</div>
            </div>
          </div>
          <div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
            <div className="weather-gif" >
              <RandomGif keyword={this.state.weatherData.list[1].weather[0].description} />
            </div>
          </div>
        </div>

        {/* Day 3 */}


        {/* Day 4 */}


        {/* Day 5 */}


      </div>
    )
  }
}

export default OpenWapi;

Need help with react... Trying to implement a collapsible list of cards with weather information. Already implemented the behavior of expand and collapse, but when i clicked on one panel the other panel open at the same time (i have 2 panels and need 7 to display weahter for 7 days of the week).

How can i open and close just one panel?

Code:

import React, { Component } from 'react';
import Moment from 'react-moment';

import RandomGif from './RandomGif.js';

const urlForCity = city => `https://cors-anywhere.herokuapp.com/http://api.openweathermap.org/data/2.5/forecast/daily?q=${city}&units=metric&cnt=7&appid=1fba7c3eaa869008374898c6a606fe3e`

class OpenWapi extends Component {
  constructor(props) {
    super(props);
    this.state = {
      requestFailed: false,
      shown: false
    }
    this.componentDidMount = this.componentDidMount.bind(this);
    this.toggle = this.toggle.bind(this);
  }

  componentDidMount() {
    fetch(urlForCity(this.props.city))
      .then(response => {
        if(!response.ok) {
          throw Error("Network request failed")
        }
        return response;
      })
      .then(data => data.json())
      .then(data => {
        this.setState({
          weatherData: data
        })
      }, () => {
        this.setState({
          requestFailed: true
        })
      })
  }

  toggle() {
        this.setState({
            shown: !this.state.shown
        });
    }

  render() {

    if(this.state.requestFailed) return <p>Request Failed.</p>;
    if(!this.state.weatherData) return <p>Loading...</p>;

    return (
      <div>
        <p>City: {this.state.weatherData.city.name}</p>

        {/* Day 1 */}
        <div onClick={this.toggle} className="dayWeekItem">
          <div className="top-content">
            <div className="icon-weather"></div>
            <div className="date">
              <div className="weekday">Today</div>
              <div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[0].dt}</Moment></div>
            </div>
            <div className="temperature">
              <div className="temp-high">{parseInt(this.state.weatherData.list[0].temp.max)}º</div>
              <div className="temp-low">{parseInt(this.state.weatherData.list[0].temp.min)}º</div>
            </div>
          </div>
          <div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
            <div className="weather-gif" >
              <RandomGif keyword={this.state.weatherData.list[0].weather[0].description} />
            </div>
          </div>
        </div>



        {/* Day 2 */}
        <div onClick={this.toggle} className="dayWeekItem">
          <div className="top-content">
            <div className="icon-weather"></div>
            <div className="date">
              <div className="weekday">Tomorrow</div>
              <div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[1].dt}</Moment></div>
            </div>
            <div className="temperature">
              <div className="temp-high">{parseInt(this.state.weatherData.list[1].temp.max)}º</div>
              <div className="temp-low">{parseInt(this.state.weatherData.list[1].temp.min)}º</div>
            </div>
          </div>
          <div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
            <div className="weather-gif" >
              <RandomGif keyword={this.state.weatherData.list[1].weather[0].description} />
            </div>
          </div>
        </div>

        {/* Day 3 */}


        {/* Day 4 */}


        {/* Day 5 */}


      </div>
    )
  }
}

export default OpenWapi;
Share Improve this question edited Nov 19, 2022 at 18:51 Daniel Widdis 9,13113 gold badges48 silver badges68 bronze badges asked Dec 28, 2017 at 19:54 BrunoBruno 931 gold badge1 silver badge4 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 7

I would have an object to represent the state, a field for each panel.

Like this:

constructor(props) {
    ...
    this.state = {
      requestFailed: false,
      shown: {}
    }
    ...
}

...

toggle(panelNumber) {
   this.setState({
        shown: {
            ...this.state.shown,
            [panelNumber]: !this.state.shown[panelNumber]
        }
    });
}

...

The toogle function is used like this, for instance, Day 1:

<div onClick={() => this.toggle(1)} className="dayWeekItem">
    ...
</div>

And to show in html, for instance, Day 1:

      <div className={this.state.shown[1] ? "toggleContent-open" : "toggleContent-closed"} >
        <div className="weather-gif" >
          <RandomGif keyword={this.state.weatherData.list[0].weather[0].description} />
        </div>
      </div>

They all will collapse always with your implementation.

You have a state

state = {
  shown: true
}

You have a function to toggle it

toggle = () => {
   this.setState(shown: !this.state.shown)
}

And you render the component, using the this.state.shown in two places, but the value will always be one true or false

render() {
    return(<div .....//something>
        <div onClick={this.toggle}>
           { this.state.shown ? <SomeComponent or HTML Tag> : null }
        </div>
        <div onClick={this.toggle}>
          { this.state.shown ? <SomeComponent or HTML Tag> : null }
        </div>
    </div>)
}

So where ever you toggle, once the state is updated and render method is called again to paint the view, both sections of divs get the sameBoolean` value. Therefore, they both collapse.

Best Solution I can offer for this problem will be:

Create a separate component which has two jobs to be do: 1. Maintains its own state, of collapse true or false. 2. Render the children given to it without wondering what they might be.

So let say

 class WeatherWidget extends React.PureComponent {
   state= {
     shown: true
   }     
   toggle = () => this.setState({shown: !this.state.shown})
   render() {
       return(
         <div onClick={this.toggle} className="dayWeekItem">
            <div className="top-content">
            <div className="icon-weather"></div>
            <div className="date">
              <div className="weekday">Today</div>
              <div className="day-long">
                    <Moment unix format="MMM DD YYYY">{this.props.date}</Moment>
              </div>
            </div>
            <div className="temperature">
              <div className="temp-high">{parseInt(this.props.maxTemp)}º
              </div>
              <div className="temp-low">{parseInt(this.props.minTemp)}º
              </div>
            </div>
         </div>
             <div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
                 <div className="weather-gif" >
                    <RandomGif keyword={this.props.gifDescription} />
                 </div>
              </div>
     </div>
       )
   }


 }

So you create a reusable component which manages its own state ( React Paradigm/ Composition brings reusability)

As for displaying multiple widgets

class OpenWapi extends Component {
   constructor(props) {
      super(props);
      this.state = {
        requestFailed: false,
        shown: false
      }
      this.componentDidMount = this.componentDidMount.bind(this);
      this.toggle = this.toggle.bind(this);
    }

    componentDidMount() {
      fetch(urlForCity(this.props.city))
      .then(response => {
      if(!response.ok) {
         throw Error("Network request failed")
      }
      return response;
     })
     .then(data => data.json())
     .then(data => {
     this.setState({
        weatherData: data
       })
     }, () => {
       this.setState({
         requestFailed: true
     })
  })
}
render() {
    if(this.state.requestFailed) return <p>Request Failed.</p>;
    if(!this.state.weatherData) return <p>Loading...</p>;

    return(
    <div>
       <p>City: {this.state.weatherData.city.name}</p>
       <WeatherWidget 
          date={this.state.weatherData.list[0].dt}
          maxTemp={this.state.weatherData.list[0].temp.max}
          minTemp={this.state.weatherData.list[0].temp.min}
          gifDescription=
                {this.state.weatherData.list[0].weather[1].description}

       />
       <WeatherWidget 
          date={this.state.weatherData.list[1].dt}
          maxTemp={this.state.weatherData.list[1].temp.max}
          minTemp={this.state.weatherData.list[1].temp.min}
          gifDescription=
                {this.state.weatherData.list[1].weather[1].description}

       />
    </div>
    )
} 

Hopefully, this solves the use case.

发布评论

评论列表(0)

  1. 暂无评论