Search code examples
reactjsreact-bootstrap

Accordion closes when I change value of a form within it


I've got an Accordion that has within it a text form (code sandbox: https://codesandbox.io/live/1qr00im):

function MovementAccordion() {
  const [formInput, setFormInput] = useState('')

    return(
      <Accordion>
        <Card>
          <Accordion.Toggle
            as={Card.Header}
            eventKey="0">
            Accordian Header
          </Accordion.Toggle>
          <Accordion.Collapse eventKey="0">
            <Card.Body>
              Fill in the form:
              <Form.Control
                onChange={(event) => setFormInput(event.target.value)}
                type="text"
                placeholder="Enter input here" />
              <Button
                variant="primary"
                >
                  Add
              </Button>
            </Card.Body>
          </Accordion.Collapse>
        </Card>
      </Accordion>
    )
  }

I've added an onChange() function to the Form so that any time the form is updated it is pushed to state. However, any time the Form is updated the Accordion closes.

If I remove the onChange() function altogether, the form works fine and I can update it at will. The issue comes whenever the onChange() is included.

After spending more time than I'd like to admit trying to find a fix this, I'm stuck.


Solution

  • According to your sandbox:-

    • you should not call your function like you call a normal JSX functional component. Instead you should've just called it like how normal function should be called - {PrimaryMovementAccordion()}

    • if you want to called like the current state you're now. Then that would only be achievable if the SecondaryMovementAccordion is indeed a JSX functional component. So you need to create another React Functional Component of SecondaryMovementAccordion, like so, in order to make it work:-

    • in App.js:-

    import React, { useState } from "react";
    import "bootstrap/dist/css/bootstrap.min.css";
    import "./styles.css";
    
    import Accordion from "react-bootstrap/Accordion";
    import Card from "react-bootstrap/Card";
    import Form from "react-bootstrap/Form";
    import Button from "react-bootstrap/Button";
    
    import SecondaryMovementAccordion from "./SecondaryMovementAccordion";
    
    export default function App() {
      const [newPrimary, setNewPrimary] = useState("");
      const [newSecondary, setNewSecondary] = useState("");
    
      return (
        <>
          {/* Incorrect way of calling normal function */}
          {/* <PrimaryMovementAccordion /> */}
          {/* correct way of calling a normal function */}
          {PrimaryMovementAccordion()}
          {/* correct way of calling a JSX Functional Component */}
          <SecondaryMovementAccordion
            newSecondary={newSecondary}
            setNewSecondary={setNewSecondary}
          />
          <p>Primary: {newPrimary}</p>
          <p>Secondary: {newSecondary}</p>
        </>
      );
    
      // Primary Accordian
      function PrimaryMovementAccordion() {
        return (
          <Accordion defaultActiveKey="0">
            <Card>
              <Accordion.Toggle as={Card.Header} eventKey="0">
                Form accordion
              </Accordion.Toggle>
              <Accordion.Collapse eventKey="0">
                <Card.Body>
                  Fill in the form:
                  <Form.Control
                    type="text"
                    placeholder="Enter secondary movement"
                    value={newPrimary}
                    onChange={(event) => setNewPrimary(event.target.value)}
                  />
                  <p>{newPrimary}</p>
                </Card.Body>
              </Accordion.Collapse>
            </Card>
          </Accordion>
        );
      }
    }
    
    • SecondaryMovementAccordion.js:-
    import React from "react";
    import "bootstrap/dist/css/bootstrap.min.css";
    import "./styles.css";
    
    import Accordion from "react-bootstrap/Accordion";
    import Card from "react-bootstrap/Card";
    import Form from "react-bootstrap/Form";
    import Button from "react-bootstrap/Button";
    
    const SecondaryMovementAccordion = ({ newSecondary, setNewSecondary }) => {
      return (
        <Accordion defaultActiveKey="0">
          <Card>
            <Accordion.Toggle as={Card.Header} eventKey="0">
              Form accordion
            </Accordion.Toggle>
            <Accordion.Collapse eventKey="0">
              <Card.Body>
                Fill in the form:
                <Form.Control
                  type="text"
                  placeholder="Enter secondary movement"
                  value={newSecondary}
                  onChange={(event) => setNewSecondary(event.target.value)}
                />
                <p>{newSecondary}</p>
              </Card.Body>
            </Accordion.Collapse>
          </Card>
        </Accordion>
      );
    };
    
    export default SecondaryMovementAccordion;
    

    This is the working sandbox shown above.