Search code examples
javascriptreactjsreact-modal

Connect function outside the state in React


I have an application with many items collection, and in admin area, the admin user can delete or modify items (like a traditional e-commerce website back office). In logic, for modify an item, user click on "modify item" button, and a modal open.

My problem is i can't open modal on click button, because my browser show me an error message when I click on "modify item" button : "TypeError: Cannot read property 'setState' of undefined"

I use react-modal-responsive for this task.

I have been stuck for several days, I would be super happy if you could help me. Thank in advance.

This is my code:

import React, { Component } from 'react';
import { database } from '../firebase/firebase';
import * as firebase from 'firebase';
import withAuthorization from './withAuthorization';
import _ from 'lodash';
import AuthUserContext from './AuthUserContext';
import FileUploader from "react-firebase-file-uploader";
import Image from 'react-image-resizer';
import{InstantSearch, SearchBox, Hits, Highlight, RefinementList} from "react-instantsearch/dom";
import Modal from 'react-responsive-modal';



function removeToCatalogue(hit) { 
  const item = hit.objectID;
    firebase.database().ref(`catalogue/${item}`).remove();
    console.log("Capsule correctement supprimée du catalogue")
}


const Hit = ({hit}) =>
    <div className="item">
       <img src={hit.avatarURL} width={150} height={150}></img>
        <h1 className="marque">{hit.marque}</h1>
        <h3 className="numero">{hit.numero}</h3>
        <h4 className="reference">{hit.reference}</h4>
        <h4 className="marquesuite">{hit.marquesuite}</h4>
        <p className="cote">{hit.cote}</p>

        <button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button> 
        <button onClick={() => this.setState({open: true})}>Modify Item</button>
    </div>



const Content = () =>
  <div className="text-center">

    <Hits hitComponent={Hit}/>

  </div>


class Admin extends React.Component {
  constructor (props) {
    super (props);

    this.state = {
      marque: '',
      marquesuite: '',
      numero: '',
      reference: '',
      cote: '',
      avatar: "",
      isUploading: false,
      progress: 0,
      avatarURL: "",
      catalogue: {},
      open: false,

    };
    this.onInputChange = this.onInputChange.bind(this);
    this.onHandleSubmit = this.onHandleSubmit.bind(this);

  }


  onOpenModal = () => {
    this.setState({ open: true });
  };

  onCloseModal = () => {
    this.setState({ open: false });
  };


  onInputChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    });
  }

  onHandleSubmit(e){
    e.preventDefault();
    const catalogue = {
      marque: this.state.marque,
      marquesuite: this.state.marquesuite,
      numero: this.state.numero,
      reference: this.state.reference,
      cote: this.state.cote,
      avatar: this.state.avatar,
      avatarURL: this.state.avatarURL,

    };
    database.push(catalogue);
    this.setState({
      marque: '',
      marquesuite: '',
      numero: '',
      reference: '',
      cote: '',
      avatar: "",
      isUploading: false,
      progress: 0,
      avatarURL: "",
    });
  }

  handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
  handleProgress = progress => this.setState({ progress });
  handleUploadError = error => {
    this.setState({ isUploading: false });
    console.error(error);
  };

  handleUploadSuccess = filename => {
    this.setState({ avatar: filename, progress: 100, isUploading: false });
    firebase
      .storage()
      .ref("images")
      .child(filename)
      .getDownloadURL()
      .then(url => this.setState({ avatarURL: url }));
  };


  render (){
    const { open } = this.state;
    return (
      <div className="container-fluid">
        <div className="container">
        <h1 class="text-center">Espace d'Administration</h1>
        <a href="https://super-capsule.000webhostapp.com/signaler-modification" class="btn btn-primary btn-lg active" role="button" aria-disabled="true">Signaler une modification</a>
        <form onSubmit={this.onHandleSubmit}>
          <div className="form-group">
          <label>Marque de la capsule:</label>
          <input
            value={this.state.marque}
            type="text"
            name='marque'
            placeholder="Marque"
            onChange={this.onInputChange}
            ref="marque"
            className="form-control" />
          </div>
          <div>
          <label>Numéro de la capsule:</label>
          <input
            value={this.state.numero}
            type="text"
            name='numero'
            placeholder="Numéro de la capsule"
            onChange={this.onInputChange}
            ref="numero"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Référence de la capsule:</label>
          <input
            value={this.state.marquesuite}
            type="text"
            name='marquesuite'
            placeholder="Référence de la capsule"
            onChange={this.onInputChange}
            ref="marquesuite"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Référence de la capsule (suite):</label>
          <input
            value={this.state.reference}
            type="text"
            name='reference'
            placeholder="Référence de la capsule (suite)"
            onChange={this.onInputChange}
            ref="reference"
            className="form-control"/>
          </div>
          <div className="form-group">
          <label>Cote de la capsule:</label>
          <input
            value={this.state.cote}
            type="text"
            name='cote'
            placeholder="Cote de la capsule"
            onChange={this.onInputChange}
            ref="cote"
            className="form-control"/>
          </div>

          <label>Visuel de la capsule:</label>
          {this.state.isUploading && <p>Progress: {this.state.progress}</p>}
          {this.state.avatarURL && <img src={this.state.avatarURL} />}
          <FileUploader
            accept="image/*"
            name="avatar"
            randomizeFilename
            storageRef={firebase.storage().ref("images")}
            onUploadStart={this.handleUploadStart}
            onUploadError={this.handleUploadError}
            onUploadSuccess={this.handleUploadSuccess}
            onProgress={this.handleProgress}
          />
          <button className="btn btn-info">Ajouter une capsule</button>
        </form>
      </div>

        <h1 className="text-center">Catalogue de capsule</h1>



        <InstantSearch
            apiKey="xxx"
            appId="xxx"
            indexName="xxx">


            <SearchBox translations={{placeholder:'Rechercher une capsule'}} width="500 px"/>

            <div>

                <Modal open={this.state.showModal} open={open} onClose={this.onCloseModal} center>
                  <h2>Simple centered modal</h2>
                </Modal>
            </div>

            <Content />    


          </InstantSearch>



      </div>
    )
  }
}



const authCondition = (authUser) => !!authUser;

export default withAuthorization(authCondition)(Admin);

Solution

  • I don't know if this answer might entirely solve your problem, but it'll at least let you open the modal.

    First, you need to pass an handler when rendering the Content component, something like:

    <Content onEdit={ this.onOpenModal } />
    

    Then, we should have a similar handler on the Hit component, attaching it to the button click event:

    const Hit = ({ hit, onEdit }) =>
      <div className="item">
        <img src={hit.avatarURL} width={150} height={150}></img>
        <h1 className="marque">{hit.marque}</h1>
        <h3 className="numero">{hit.numero}</h3>
        <h4 className="reference">{hit.reference}</h4>
        <h4 className="marquesuite">{hit.marquesuite}</h4>
        <p className="cote">{hit.cote}</p>
    
        <button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button> 
        <button onClick={ onEdit }>Modify Item</button>
      </div>
    

    Now we have to pass down this handler from the Content component to the Hit one.

    The problem I see is that the Hits component take a hitComponent prop which has to be a component to be rendered. This means that, to pass the onEdit handler, we have to enhance the Hit component with a Javascript clojure before passing it down:

    const Content = ({ onEdit }) => {
    
      const EnhancedHit = props =>
         <Hit onEdit={ onEdit } { ...props } />
    
      return (
        <div className="text-center">  
          <Hits hitComponent={ EnhancedHit } />
        </div>
      )
    }
    

    Can you try and report if it opens the modal?