简体   繁体   中英

React props/state not passing to modal window

my first ever StackOverflow question, having been banging my head for about 4 hours!

I am building a react page that has an image gallery section. On the desktop site, when the user clicks on an image, it launches a Bootstrap modal pop up that should show the details of the clicked painting (title, size, type and media) and a larger image.

I have got the modal working (without needing any special library like react-bootstrap) but whichever image is clicked, the modal only shows the image and details of the first image in the set.

I have tried all sorts of permutations of props and state, trying functional and class components, and nothing seems to work.

Weirdly, if I look at each component in the chrome dev tools react DOM, the props for each image are set correctly, even in the class that renders the modal...but in the actual rendered page, it does not work.

In my code, we have a Gallery component, containing many Holder components, each Holder contains a PortfolioItem, and the PortfolioItem contains a component called More that deals with the modal. As each Holder component defines props, these are passed down to each PortfolioItem (which works as I am getting the correct images and details in the gallery) and I just want those same props to show up in the modal.

import React from 'react';
import ReactDOM from 'react-dom';


class PortfolioItem extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        url: props.url,
        title: props.title,
        type: props.type,
        size: props.size,
        media: props.media
    };
}

render() {
    return (
        <div className="ot-portfolio-item">
            <More url={this.state.url} title={this.state.title} type={this.state.type} size={this.state.size}
                  media={this.state.media}/>
            <figure className="effect-border">
                <img src={this.state.url} className="img-responsive portfolio-img" alt={this.state.title}/>
                <figcaption>
                    <h2 className="paintingTitle">{this.state.title}</h2>
                    <p className="paintingDetail">{this.state.type}, {this.state.size}, {this.state.media}</p>
                    <a data-toggle="modal" data-target="#Modal"> </a>
                </figcaption>
            </figure>
        </div>
    );
}
}

class More extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        url: props.url,
        title: props.title,
        type: props.type,
        size: props.size,
        media: props.media
    };
}

render() {
    return (
        <div className="modal fade" id="Modal" aria-labelledby="Modal-label">
            <div className="modal-dialog">
                <div className="modal-content">
                    <div className="modal-header">
                        <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">&times;</span></button>
                        <h4 className="modal-title">{this.state.title}</h4>
                    </div>
                    <div className="modal-body">
                        <img src={this.state.url} alt={this.state.title} className="img-responsive"/>
                        <div className="modal-works">
                            <span>{this.state.type}</span>
                            <span>{this.state.size}</span>
                            <span>{this.state.media}</span>
                        </div>
                    </div>
                    <div className="modal-footer">
                        <button type="button" className="close btn btn-default" data-dismiss="modal"
                                aria-label="Close">Close
                        </button>
                    </div>
                </div>
            </div>
        </div>
    );
}
}


const Holder = (props) => {
return (
    <div className="col-sm-6 col-md-4 col-0-gutter holder">
        <PortfolioItem url={props.url} title={props.title} type={props.type} size={props.size} media={props.media}/>
    </div>
);
};

const Gallery = () => {
return (
    // When adding a new painting, just copy a <Holder/> in correct orientation section, and change the props!
    <div>
        {/* Landscape */}
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/girl.jpg" title="#Disengaged" type="Acrylic"
                size="24 by 16 inches" media="Board"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/swimming.jpg" title="Submerge" type="Acrylic"
                size="24 by 16 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/staithes.jpg" title="Staithes 2015" type="Acrylic"
                size="24 by 16 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/ladies.jpg" title="Nederlandes Meisjes" type="Acrylic"
                size="24 by 16 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/park.jpg" title="Family Stroll" type="Acrylic"
                size="24 by 16 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/mexican.jpg" title="Tree Of Life" type="Acrylic"
                size="12 by 10 inches" media="Canvas"/>

        {/* Square */}
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/face.jpg" title="Attitudes Of Colour" type="Acrylic"
                size="19.5 by 19.5 inches" media="Board"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/lee.jpg" title="Lee" type="Acrylic"
                size="19.5 by 19.5 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/boat.jpg" title="Drift Away" type="Acrylic"
                size="12 by 12 inches" media="Board"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/cindyself.jpg" title="Prizeworthy" type="Acrylic"
                size="19.5 by 19.5 inches" media="Canvas"/>

        {/* Portrait */}
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/diving.jpg" title="Ascent" type="Acrylic"
                size="32 by 40 inches" media="Ply"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/bluehair.jpg" title="Untitled" type="Acrylic"
                size="10 by 12 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/beach.jpg" title="Namaste" type="Acrylic"
                size="19.5 by 23.5 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/danielle.jpg" title="Concrete Jungle" type="Acrylic"
                size="19.5 by 23.5 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/africa.jpg" title="Untitled" type="Acrylic"
                size="19.5 by 23.5 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/sealady.jpg" title="Aqua Blue" type="Acrylic"
                size="32 by 40 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/violinman.jpg" title="Nightfall" type="Acrylic"
                size="11 by 14 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/indiangirl.jpg" title="Transition" type="Acrylic"
                size="19.5 by 23 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/indianboys.jpg" title="Backpacker's Tales" type="Acrylic"
                size="32 by 40 inches" media="Canvas"/>
        <Holder url="https://dgtqqzvj8521c.cloudfront.net/indianwoman.jpg" title="Journey" type="Acrylic"
                size="19.5 by 23.5 inches" media="Board"/>
    </div>
);
};

const App = () => {
return (
    <Gallery/>
);
};

ReactDOM.render(<App/>, document.getElementById('root'));

Any pointers would be greatly appreciated!

Your issue is probably down to the fact that you are rendering the <More /> component and within the id of the modal is always the same.

So your line <a data-toggle="modal" data-target="#Modal"> </a> is going to always target id="Modal" . I suspect that your rendered html is actually invalid because your id's must be unique on a page.

If you change the ID of each modal rendered, then make sure the data-target links to the correct one, then it should work.

Try to remove this kind of block code from each component, as you don't need :

```js

constructor(props) {
    super(props);
    this.state = {
        url: props.url,
        title: props.title,
        type: props.type,
        size: props.size,
        media: props.media
    };
}

```

By example :

render() {
let {url, title, size, type, media} = this.props;
    return (
        <div className="ot-portfolio-item">
            <More url={url} title={title} type={type} size={size}
                  media={media}/>
            <figure className="effect-border">
                <img src={url} className="img-responsive portfolio-img" alt={title}/>
                <figcaption>
                    <h2 className="paintingTitle">{title}</h2>
                    <p className="paintingDetail">{type}, {size}, {media}</p>
                    <a data-toggle="modal" data-target="#Modal"> </a>
                </figcaption>
            </figure>
        </div>
    );
}

use directly prop.url in your components. And let's see.

Cheers !

Modal must be rendred Just one Time ! and you pass data to that Modal onClick

class App extends React.Component {
 state = {
  data: {},
 }
sendData = (data) => {
 this.setState({ data });
}

render() {
 return (
  <div>
    <More data={this.state.data} />
    <Gallery sendData={this.sendData} />
  </div>
 );
 }
}

You are saving the component's props into its state - this is against a philosophy of React (maintaining a single source of truth) and will frequently cause bugs.

A React component will re-render when either its props or its state changes, but the constructor will not be rerun. Take this simplified component:

class Simple extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: props.name
    }
  }

  render() {
     return (
       <div>{this.state.name}</div>
     );
  }
}

When we initially render <Simple name="Alex" /> then "Alex" is set to the state, the component renders and everything seems fine. When this changes to <Simple name="Steve" /> then what happens? The props update, so the component re-renders. Unfortunately, the component state has not changed, so the rendered output is the same.

How do we fix this? We maintain one source of truth. This means that any piece of information is stored in one, and only one, place in the application. In the case of the Simple component, this place is in the props. We should refactor it to use the props in the render method:

class Simple extends React.Component {   
  render() {
     return (
       <div>{this.prop.name}</div>
     );
  }
}

Now that we've done that it doesn't even need to be a class any more:

const Simple = ({ name }) => (
  <div>{name}</div>
);

You should do this refactoring now, because it will break your app either now or in the future.

Looking at your specific problem, the issue is most likely that you end up with multiple divs with the id "#Modal". Every More element you create creates a div with that id. To distil your code down to a simpler form, let's use:

const PortfolioItem = ({ name, url }) => (
  <div>
    <a data-toggle="modal" data-target="#Modal"> </a>
    <More name={name} url={url}
  </div>
);

const More = ({ name, url }) => (
  <div id="Modal">
    <a href={url}>{name}</a>
  </div>
);

What we really need is to have a unique id for each modal, so there is no confusion as to which one gets triggered. We can do this simply by using the name prop as an id:

const PortfolioItem = ({ name, url }) => (
  <div>
    <a data-toggle="modal" data-target={name}> </a>
    <More name={name} url={url}
  </div>
);

const More = ({ name, url }) => (
  <div id={name}>
    <a href={url}>{name}</a>
  </div>
);

You may be able to find a more appropriate prop to use as the ID, or you could use a solution that uses array indices, or random characters. The exact ids don't matter - they just need to be unique for each pair of PortfolioItem and More.

It is also worth noting that this is not an idiomatic way of handling modals in React. Usually the open/closed state of the modal is stored within React itself, and handled using click handlers. If you want to go more down this path, then the react-modal library is an excellent path down which to go.

when you use a tags,you dont have to use datta-toggle, use href instead (got it from bootstrap documentation)

Note: For elements, omit data-target, and use href="#modalID" instead

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM