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
addCountrymethod thats passed in will add a country to theselectedCountriesarray. 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';
- Full repo is here.
Thank you, and please let me know if I can elaborate on anything.