## Building an Interface 🔹 *parent* [[✧ Explore React JS Development|Explore React JS Development]] ▫️ *next* [[✦ Creating and Hosting a Full-Stack Site|Creating and Hosting a Full-Stack Site]], [[React Hooks]] ### Overview LinkedIn Learning Course[^1] about building an interface with [[⧋ React|React]] and [[Tailwind|Tailwind]]. ### Setup Adding Icons - [React Icons](https://react-icons.github.io/react-icons)[^2] ```js import { ICONNAME } from "react-icons/LIBNAME"; ... <ICONNAME /> ``` Copy icon and extension - add each library ```js import { BiArchive } from "react-icons/bi" ... <BiArchive /> ``` Install Tailwind.[^3] - May require extra packages for PostCSS and Tailwind Forms - He also added AutoPrefixer - He added Craco in order to overide settings to update for PostCSS Craco installation - Change package.json - Craco.config **craco.config.js** ```js module.exports = style: { postcss: { plugins: [ require ('tailwindcss'), require ('autoprefixer'), ], }, }, } ``` npx this ```zsh npx tailwindcss init ``` >[!note] > Tailwind will actually remove anything you're not using as part of it's postcss process (it's a PostCSS plugin). This is much better than something like Bootstraps which will add a lot of unecessary stuff. > **tailwind.config.js** ```json module.exports = { purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [require('@tailwindcss/forms')] } ``` In **Index.css** add this to the very beginning to setup base for tailwind. ```css @tailwind base; @tailwind components; @tailwind utilities; ``` [[Tailwind]] starting classes - `container` - creates grid - `mx-auto` - centers grid - `mt-3` - top margin - `font-thin` - thin fonts - `inline-block` - self described - `text-red-400` - format is *property-color-shade* - shade will go from light tint to dark bold ```js function App() { return ( <div className="App container mx-auto mt-3 font-thin"> <h1><BiArchive />Your appointments</h1> </div> ); } ``` Could be cool to build next app using Tailwind. ### Getting started He mostly added the components from the provided site into different components of the project, not a lot of actual coding, just making sure everything links up correctly. - Home - Appointments Info - Appointments Component - Appointments Data - Drop Down JSX - Search component [Components](https://github.com/LinkedInLearning/react-interface-2880067/wiki/Appointments-Data)[^4] ### Sort and Search Adding a easy toggle button - `onClick` with `setToggleForm` to opposite of whatever the `toggleForm` state currently is. ```js const AddAppointment = () => { let [toggleform, setToggleForm] = useState(false) return ( <div> <button onClick={() => { setToggleForm(!toggleForm) }} ... { toggleForm && // includes form code in curl braces, // will only show rest of code if toggleForm // is in correct state. } ) } ``` He also created an expression which will alter the Tailwind CSS depending on the toggle state. ```js <button onClick={() => { setToggleForm(!toggleForm) }} className={`bg-blue-400 text-white px-2 py-3 w-full text-left rounded-t-md`} ${toggleForm ? 'rounded-t-md' : 'rounded-md' }`}> ``` This uses a terniary to specify which class will show depending on the state. Pretty cool! This can be passed down to the child component by adding a component with `toggle` attribute, and then passing that attribute as a prop into the child component. ```js const DropDown = ({ toggle }) => { if (!toggle) { return null; } return ( // rest of component ) } ... // below, button will cal togglesort through passed attribute <DropDown toggle={toggleSort} /> ``` Keep in mind, this doesn't just hide the component - it won't be showing at all. ### Mutating Data Set the appointment data to state so that it can be used when needed from state. Set `useState` to an empty array (where it should land when fetched). ```js function App() { let [apointmentList, setAppointmentList] = useState([]) } ``` He added the `data.json` file to the *public* folder so that it can fetch the data from a server. It can still be accessed in the application. Now fetch the data within the `App` component using `useCallBack()` hook. ```js function App() { let [apointmentList, setAppointmentList] = useState([]) const fetchData = useCallback(() => { fetch('./data.json') .then(response => response.json()) .then(data => { setAppointmentList(data) }) }, []) ... }) } ``` Now with `useEffect()` we can issue the `fetchData()` function from before ```js useEffect(() => { fetchData() }, [fetchData]); ``` ### Deleting Records The parent component will manage the data that has been loaded in, but we can create a button in the child component to tell the parent what record needs to be accessed. ```js const AppointmentInfo = ({ appointment }) => { return ( <li className="px-3 py-3 flex items-start"> <button onClick={() => onDeleteAppointment(appointment.id)} type="button"> <BiTrash /></button> ) } ``` Now in the parent element we'll write a function that will take the `appointment.id` passed through `onDeleteAppointment` and will use `filter` to find any matching records in the list in order to remove. ```js <ul className="..." {appointmentList .map(appointment => ( <AppointmentInfo key={appointment.id} appointment={appointment} onDeleteAppointment={ appointmentId => setAppointmentList(appointmentList .filter (appointment => appointment.id !== appointmentId)) } )) } ``` This is a long function and could be extracted, but it also could be kept as is ### Searching with a filtered array Similar to my Breaking News App, but this time the input will have the `onChange` handler inline within the component. Remember - `type="text"` - type of field - name - `name=query` for checking for searched info - id - `id=query` - value `value={query}` - *important* where the value of `onQueryChange()` will return - `onChange` will trigger event to pass target (searched text) to the `{query}` expression ```js <input type="text" name="query" id="query" value={query} onChange={(event) => {onQueryChange(event.target.value)}} ``` We need to add the `query` and `onQueryChange` event as props to the component to pass back up to the state. **Child Props** ```js const Search = ({ query, onQueryChange }) => { ``` **Parent State** ```js let [query, setQuery] = useState(''); ... // initializes query variable to capture data in Search component <Search query={query} onQueryChange={myQuery => setQuery(myQuery) }/> ``` We now want to modify the data. However, we don't want to modify the original data, so we need to create a separate array that simply keeps the appointment filtered by the query so that the original list is safe. ```js const filteredAppointments = appointmentList.filter( item => { return ( item.petName.toLowerCase().includes(query.toLowerCase())) || item.ownerName.toLowerCase().includes(query.toLowerCase())) || item.aptNotes.toLowerCase().includes(query.toLowerCase())) || ) } ) ``` The `filteredAppointments` will now show instead, which will also be much quicker since it's only comparing the fetched data and returning a filter of what's already been loaded into the main list. ### Setting up a Sort Adding `sort` to the `filteredAppointments` list ```js const filteredAppointments = appointmentList.filter( item => { return ( item.petName.toLowerCase().includes(query.toLowerCase())) || item.ownerName.toLowerCase().includes(query.toLowerCase())) || item.aptNotes.toLowerCase().includes(query.toLowerCase())) || ) } ).sort((a, b) => { let order = (orderBy==='asc') ? 1 : -1; return ( a[sortBy].toLowercase() < b[sortBy].toLowerCase() ? -1 * order : 1 * order ) }) ``` >[!tip] > This sets up a sort that will sort each item alphabetically by comparing them and then moving them up or down (1 : -1) and then returning them to the ordered list variable. > > This can be modified simply by changing the `useState('petName')` value to `useState('ownerName')` for another list value changing the `useState('asc')` to `useState('desc')` for the descending sort. > > ![](https://i.imgur.com/Bwh97UB.png) ### Programming a sorting interface Now, as part of the Search component, we're goint to build functionality that will build the query, sort, and order options above into the component itself. ```js <Search query={query} onQueryChange={myQuery => setQuery(myQuery)} orderBy={orderBy} onOrderByChange={mySort => setOrderBy(mySort)} sortBy={sortBy} onSortByChange={mySort => setSortBy(mySort)} /> ``` Now pass the order and sort state and handler as props in the Search component so they can be added. ```js const Search = ({ query, onQueryChange, sortBy, onSortByChange, orderBy, onOrderByChange}) ``` Add the same thing into the Dropdown component, just like the Search component and make sure it's also passing the same props ```js const DropDown = ({ toggle, sortBy, onSortByChange, orderBy, onOrderByChange}) ``` ```js <DropDown toggle={toggleSort} sortBy={sortBy} onSortByChange={mySort => onSortByChange(mySort)} orderBy={orderBy} onOrderByChange={myOrder => onOrderByChange(myOrder)} /> ``` Now you can add click events to each of the fields in order to wire them up to the sort functionality. ```js <div onClick={()=> onSortByChange('petName')} ... <div onClick={()=> onSortByChange('ownerName')} ... <div onClick={()=> onSortByChange('aptDate')} ... <div onClick={()=> onOrderByChange('asc')} ... <div onClick={()=> onOrderByByChange('desc')} ``` ### New Appointments He covers setting up a form to collect new Appointment and user information. New form data can also be used to ClearData after a form is complete. ![](https://i.imgur.com/HbBb0AU.png) The spread operator can be used to add new data without overwriting the form data that currently exists. ![](https://i.imgur.com/khGoflQ.png) Important to consider when they would be needed since only passing `event.target.value` would *overwrite* the current state of the form data. Each of the form fields need to follow a similar structure. Each of the fields will fill information that can be added to an object in a new `formDataPublish` function, which will be used to compute certain data before it's sent off to the parent component. Finish by 1) sending, 2) clearing the form, 3) toggle the form closed. ![](https://i.imgur.com/lhHGzQp.png) Finally add `onSendAppointment` handler and `lastId` as props to the `AddAppointment` component. **Note, must be added as an object not an array** Now add that component above the `Search` component and add the new appointment using the spread operater almost like an array *push* (same as the input fields). ![](https://i.imgur.com/rw3I6Ns.png) He then created a very complicated `reduce` function that will sort new items to the appointmentList by checking to see if the new item is the largest, and then sorting it if it isn't. **Very confusing stuff here** ![](https://i.imgur.com/Eb9mrv7.png) [^1]: Building an Interface. [Course](https://www.linkedin.com/learning/react-js-building-an-interface-8551484) [^2]: React Icons. [Library/Github](https://react-icons.github.io/react-icons) [^3]: Tailwind CSS Installation. [Tailwind Website](https://tailwindcss.com/docs/installation) [^4]: Pre-Built appointment components from exercise [Github](https://github.com/LinkedInLearning/react-interface-2880067/wiki/Appointments-Data) [^5]: 'Building a GraphQL Project with React.js' (2021). [LinkedIn Learning Course](https://www.linkedin.com/learning/building-a-graphql-project-with-react-js)