## 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.
>
> 
### 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.

The spread operator can be used to add new data without overwriting the form data that currently exists.

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.

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).

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**

[^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)