0

I have a parent component with an if statement to show 2 different types of buttons.

What I do, on page load, I check if the API returns an array called lectures as empty or with any values:

lectures.length > 0 ? show button A : show button B

This is the component, called main.js, where the if statement is:

lectures.length > 0 
   ? <div onClick={() => handleCollapseClick()}>
       <SectionCollapse open={open} />                                         
     </div>
   : <LectureAdd dataSection={dataSection} />

The component LectureAdd displays a + sign, which will open a modal to create a new Lecture's title, while, SectionCollapse will show an arrow to show/hide a list of items.

The logic is simple:
1. On page load, if the lectures.lenght > 0 is false, we show the + sign to add a new lecture
OR
2. If the lectures.lenght > 0 is true, we change and show the collpase arrow.

Now, my issue happens when I add the new lecture from the child component LectureAdd.js

import React from 'react';
import { Form, Field } from 'react-final-form';

// Constants
import { URLS } from '../../../../constants';

// Helpers & Utils
import api from '../../../../helpers/API';

// Material UI Icons
import AddBoxIcon from '@material-ui/icons/AddBox';


export default ({ s }) => {

  const [open, setOpen] = React.useState(false);
  const [ lucturesData, setLecturesData ] = React.useState(0);
  const { t } = useTranslation();



  const handleAddLecture = ({ lecture_title }) => {
    const data = {
      "lecture": {
        "title": lecture_title
      }
    }

    return api
      .post(URLS.NEW_COURSE_LECTURE(s.id), data)
      .then(data => {

        if(data.status === 201) {
          setLecturesData(lucturesData + 1) <=== this doesn't trigger the parent and the button remains a `+` symbol, instead of changing because now `lectures.length` is 1 
        }

      })
      .catch(response => {
        console.log(response)
      });
    }


  return (
    <>
      <Button variant="outlined" color="primary" onClick={handleClickOpen}>
        <AddBoxIcon />
      </Button>

          <Form 
            onSubmit={event => handleAddLecture(event)}
          >
            {
              ({ 
                handleSubmit
              }) => (
              <form onSubmit={handleSubmit}>
                <Field 
                  name='lecture_title'
                >
                  {({ input, meta }) => (
                    <div className={meta.active ? 'active' : ''}>
                      <input {...input} 
                          type='text'
                          className="signup-field-input"
                        />
                    </div>
                  )}
                </Field>

                <Button 
                  variant="contained" 
                  color="primary"
                  type="submit"
                >
                  ADD LECTURE
                </Button>
              </form>
            )}
          </Form>
    </>
  )
}

I've been trying to use UseEffect to trigger a re-render on the update of the variable called lucturesData, but it doesn't re-render the parent component.
Any idea? Thanks Joe

1
  • 1
    react docs: "lifting state up" Commented Mar 13, 2020 at 18:28

1 Answer 1

1

Common problem in React. Sending data top-down is easy, we just pass props. Passing information back up from children components, not as easy. Couple of solutions.

  1. Use a callback (Observer pattern) Parent passes a prop to the child that is a function. Child invokes the function when something meaningful happens. Parent can then do something when the function gets called like force a re-render.
function Parent(props) {
    const [lectures, setLectures] = useState([]);

    const handleLectureCreated = useCallback((lecture) => {
        // Force a re-render by calling setState
        setLectures([...lectures, lecture]);
    }, []);

    return (
        <Child onLectureCreated={handleLectureCreated} />
    )
}

function Child({ onLectureCreated }) {
    const handleClick = useCallback(() => {
        // Call API
        let lecture = callApi();

        // Notify parent of event
        onLectureCreated(lecture);
    }, [onLectureCreated]);

    return (
        <button onClick={handleClick}>Create Lecture</button>
    )
}
  1. Similar to solution #1, except for Parent handles API call. The benefit of this, is the Child component becomes more reusable since its "dumbed down".
function Parent(props) {
    const [lectures, setLectures] = useState([]);

    const handleLectureCreated = useCallback((data) => {
        // Call API
        let lecture = callApi(data);

        // Force a re-render by calling setState
        setLectures([...lectures, lecture]);
    }, []);

    return (
        <Child onLectureCreated={handleLectureCreated} />
    )
}

function Child({ onLectureCreated }) {
    const handleClick = useCallback(() => {
        // Create lecture data to send to callback
        let lecture = {
            formData1: '',
            formData2: ''
        }

        // Notify parent of event
        onCreateLecture(lecture);
    }, [onCreateLecture]);

    return (
        <button onClick={handleClick}>Create Lecture</button>
    )
}
  1. Use a central state management tool like Redux. This solution allows any component to "listen in" on changes to data, like new Lectures. I won't provide an example here because it's quite in depth.

Essentially all of these solutions involve the same solution executed slightly differently. The first, uses a smart child that notifies its parent of events once their complete. The second, uses dumb children to gather data and notify the parent to take action on said data. The third, uses a centralized state management system.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Spidy. The idea is to use Redux. Ik having a bit of a problem to use Redux with hooks. But I will. figure it out

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.