1

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);
Mohamed Ibrahim Elsayed
  • 2,734
  • 3
  • 23
  • 43
Millos
  • 43
  • 1
  • 2
  • 5

2 Answers2

1

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?

0xc14m1z
  • 3,675
  • 1
  • 14
  • 23
0

A component's state is enclosed within the component that defines it. In your case you are trying to update the state of the Admin through Hit component. From React.js State and Life Documentation

State is similar to props, but it is private and fully controlled by the component.

But the reason this error is thrown is not because Hit can't update the state of Admin, it doesn't event try to. What happens when you click the Modify Item button is Hit is trying to update its own state, but this won't work for two reasons:

  1. First and foremost from the same documentation:

We mentioned before that components defined as classes have some additional features. Local state is exactly that: a feature available only to classes.

  1. Even if you transform the component to a class component, the state should be defined either in the constructor or as class field. But this won't help you anyway, because although you will be able to update the state, it would be Hit's one.

So, if I were you, you would go for one of the following solutions (depending on the case).

  1. Inline Content, Hits and Hit within Admin's render method or extract them as renderContent and renderHits Admin's class field methods, this way you will be able to update the state within this methods methods.

  2. Probably the one you should consider more: Pass openModel class field function as onModifyItem prop down to Hits component, and use it as onClick prop for Modify Item button.

Here is an example:

const Hit = ({onModifyItem, 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={onModifyItem}>Modify Item</button>
    </div>


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

<Content onModifyItem={this.openDialog}/>
NValchev
  • 2,855
  • 2
  • 15
  • 17