1
\$\begingroup\$

I've been designing a React component to select countries that a user has visited, grouped by continent. This component is a part of larger project but is relatively self contained. This is my first time using React (and JS really!) and I feel like passing props from the top level component down through other components is 'wrong'. Is this the correct idea in React?

Component #1: I want to render the menu on to a canvas.

const SelectCanvas = (props) => {
    return (
      <>
        <Offcanvas show={props.show} onHide={props.handleClose} name="Test">
          <Offcanvas.Header closeButton>
            <Offcanvas.Title>Countries Visited</Offcanvas.Title>
          </Offcanvas.Header>
          <Offcanvas.Body>
            <CountrySelector addCountry={props.addCountry} selectedCountries={props.selectedCountries}/>
          </Offcanvas.Body>
        </Offcanvas>
      </>
    );
  }

Component #2: Read in my countries data and give the user an interface to filter the countries with a search box. Creates an expander for each continent.

const CountrySelector = (props) => {
    const [data, setData] = useState([]);
    const [filter, setFilter] = useState('');
    const [groupedCountries, setGroupedCountries] = useState({});
  

  useEffect(() => {
    csv(`/countries.csv`).then((data) => {
      setData(data);
    });
  }, []);

  useEffect(() => {
    let filteredCountries = data.filter(country => String(country.NAME).toLowerCase().startsWith(filter.toLowerCase()));
    setGroupedCountries(groupBy(filteredCountries, "CONTINENT"))
  }, [data, filter])

  function groupBy(arr, property) {
    return arr.reduce(function(memo, x) {
      if (!memo[x[property]]) { memo[x[property]] = []; }
      memo[x[property]].push(x);
      return memo;
    }, {});
  }  

  return (
      <Form>
        <Form.Group className="mb-3">
          <Form.Control placeholder="Search country" onChange={(e) => setFilter(e.target.value)}/>
        </Form.Group>
        <Accordion defaultActiveKey={0}> 
          {Object.keys(groupedCountries).map((continent, i) => (
            <Accordion.Item eventKey={i}>
              <Accordion.Header>{continent}</Accordion.Header>
              <Accordion.Body>
                <CountryList 
                  filteredCountries={groupedCountries[continent]} 
                  selectedCountries={props.selectedCountries} 
                  addCountry={props.addCountry}/>
              </Accordion.Body>
            </Accordion.Item>
          ))
          }
        </Accordion>
      </Form>
  );
}

Component #3: For the array of country objects, render them as a list of checkboxes.

const CountryList = (props) => {
  return (
    props.filteredCountries.map((country) => (
      <Form.Check 
          key={country.ISO3}
          type='checkbox'
          id={country.ISO3}
          label={country.NAME}
          onClick={props.addCountry}
          defaultChecked={props.selectedCountries.includes(country.ISO3) ? true : false}
      />
  ))
  )
}

A little more info:

  • The addCountry method thats passed in will add a country to the selectedCountries array. If the country is already in the array, it will remove it.
  • The 'countries.csv' file contains names, ISO3 codes, and various other bits of information about countries.
  • These are the imports I'm using:
import React, {useState, useEffect} from "react";
import Offcanvas from 'react-bootstrap/Offcanvas';
import Form from 'react-bootstrap/Form';
import Accordion from 'react-bootstrap/Accordion'
import {csv} from 'd3-fetch';

Thank you, and please let me know if I can elaborate on anything.

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

Prop drilling is not necessarily always wrong, but often discouraged, so I'd say your instinct is correct here.

Here's an article on how to Avoid Prop Drilling with React Context making use of React's Context API, which should be exactly what you're looking for here. If you prefer video over text, this video tutorial by WebDev Simplified (13 minutes) should be a good starting point.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.