0

I'm fairly new to React and I'm trying to add new components to already exsisting, but I am not sure how to do that.

So here I have the list of my Seances and the button to add more:
SeanceManager.js

return (
  <MDBRow>
    <MDBCol md="6">
      <MDBCard>
        <MDBCardBody>
          <form>
            <p className="h4 text-center py-4">Sign up</p>
            <div className="grey-text">
              <MDBInput
                label="Type your email"
                icon="envelope"
                group
                type="email"
                validate
                error="wrong"
                success="right"
              />
              <MDBInput
                label="Type your password"
                icon="lock"
                group
                type="password"
                validate
              />
            </div>
            <div className="text-center py-4 mt-3">
              <MDBBtn color="cyan" type="submit" onClick={props.submitLogin}>
                Log in
              </MDBBtn>
            </div>
          </form>
        </MDBCardBody>
      </MDBCard>
    </MDBCol>
  </MDBRow>
);

By pressing the button to add more, a modal view pops up and at the end it has a submit button that should add the Seance.
AddSeanceModal.js

return (
  <Modal
    {...this.props}
    size="lg"
    aria-labelledby="contained-modal-title-vcenter"
    centered
  >
    <Modal.Header closeButton>
      <Modal.Title id="contained-modal-title-vcenter">Add Seance</Modal.Title>
    </Modal.Header>
    <Modal.Body>
      <div>
        <form>
          {/*First row*/}
          <MDBRow>
            <MDBCol md="4">
              <div className="custom-file">
                <input
                  type="file"
                  className="custom-file-input"
                  id="inputGroupFile01"
                  aria-describedby="inputGroupFileAddon01"
                />
                <label className="custom-file-label" htmlFor="inputGroupFile01">
                  Choose file
                </label>
              </div>
            </MDBCol>
          </MDBRow>

          {/*Second row*/}
          <MDBRow>
            <MDBCol md="4">
              <MDBInput
                onChange={this.changeHandler}
                type="text"
                id="materialFormRegisterPasswordEx4"
                name="algus_aeg"
                label="Algus Aeg"
                required
              />
            </MDBCol>
            <MDBCol md="4">
              <MDBInput
                onChange={this.changeHandler}
                type="text"
                id="materialFormRegisterPasswordEx4"
                name="lopp_aeg"
                label="Lõpp Aeg"
                required
              />
            </MDBCol>
          </MDBRow>

          {/*Third row*/}
          <MDBRow>
            <MDBCol md="4">
              <MDBInput
                onChange={this.changeHandler}
                type="text"
                id="materialFormRegisterPasswordEx4"
                name="aja_samm"
                label="Aja Samm"
                required
              />
            </MDBCol>
          </MDBRow>

          <Button variant="secondary" onClick={this.props.onHide}>
            Close
          </Button>
          <MDBBtn color="success" type="submit" className="float-right">
            Add Seance
          </MDBBtn>
        </form>
      </div>
    </Modal.Body>
  </Modal>
);

And finally the Seance itself:
Seance.js

return (
  <div
    className="card"
    style={{ marginBottom: "7px" }}
    onClick={() => this.setState({ modalShow: true })}
  >
    <div className="card-body">
      <h5 className="card-title">Seance nr: {this.props.id}</h5>
      <p className="card-text">Start aeg: {this.props.startDate}</p>
      <p className="card-text">End aeg: {this.props.endDate}</p>
      <button type="button" className="close float-right" aria-label="Close">
        <span aria-hidden="true">×</span>
      </button>
      <ResultModal id={1} show={this.state.modalShow} onHide={modalClose} />
    </div>
  </div>
);

I also made a fiddle on sandbox: https://codesandbox.io/s/qloo1vqr7j?fontsize=14

At the moment I have 4 static Seances, but it should start with 0 and add more once you add them.
Also the Xon the Seance should remove it.

I have tried creating a list on state in SeanceManager.js, but I have not understoond how to add components to the list from another component AddSeanceModal.

Richard
  • 1,087
  • 18
  • 52
  • You can use a `for` loop to programmatically add components, see this answer: https://stackoverflow.com/questions/22876978/loop-inside-react-jsx – Kokodoko Apr 26 '19 at 09:58
  • Where dou you save the information stored in your `Seance`s? Where is the code that `renders` each `Seance`? – four-eyes Apr 26 '19 at 11:13
  • Well they should be stored locally, but at the moment they aren't stored anywhere. – Richard Apr 26 '19 at 11:16

1 Answers1

1

There are quite a few code choices that are holding your application back from being dynamic.

First, you'll need to utilize a container component that handles all things related to Seance state. This includes, adding, removing and viewing. All of this needs to be handled by a parent that updates its children according to what current in its state. Please follow this guide to understand containers vs components.

Second, you need to refactor how you're utilizing Seances. They need to be stored as an array within the parent's state. As you have it now, it's hard-coded with 4 <Seances /> that can't be removed/updated.

Instead, you should create an array of objects like so within state:

seances: [
  { id: "1", startDate: "2019-04-10 10:28:05.926-07", endDate: "2019-05-10 10:28:05.924-07" },
  { id: "2", startDate: "2019-04-11 11:28:05.926-07", endDate: "2019-05-11 11:28:05.924-07" },
  { id: "3", startDate: "2019-04-12 12:28:05.926-07", endDate: "2019-05-12 12:28:05.924-07" },
  ...etc
];

You'll utilize Array.map to map over this seances array and implicitly return these seances dynamically (to make easier to read, I'm writing your <Seance id={id} /> component inline):

<div>
  {seances.map(({ id, startDate, endDate }) => (
    <div key={id}>
     <h1>Seance nr: {id} </h1>
     <p>Start date: {startDate}</p>
     <p>End date: {endDate}</p>
     <button type="button" onClick={() => handleDeleteSeance(id)}>X</button>
    </div>
  ))}
</div>

Now you'll remove these items from the seances array by utilizing Array.filter by a seance's id property. When a user clicks the "X" button it calls this.handleDeleteSeance with the specific seance id:

handleDeleteSeance = id => {
  this.setState(prevState => ({
    ...prevState, // spread out any previous state not related to "seances"
    seances: prevState.seances.filter(seance => seance.id !== id) // this translates to: compare each "seance" within "seances" and implicitly return any that do NOT match the "id" that was clicked
  })
};

To add an item to the seances array, you'll utilize the spread operator, and add an object with properties that were collected from a form:

handleSubmit = e => {
  e.preventDefault();
  const { id, startDate, endDate } = this.state;

  if (!id || !startDate || !endDate } return null; // if any properties are missing, don't add an item yet

  this.setState(prevState => ({
    ...prevState, // spread out any previous state not related to "seances"
    seances: [...prevState.seances, { id, startDate, endDate } ] // this translates to: spread out any previous seances and add a new object with "id", "startDate" and "endDate"
  });  
} 

Working with arrays example (since this a client-only implementation, I decided to filter by array position so I don't have to have to deal with generating unique ids; however, you can utilize uuid to generate these dynamic ids if you wish):

Edit Class Component - Filter Data by Array Index


Other notes:

  • One of your components contains a let modalClose = () => this.setState({ modalShow: false }); within the render method. This is invalid and should be defined as a class method.
  • Structure your components to be reusable. You have some components that are, but then you have some that reside within another component (like FileInput). As your application grows, you may need to use FileInput elsewhere for another form.
  • Utilize ES6 syntax throughout your project to simplify your code. For example you can deconstruct props for your Login component like so: const Login = ({ submitLogin }) => ( <MDBRow>...etc</MDBRow>);.
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • Thank you for this answer, there is a lot to look into. I am pretty sure this is what I am looking for and will help me. I will accept this answer as soon as I get it working. Thank you a lot for your answer. – Richard May 05 '19 at 19:58
  • Can you answer this one question please. In my example I have `AddSeanceModal.js` and it was a component and I tried to switch it to a container as per your example. But the `` in that class requires `{...this.props}` and so does the ` – Richard May 05 '19 at 20:41
  • In that case, you wouldn't deconstruct `props` if both `Modal` and `Button` need the `onHide` function. It'd be: `const AddSeanceModal = props => ( ......`. HOWEVER, if the `Modal` doesn't need `onHide`, then you can do something like `const AddSeanceModal = ({onHide, ...rest}) => ( ... .. )`. You partially deconstruct `props` via `...rest`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Rest_syntax_(parameters) – Matt Carlotta May 05 '19 at 20:57
  • Ideally, you'd only have one container that updates all its children (not multiple). However, you can have a stateful `form` component that, once submitted, passes back its `state` to the `parent` it resides in. For example: `` resides in `App`. Now `AddSeance` has a `handleSubmit` prop via `this.props.handleSubmit`. This is especially useful if you want to make this `AddSeance` component self-contained (it handles its own `state` separately from `App`), but it still utilizes a class method from `App`. – Matt Carlotta May 05 '19 at 21:02
  • Alright, I got it working. There are still some errors but I'm pretty sure I'll be able to figure them out. Thank you a lot, I will give you the bounty as soon as it lets me. – Richard May 05 '19 at 21:10
  • 1
    You bet. If you find that you're passing down props too many times and/or have way too many mounted children that are mounted but invisible, then you should look into redux: https://redux.js.org/introduction/getting-started. It acts like a container for your entire application, but it doesn't need to traverse the children to reach a component that requires some `state`. Instead, you just directly `connect` to the child that needs the `state`. However, your project, as of now, doesn't need this yet. – Matt Carlotta May 05 '19 at 21:19
  • Did most of it from your example and finally got it all working. Thank you a lot. – Richard May 09 '19 at 20:00
  • 1
    Awesome. That was my intention. I didn't want to fix your code, otherwise, if I did then you wouldn't learn nearly as much by doing it on your own. – Matt Carlotta May 09 '19 at 20:49