Skip to content

Commit

Permalink
manage loading state for home and list views
Browse files Browse the repository at this point in the history
  • Loading branch information
jtkabenni committed Mar 26, 2024
1 parent fac3fbe commit e769629
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 46 deletions.
15 changes: 11 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ export function App() {
* the shopping lists that the user has access to.
* Check ./api/firestore.js for its implementation.
*/
const lists = useShoppingLists(userId, userEmail);
const { listsData, areListsLoading } = useShoppingLists(userId, userEmail);
/**
* This custom hook takes our token and fetches the data for our list.
* Check ./api/firestore.js for its implementation.
*/
const data = useShoppingListData(listPath);
const { shoppingListData, isShoppingListLoading } =
useShoppingListData(listPath);

return (
<QueryClientProvider client={queryClient}>
Expand All @@ -57,7 +58,8 @@ export function App() {
index
element={
<Home
data={lists}
data={listsData}
areListsLoading={areListsLoading}
setListPath={setListPath}
listPath={listPath}
listName={listName}
Expand All @@ -68,7 +70,12 @@ export function App() {
<Route
path="/list"
element={
<List data={data} listPath={listPath} listName={listName} />
<List
data={shoppingListData}
isShoppingListLoading={isShoppingListLoading}
listPath={listPath}
listName={listName}
/>
}
/>
</Route>
Expand Down
17 changes: 9 additions & 8 deletions src/api/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import { getFutureDate, sortByDaysBetweenDates } from '../utils/dates.js';
export function useShoppingLists(userId, userEmail) {
// Start with an empty array for our data.
const initialState = [];
const [data, setData] = useState(initialState);
const [isLoading, setIsLoading] = useState(true);
const [listsData, setListsData] = useState(initialState);
const [areListsLoading, setAreListsLoading] = useState(true);

useEffect(() => {
// If we don't have a userId or userEmail (the user isn't signed in),
Expand All @@ -40,12 +40,13 @@ export function useShoppingLists(userId, userEmail) {
// We keep the list's id and path so we can use them later.
return { name: listRef.id, path: listRef.path };
});
setData(newData);
setListsData(newData);
setAreListsLoading(false);
}
});
}, [userId, userEmail]);

return data;
return { listsData, areListsLoading };
}

/**
Expand All @@ -58,8 +59,8 @@ export function useShoppingListData(listPath) {
// Start with an empty array for our data.
/** @type {import('firebase/firestore').DocumentData[]} */
const initialState = [];
const [data, setData] = useState(initialState);
const [loading, setLoading] = useState(true);
const [shoppingListData, setData] = useState(initialState);
const [isShoppingListLoading, setIsShoppingListLoading] = useState(true);

useEffect(() => {
if (!listPath) return;
Expand All @@ -81,12 +82,12 @@ export function useShoppingListData(listPath) {

// Update our React state with the new data.
setData(nextData);
setLoading(false);
setIsShoppingListLoading(false);
});
}, [listPath]);

// Return the data so it can be used by our React components.
return { data, loading };
return { shoppingListData, isShoppingListLoading };
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/components/AddItemForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export default function AddItemForm({ listPath, data }) {
const [itemDuration, setItemDuration] = useState(7);

const normalizedItemNames = useMemo(() => {
return data?.data.map((item) => normalizeInput(item.name));
}, [data?.data]);
return data?.map((item) => normalizeInput(item.name));
}, [data]);

const {
isSuccess,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function sortByDaysBetweenDates(data) {
let inactiveArr = [];
const alphabetSort = (a, b) => a.name.localeCompare(b.name);

data.data.forEach((item) => {
data.forEach((item) => {
const daysBetween = getDaysBetweenDates(
item.dateLastPurchased?.toDate(),
item.dateNextPurchased?.toDate(),
Expand Down
23 changes: 16 additions & 7 deletions src/views/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import './Home.css';
import AddListForm from '../components/AddListForm.jsx';
import SelectListForm from '../components/SelectListForm.jsx';
export function Home({ data, setListPath, listPath, listName }) {
console.log(data);
export function Home({
data,
areListsLoading,
setListPath,
listPath,
listName,
}) {
return (
<div className="Home">
<h4>Current List:</h4>
Expand All @@ -12,11 +17,15 @@ export function Home({ data, setListPath, listPath, listName }) {
<div>
<p>----- OR -----</p>
</div>
<SelectListForm
data={data}
listPath={listPath}
setListPath={setListPath}
/>
{areListsLoading ? (
<div>Loading lists...</div>
) : (
<SelectListForm
data={data}
listPath={listPath}
setListPath={setListPath}
/>
)}
</div>
);
}
20 changes: 9 additions & 11 deletions src/views/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import './List.css';
import AddItemForm from '../components/AddItemForm';
import ShareForm from '../components/ShareForm';

export function List({ data, listPath }) {
export function List({ data, isShoppingListLoading, listPath, listName }) {
const [input, setInput] = useState('');

const sortedItems = comparePurchaseUrgency(data);
Expand All @@ -28,13 +28,6 @@ export function List({ data, listPath }) {
setInput('');
}

const listName = listPath?.substring(listPath.indexOf('/') + 1);

if (data.data.loading) {
// If data is not loaded yet, render a loading indicator
return <p>Loading...</p>;
}

if (!listPath) {
return (
<>
Expand All @@ -43,13 +36,18 @@ export function List({ data, listPath }) {
);
}

if (isShoppingListLoading) {
// If data is not loaded yet, render a loading indicator
return <p>Loading...</p>;
}

return (
<>
<h2>{listName}</h2>
{data.data.length === 0 && (
{data.length === 0 && (
<h2 data-testid="noItemsErrorMsg">You have no items in this list!</h2>
)}
{data.data.length !== 0 && (
{data.length !== 0 && (
<div className="searchInput">
<form action="" onSubmit={(e) => e.preventDefault()}>
<label htmlFor="searchItems">Search your shopping list: </label>
Expand All @@ -68,7 +66,7 @@ export function List({ data, listPath }) {
<ListItem key={item.id} item={item} listPath={listPath} />
))}
</div>
{data.data.length > 0 && filteredItems.length === 0 && (
{data.length > 0 && filteredItems.length === 0 && (
<div>
<p>No match found for that filter query.</p>
</div>
Expand Down
10 changes: 5 additions & 5 deletions tests/components/AddItemForm/AddItemForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('AddItemForm', () => {
};

it('renders AddItemForm', () => {
const data = { data: [{ name: 'apple' }], loading: false };
const data = [{ name: 'apple' }];
renderAddItemForm({ listPath: '/test', data });
});

Expand All @@ -36,7 +36,7 @@ describe('AddItemForm', () => {
totalPurchases: 0,
};

const data = { data: [{ name: 'apple' }], loading: false };
const data = [{ name: 'apple' }];

const mockedAddItem = vi.spyOn(FirebaseFunctions, 'addItem');
mockedAddItem.mockImplementationOnce(async () => {
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('AddItemForm', () => {
});

it('Adds item but get an error', async () => {
const data = { data: [{ name: 'apple' }], loading: false };
const data = [{ name: 'apple' }];

const mockedAddItem = vi.spyOn(FirebaseFunctions, 'addItem');

Expand Down Expand Up @@ -101,7 +101,7 @@ describe('AddItemForm', () => {
});

it('Tries to add duplicate item but gets error', async () => {
const data = { data: [{ name: 'apple' }], loading: false };
const data = [{ name: 'apple' }];

const mockedAddItem = vi.spyOn(FirebaseFunctions, 'addItem');

Expand All @@ -126,7 +126,7 @@ describe('AddItemForm', () => {
expect(mockedAddItem).toHaveBeenCalledTimes(0);
});
it('Tries to add empty string but gets error', async () => {
const data = { data: [{ name: 'apple' }], loading: false };
const data = [{ name: 'apple' }];

const mockedAddItem = vi.spyOn(FirebaseFunctions, 'addItem');

Expand Down
36 changes: 28 additions & 8 deletions tests/components/List/List.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ describe('List', () => {
vi.clearAllMocks();
});

const renderList = ({ listPath, data }) => {
const renderList = ({ data, isShoppingListLoading, listPath, listName }) => {
return render(
<QueryClientProvider client={queryClient}>
<List listPath={listPath} data={data} />
<List
data={data}
isShoppingListLoading={isShoppingListLoading}
listPath={listPath}
listName={listName}
/>
</QueryClientProvider>,
);
};

it('renders AddItemForm and ShareForm if listPath is passed in', async () => {
const data = { data: [{ name: 'apple' }], loading: false };
renderList({ listPath: '/test-list', data });
const listProps = {
data: [{ name: 'apple' }],
isShoppingListLoading: false,
listPath: '/test-list',
listName: 'test-list',
};
renderList(listProps);

const addItemForm = screen.queryByTestId('addItemForm-header');
const shareForm = screen.queryByTestId('shareForm-header');
Expand All @@ -32,8 +42,13 @@ describe('List', () => {
});

it('renders error message when listPath is not passed in', async () => {
const data = { data: [] };
renderList({ listPath: '', data });
const listProps = {
data: [{ name: 'apple' }],
isShoppingListLoading: false,
listPath: '',
listName: 'test-list',
};
renderList(listProps);

const addItemForm = screen.queryByTestId('addItemForm-header');
const shareForm = screen.queryByTestId('shareForm-header');
Expand All @@ -45,8 +60,13 @@ describe('List', () => {
});

it('renders error message when items are not passed in', async () => {
const data = { data: [] };
renderList({ listPath: '/test-list', data });
const listProps = {
data: [],
isShoppingListLoading: false,
listPath: '/test-list',
listName: 'test-list',
};
renderList(listProps);
const errorMessage = screen.getByTestId('noItemsErrorMsg');

expect(errorMessage).toBeInTheDocument();
Expand Down

0 comments on commit e769629

Please sign in to comment.