Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
import React from "react";
import { render, screen } from "@testing-library/react";
import Apps from "./App";

test('renders learn react link', () => {
render(<App />);
test("renders learn react link", () => {
render(<Apps />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
8 changes: 4 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import logo from './logo.svg';
import './App.css';
import "./App.css";
import ReactUseRefHooks from "./hooks/useref/UseRefHook";

function App() {
return (
<div className="App">

<div className=" h-full w-full">
<ReactUseRefHooks />
</div>
);
}
Expand Down
Binary file added src/assets/success.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions src/hooks/useref/UseRefHook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import InfiteScrollView from "./component/InfiniteScrollView";
import InfiteScrollViewCorrected from "./component/InfiniteScrollViewCorrected";
import ListComponent from "./component/ListComponent";

export interface ReactUseStateHooksProps {}

export default function ReactUseRefHooks(props: ReactUseStateHooksProps) {
return (
<div className="flex-row justify-normal bg-cover m-5 outline-sky-600">
<Row1 />
<Row2 />
<Row3 />
</div>
);
}

function Row1() {
return (
<>
<div className="m-4 py-2 text-lg font-mono">
1. List scroll component with useRef hook
</div>
<ListComponent />
</>
);
}

function Row2() {
return (
<>
<div className="m-4 py-2 text-lg font-mono">
2. Infinite List implementation using the useRef hook.
</div>
<InfiteScrollView />
</>
);
}

function Row3() {
return (
<>
<div className="m-4 py-2 text-lg font-mono">
3. Infinite list implementation using the useRef and UseReducer to
correct the loading.
</div>
<InfiteScrollViewCorrected />
</>
);
}
65 changes: 65 additions & 0 deletions src/hooks/useref/component/InfiniteScrollView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Button, CircularProgress, TextField } from "@mui/material";
import { dataList, DataType } from "./data/ListData";
import { Ref, useCallback, useEffect, useRef, useState } from "react";

export interface InfiteScrollViewProps {}

type RefType = HTMLDivElement | null;

// Infinite scroll without useReducer it causes issue with loading spinner visibility.
export default function InfiteScrollViewCorrected(
props: InfiteScrollViewProps
) {
const [dataListCurrent, setDataListCurrent] = useState<DataType[]>([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);

const lastIndexRef = useRef<number>(0);
const lastListItemRef = useRef<RefType>(null);

// Data fetch mock code
useEffect(() => {
const observer = new IntersectionObserver((enteries) => {
if (enteries[0].isIntersecting && hasMore) {
setLoading(true);
//Code to mock the network request.
setTimeout(() => {
setDataListCurrent([...dataList.slice(0, lastIndexRef.current + 15)]);
lastIndexRef.current = lastIndexRef.current + 15;
if (lastIndexRef.current >= dataList.length - 1) {
setHasMore(false);
}
setLoading(false);
}, 2000);
}
});
if (lastListItemRef.current) {
observer.observe(lastListItemRef.current);
}

return () => {
if (lastListItemRef.current) observer.unobserve(lastListItemRef.current);
};
}, [lastListItemRef]);

return (
<div className="grid grid-cols-1s grid-flow-row gap-6 m-5">
<ul className=" flex-nowrap overflow-y-hidden whitespace-nowrap flex outline-dotted p-3 scroll-m-0 rounded-lg items-center">
{dataListCurrent.map((data: DataType) => (
<div
className=" p-3 bg-green-400 m-2 outline-1 rounded-md items-center"
key={data.id}
>
{data.name}
</div>
))}
{loading && (
<div className="h-16 w-16">
<CircularProgress />
</div>
)}
<div className="h-16" ref={lastListItemRef}></div>
</ul>
</div>
);
}
103 changes: 103 additions & 0 deletions src/hooks/useref/component/InfiniteScrollViewCorrected.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { CircularProgress, Icon, SvgIcon, TextField } from "@mui/material";
import { dataList, DataType } from "./data/ListData";
import { useEffect, useReducer, useRef, useState } from "react";
import Success from "../../../assets/success.png";
export interface InfiteScrollViewProps {}

type RefType = HTMLDivElement | null;

enum Type {
LOADING = "loading",
DATA = "data",
}

/**
* Type definition of the action type of the action for component.
*/
interface ActionType {
type: Type;
lastIndex: number;
}

interface State {
data: DataType[];
loading: boolean;
}

/**
* Reducer function to manage the states and actions.
*
* @param state Total number of the states to manage.
* @param action Action related to the state we have to manage.
* @returns
*/
function reducerFunction(state: State, action: ActionType) {
console.log(action.type);
switch (action.type) {
case Type.LOADING:
console.log(state.loading);
return { data: state.data, loading: !state.loading };
case Type.DATA:
return {
data: [...dataList.slice(0, action.lastIndex + 15)],
loading: state.loading,
};
}
}

const initialState: State = { data: [], loading: false };

// Infinite scroll without useReducer it causes issue with loading spinner visibility.
export default function InfiteScrollView(props: InfiteScrollViewProps) {
const [state, dispatch] = useReducer(reducerFunction, initialState);

const lastIndexRef = useRef<number>(0);
const lastListItemRef = useRef<RefType>(null);

// Data fetch mock code
useEffect(() => {
const observer = new IntersectionObserver((enteries) => {
if (enteries[0].isIntersecting) {
dispatch({ type: Type.LOADING, lastIndex: lastIndexRef.current });
//Code to mock the network request.
setTimeout(() => {
dispatch({ type: Type.DATA, lastIndex: lastIndexRef.current });
dispatch({ type: Type.LOADING, lastIndex: lastIndexRef.current });
lastIndexRef.current = lastIndexRef.current + 15;
if (lastIndexRef.current >= dataList.length - 1) {
}
}, 3000);
}
});
if (lastListItemRef.current) {
observer.observe(lastListItemRef.current);
}

return () => {
if (lastListItemRef.current) observer.unobserve(lastListItemRef.current);
};
}, [lastListItemRef]);

return (
<div className="grid grid-cols-1 grid-flow-row gap-6 m-5">
<ul className=" flex-nowrap overflow-y-hidden whitespace-nowrap flex outline-dotted p-3 scroll-m-0 rounded-lg items-center">
{state.data.map((data: DataType) => (
<div
className=" p-3 bg-green-400 m-2 outline-1 rounded-md"
key={data.id}
>
{data.name}
</div>
))}
<div className="h-16" ref={lastListItemRef}></div>
</ul>
<div className=" rounded-xl shadow-md w-fit p-4 items-center outline">
{state.loading ? (
<CircularProgress />
) : (
<img className="h-10" src={Success} />
)}
</div>
</div>
);
}
73 changes: 73 additions & 0 deletions src/hooks/useref/component/ListComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button, TextField } from "@mui/material";
import { dataList, DataType } from "./data/ListData";
import { useRef, useState } from "react";
import { text } from "stream/consumers";

export interface IListComponentProps {}

type RefType = HTMLUListElement | null;

export default function ListComponent(props: IListComponentProps) {
const [index, setIndex] = useState(0);

function scrollToIndex(index: number) {
const listNode: RefType = listRef.current;
const childNode = listNode!.querySelectorAll("ul > div")[index];
childNode.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "center",
});
}

const listRef = useRef<RefType>(null);

const handleFirstButtonClick = () => {
scrollToIndex(0);
};

const handleScrollToIndexButtonClick = () => {
scrollToIndex(index);
};

const handleSecondButtonClick = () => {
scrollToIndex(dataList.length - 1);
};

return (
<div className="grid grid-cols-2 grid-flow-row gap-6 m-5">
<div className="flex justify-between outline-dotted p-3 rounded-lg items-center">
<Button variant="contained" onClick={handleFirstButtonClick}>
Go to first
</Button>

<TextField
value={index}
onChange={(e: any) => {
setIndex(e.target.value);
}}
label="Index"
/>
<Button variant="contained" onClick={handleScrollToIndexButtonClick}>
Go to index
</Button>
<Button variant="contained" onClick={handleSecondButtonClick}>
Go to last
</Button>
</div>
<ul
className=" flex-nowrap overflow-y-hidden whitespace-nowrap flex outline-dotted p-3 scroll-m-0 rounded-lg items-center"
ref={listRef}
>
{dataList.map((data: DataType) => (
<div
className=" p-3 bg-green-400 m-2 outline-1 rounded-md"
key={data.id}
>
{data.name}
</div>
))}
</ul>
</div>
);
}
73 changes: 73 additions & 0 deletions src/hooks/useref/component/data/ListData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Type definition for the data string.
*/
export interface DataType {
id: number;
name: string;
}

/**
* Data list to show the infinite list thing.
*/
export const dataList: DataType[] = [
{ id: 1, name: "Services1" },
{ id: 2, name: "Engineering" },
{ id: 3, name: "Services2" },
{ id: 4, name: "Training" },
{ id: 5, name: "Support" },
{ id: 6, name: "Research and Development" },
{ id: 7, name: "Training" },
{ id: 8, name: "Human Resources" },
{ id: 9, name: "Services2" },
{ id: 10, name: "Legal1" },
{ id: 11, name: "Sales" },
{ id: 12, name: "Legal2" },
{ id: 13, name: "Accounting" },
{ id: 14, name: "Business Development" },
{ id: 15, name: "Accounting" },
{ id: 16, name: "Services3" },
{ id: 17, name: "Training" },
{ id: 18, name: "Research and Development" },
{ id: 19, name: "Human Resources" },
{ id: 20, name: "Legal3" },
{ id: 21, name: "Research and Development" },
{ id: 22, name: "Human Resources" },
{ id: 23, name: "Services4" },
{ id: 24, name: "Research and Development" },
{ id: 25, name: "Research and Development" },
{ id: 26, name: "Accounting" },
{ id: 27, name: "Product Management" },
{ id: 28, name: "Human Resources" },
{ id: 29, name: "Legal4" },
{ id: 30, name: "Legal5" },
{ id: 31, name: "Services5" },
{ id: 32, name: "Engineering1" },
{ id: 33, name: "Services6" },
{ id: 34, name: "Training" },
{ id: 35, name: "Support" },
{ id: 36, name: "Research and Development" },
{ id: 37, name: "Training" },
{ id: 38, name: "Human Resources" },
{ id: 39, name: "Services7" },
{ id: 40, name: "Legal6" },
{ id: 41, name: "Sales" },
{ id: 42, name: "Legal7" },
{ id: 43, name: "Accounting" },
{ id: 44, name: "Business Development" },
{ id: 45, name: "Accounting" },
{ id: 46, name: "Services7" },
{ id: 47, name: "Training" },
{ id: 48, name: "Research and Development" },
{ id: 49, name: "Human Resources" },
{ id: 50, name: "Legal8" },
{ id: 51, name: "Research and Development" },
{ id: 52, name: "Human Resources" },
{ id: 53, name: "Services8" },
{ id: 54, name: "Research and Development" },
{ id: 55, name: "Research and Development" },
{ id: 56, name: "Accounting" },
{ id: 57, name: "Product Management" },
{ id: 58, name: "Human Resources" },
{ id: 59, name: "Legal9" },
{ id: 60, name: "Legal10" },
];
Loading