react-web-utilities
React utility library with handy hooks, components, helper functions.
Table of contents
Install
with npm,
npm install --save @ssbdev/react-web-utilities
with yarn,
yarn add @ssbdev/react-web-utilities
Usage
Services
buildClient
Builds a axios instance.
Options
Option Name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
onResponseFulfilled | `( res: AxiosResponse ) => AxiosResponse |
undefined` | undefined |
Optional |
onResponseRejected | `( error: AxiosError ) => AxiosError |
undefined` | undefined |
Optional |
onRequestFulfilled | `( req: AxiosRequestConfig ) => AxiosRequestConfig |
undefined` | undefined |
Optional |
onRequestRejected | `( error: AxiosError ) => AxiosError |
undefined` | undefined |
Optional |
custom | `{ [name: string]: ( ( this: AxiosInstance, ...args: any ) => any ) |
string | number | any[] |
...rest | AxiosRequestConfig |
- | - | axios request configuration |
Basic usage
Without token,
const Client = buildClient({
baseURL: "http://localhost:8000",
});
// ...
Client.put(
// url
"/api/books",
// data
{
title: "Update title",
desc: "Updated description",
},
// config
{
// params
params: { bookId: 21 },
}
)
.then(res => {
// then block
})
.catch(error => {
// catch block
});
// ...
With token,
Attaching token to Authorization header can be done in onRequestFulfilled callback code,
// api.service.js
// ...
import { buildClient } from "@ssbdev/react-web-utilities";
// ...
const Client = buildClient({
baseURL: "http://localhost:8000",
onRequestFulfilled(req) {
const token = localStorage.getItem("token") ?? "";
req.headers["Authorization"] = `Bearer ${token}`;
return req;
},
onResponseRejected(error) {
// e.g, unauthorized error
if (error.response && error.response.status === 401) {
// redirection logic (or) reenter password popup
// ...
}
},
});
export { Client };
// MyComponent.js
// ...
import { Client } from "./api.service.js";
// ...
export default () => {
const [data, setData] = useState([]);
useEffect(() => {
Client.post(
// url
"/api/books",
// data
{ title: "MyBook", price: 100 }
)
.then(res => {
setData(res.data);
})
.catch(e => {
console.log("ERROR:", e);
});
}, []);
return <div>// ...</div>;
};
Advance Usage
Adding custom methods and properties. "this" inside a custom method points to axios instance
// api.service.js
// ...
import { buildClient } from "@ssbdev/react-web-utilities";
// ...
const Client = buildClient({
baseURL: "http://localhost:8000",
custom: {
getStaticBase() {
return this.defaults.baseURL + "/static";
},
upload(
method, // "post" | "put"
url,
data,
config // AxiosRequestConfig
) {
return this[method](url, data, {
timeout: 0,
onUploadProgress(e) {
// e: ProgressEvent
const progress = (e.loaded / e.total) * 100;
// logic to indicate the progress on the ui
// ...
},
...config,
});
},
},
});
export { Client };
// ...
BuildClientWithCanceler
Similar to buildClient, but this is a constructor which provides a canceler function along with the promise.
Options
Option Name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
onResponseFulfilled | `( res: AxiosResponse ) => AxiosResponse |
undefined` | undefined |
Optional |
onResponseRejected | `( error: AxiosError ) => AxiosError |
undefined` | undefined |
Optional |
onRequestFulfilled | `( req: AxiosRequestConfig ) => AxiosRequestConfig |
undefined` | undefined |
Optional |
onRequestRejected | `( error: AxiosError ) => AxiosError |
undefined` | undefined |
Optional |
...rest | AxiosRequestConfig |
- | - | axios request configuration |
Basic usage
const Client = new BuildClientWithCanceler({
baseURL: "http://localhost:8000",
});
// ...
const [promise, canceler] = Client.put(
// url
"/api/books",
// data
{
title: "Update title",
desc: "Updated description",
},
// config
{
// params
params: { bookId: 21 },
}
);
promise
.then(res => {
// then block
})
.catch(error => {
// catch block
});
//... some where else in the code, to cancel the request
canceler();
// ...
Usage with useEffect
// api.service.js
// ...
import { buildClient } from "@ssbdev/react-web-utilities";
// ...
const Client = new BuildClientWithCanceler({
baseURL: "http://localhost:8000",
});
export { Client };
// MyComponent.js
// ...
import { Client } from "./api.service.js";
// ...
export default () => {
const [data, setData] = useState([]);
useEffect(() => {
const [promise, canceler] = Client.post(
// url
"/api/books",
// data
{ title: "MyBook", price: 100 }
);
promise
.then(res => {
setData(res.data);
})
.catch(e => {
console.log("ERROR:", e);
});
return () => canceler();
}, []);
return <div>// ...</div>;
};
Core
Routes
Responsible for rendering all the routes with switch. Uses react-router-dom, "Switch" & "Route" Components
Props table
Prop Name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
path | string |
undefined |
Required | Any valid URL path |
component | React.ComponentType<any> |
undefined |
Optional | A React component to render only when the location matches. It will be rendered with route props |
active | boolean |
true |
Optional | When false, route is equivalent to not existing |
name | string |
undefined |
Optional | A Name for the route, used by the crumb |
...rest | RouteProps |
- | - | rest of react-router-dom's RouteProps. For more info, click here. |
Basic usage
// AppRouter.js
// ...
import { Routes } from "@ssbdev/react-web-utilities";
// ...
export default function AppRouter() {
const routes = [
{
path: "/",
exact: true,
component: Home,
},
{
path: "/books",
exact: true,
component: Books,
},
{
path: "/books/1",
exact: true,
component: BookInfo,
},
{
path: "/books/1/author",
exact: true,
component: AuthorInfo,
},
];
return <Routes routes={routes} redirectTo="/" />;
}
Usage with crumbs
// AppRouter.js
// ...
import { Routes } from "@ssbdev/react-web-utilities";
// ...
export default function AppRouter() {
const routes = [
{
path: "/",
exact: true,
component: Home,
name: "Home",
},
{
path: "/books",
exact: true,
component: Books,
name: "All Books",
},
{
path: "/books/1",
exact: true,
component: BookInfo,
name: "Book",
},
{
path: "/books/1/author",
exact: true,
component: AuthorInfo,
name: "Author",
},
];
return <Routes routes={routes} crumbs={true} redirectTo="/" />;
}
Basic Breadcrumb navigation component implementation. or use createBreadcrumb instead.
// Crumbs.js
// ...
export const Crumbs = props => {
const { details } = props;
const renderCrumbs = () => {
const displayContent = [];
details.forEach((detail, index) => {
const { key, content, active, href } = detail;
displayContent.push(
<Fragment key={key}>
{active ? (
<a href={href}>{content}</a>
) : (
<span>{content}</span>
)}
</Fragment>
);
// if not the last element
if (details.length !== index + 1) {
displayContent.push(
<span key={`${key}_seperator`} className="icon">
/
</span>
);
}
});
return displayContent;
};
return <div className="flex row">{renderCrumbs()}</div>;
};
"crumbs" prop usage,
// e.g, AuthorInfo.js
// ...
import Crumbs from "./Crumbs";
// ...
export const AuthorInfo = props => {
const { crumbs } = props;
return (
<div className="flex col">
<Crumbs details={crumbs} />
<div>AuthorInfo ...</div>
</div>
);
};
LetSuspense
Implementation inspired by a blog How to Create a LetSuspense Component in React.
Component that renders a placeholder component, till the condition satisfies for it to display its children. i.e, When condition evaluates true
, LetSuspense children are rendered & when condition evaluates false
, the loadingPlaceholder is rendered muliplier number of times.
Props table
Prop Name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
condition | boolean |
false |
Optional | boolean or expression that evaluates to true or false . true -> render the children, false -> render loadingPlaceholder
|
errorCondition | boolean |
false |
Optional | boolean or expression that evaluates to true or false . true -> render the errorPlaceholder, false -> render loadingPlaceholder / children based on condition
|
loadingPlaceholder | React.ComponentType<any> |
undefined |
Required | Component to rendered if loading is true. Constructor of component. i.e, LoaderComponent instead of <LoaderComponent />
|
multiplier | number |
1 |
Optional | The number of placeholders to be rendered |
errorPlaceholder | React.ReactNode |
undefined |
Optional | Component to rendered if error occurs. Instance of a component, unlike loadingPlaceholder. i.e, <Retry onClick={ ... } /> instead of Retry
|
children | React.ReactNodeArray | React.ReactNode |
undefined |
Optional | The actual component(s) that will be rendered when the condition evaluates to true
|
initialDelay | number |
0 |
Optional | Minimum time (in milliseconds) before a component is rendered |
Basic usage
// MyComponent.js
// ...
export default ()=>{
const [data, setData] = useState([]);
useEffect( () => {
// ...
fetchData()
// ...
}, [] )
const fetchData = () => {
// api request logic
}
return (
<LetSuspense
condition={ data.length > 0 }
loadingPlaceholder={ LoaderComponent }
>
{
data.map( each => (
...
) )
}
</LetSuspense>
);
};
With delay
Useful to show a loding screen event though there is not async dependency, meaning no backend api to hit to fetch the data, but still want to show the loding component for minute amount of time.
// MyComponent.js
// ...
export default () => {
return (
<LetSuspense
condition={true}
loadingPlaceholder={LoaderComponent}
delay={500}
>
// ...
</LetSuspense>
);
};
Hooks
useCountRenders
Logs the number of times the component rerendred after mount. Logs only in development environment.
Args Table
Arg Name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
componentName | string |
undefined |
Required | Name of the component. used in the logs detail |
usage
// MyComponent.js
// ...
export default () => {
useCountRenders("MyComponent");
return (
// jsx
);
};
useFetch
React hook for fetching data based on condition and dependency array.
Options
option | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
method | ( ...args: any ) => Promise<any> |
undefined |
Required | Reference to the function which returns a Promise |
args | Parameters<method> |
undefined |
Required | Arguments to the function refered for "method" |
dependencies | any[] |
[] |
Optional | Refetch based on dependency value change, useEffect dependency array |
normalize | boolean | string |
false |
Optional | normalizes based on the key provided. true -> normalizes by "id" (or) false -> directly sets data with the response data (or) "somekey" -> normalizes by "somekey" |
onError | ( e: AxiosError ) => void |
undefined |
Optional | Callback that gets called on api request gets rejected with an error |
condition | boolean |
true |
Optional | Condition to fetch. true -> make the api request on fetch Call (or) false -> donnot make api request on fetch call |
defaultData | any |
null |
Optional | Default state of data |
transformResponse | ( res: any ) => any |
undefined |
Required | Transform the response before storing data in the "data" state. Whatever is returned by the function is set to "data". It can also return a promise. Note: if normalize is true (or) "somekey", then normalization is done on object returned |
onCancelMsg | string |
undefined |
Optional | message of the error thrown on request cancel |
onCancel | `( e: AxiosError |
Error ) => void` | undefined |
Optional |
Return object
keys | Type | Default | Description |
---|---|---|---|
fetched | Fetched |
"FALSE" |
Tells at what state the api call is in. One of `"FALSE" |
data | any |
null |
Response data or normalized response data |
setData | React.Dispatch<React.SetStateAction<any>> |
- | Function to manipulate "data" state |
fetch | () => void |
- | Function to make the api call. General usecase is to call this function on retry if initial api request fails (fetched="ERROR" ) |
Basic Usage
// books.service.js
// ...
import Client from "./api.service.js";
export default {
getAllBooks() {
return Client.get( "/api/books" );
}
getBookInfo( bookId: number ) {
return Client.get( "api/books", { id: bookId } );
}
// ...other apis
}
// MyComponent.js
// ...
import bookService from "./books.service.js";
import { useFetch } from "@ssbdev/react-web-utilities";
import { useParams } from 'react-router-dom';
//...
export default ( props ) => {
const {
fetched: booksFetched,
data: books,
setData: setBooks,
fetch: fetchBooks
} = useFetch( {
method: bookService.getAllBooks,
args: [], // args of the above method
} );
return {
"FALSE": <div>Waiting...</div>,
"FETCHING": <div>Fetching Books...</div>,
"ERROR": <button onClick={ fetchBooks }>Retry</button>,
"TRUE": (
<div className="list">
{
books.map( book => (
<span key={ book.id }>{ book.title }</span>
) );
}
</div>
)
}[booksFetched];
};
Factories
ReduxActionConstants
Constructor that create a set of strings that can be used as Redux Action Types.
usage
// books.actions.js
// ...
const BOOKS = new ReduxActionConstants("books");
console.log(BOOKS);
// output
// {
// ENTITY: "BOOKS",
// INSERT: "[BOOKS] INSERT",
// UPDATE: "[BOOKS] UPDATE",
// REMOVE: "[BOOKS] REMOVE",
// BULK_INSERT: "[BOOKS] BULK_INSERT",
// BULK_UPDATE: "[BOOKS] BULK_UPDATE",
// BULK_REMOVE: "[BOOKS] BULK_REMOVE",
// SET: "[BOOKS] SET",
// UNSET: "[BOOKS] UNSET",
// RESET: "[BOOKS] RESET"
// }
const reducer_setBooks = books => ({
type: BOOKS.SET,
payload: {
books,
},
});
export { reducer_setBooks, BOOKS };
// books.reducer.js
import BOOKS from "./books.actions.js";
// ...
const initialState = {
books: [],
};
const booksReducer = (state = initialState, action) => {
switch (action.type) {
case BOOKS.SET: {
const { books } = action.payload;
return {
...state,
books: books,
};
}
default:
return state;
}
};
export { booksReducer, initialState as booksInit };
Service
Constructor that can be used with buildClient
to assist with cancelling a request.
Usage
// books.service.js
// ...
const client = buildClient({
baseURL: "http://localhost:8000",
});
class Books extends Service {
get() {
const { cancelToken, canceler } = this.generateCancelToken();
return [
client.get("api/books", {
cancelToken,
}),
canceler,
];
}
}
const BOOKS = new Books();
export { BOOKS };
// index.js
// ...
try {
const [promise, canceler] = BOOKS.get();
promise
.then(res => {
// then block
})
.catch(error => {
// catch block
});
} catch (e) {
if (BOOKS.isCancel(e)) {
return console.log("Request Canceled");
}
console.log(e);
}
//... some where else in the code, to cancel the request
canceler();
// ...
Component Creators
createFormError
Create "FormError" component, that can be instantiated elsewhere. Returns a react component
createFormError options
option | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
icon | React.ReactNode |
undefined |
Required | icon node. |
Example for icon
option,
<!-- fa icons -->
<i className="fa fa-exclamation-triangle" aria-hidden="true"></i>
<!-- material icons -->
<span class="material-icons">warning</span>
FormError props
prop name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
touched | boolean |
undefined |
Optional | Boolean which tells whether the form field for focused or not |
error | string |
undefined |
Optional | String which contains error message |
align | "start" | "center" | "end" |
"start" |
Optional | allignment of the error message |
Basic usage
// FormError.js
// ...
import { createFormError } from "@ssbdev/react-web-utilities";
export const FormError = createFormError({
icon: <i className="warning"></i>,
});
// MyForm.js
import { FormError } from "./FormError.js";
import '@ssbdev/react-web-utilities/build/styles/components/FormError.min.css';
// ...
export default () => {
// ...
return (
<form>
//...
<div className="field">
<label>My Input</label>
<input />
<FormError
...
/>
</div>
// ...
</form>
);
};
createBreadcrumb
Create "Breadcrumb" navigation component, that can be instantiated elsewhere. Returns a react component.
createBreadcrumb options
option | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
defaultIcon | React.ReactNode |
undefined |
Required | icon node. |
activeLinkClass | string |
"breadcrumb--active" |
Optional | ClassName given to active crumb link |
inactiveLinkClass | string |
"breadcrumb--anchor" |
Optional | ClassName given to inactive crumb link |
iconWrapperClass | string |
"breadcrumb--icon-wrapper" |
Optional | ClassName given for each seperator icon-wrapper |
Example for icon
option,
<!-- fa icons -->
<i className="fa fa-exclamation-triangle" aria-hidden="true"></i>
<!-- material icons -->
<span class="material-icons">warning</span>
Custom styles - Append to existing classes as shown below (or) provide override classes in the options
.breadcrumb {
/* your styles here */
}
.breadcrumb--anchor {
/* your styles here */
}
.breadcrumb--active {
/* your styles here */
}
.breadcrumb--icon-wrapper {
/* your styles here */
}
Breadcrumb props
prop name | Type | Default | Required (or) Optional | Description |
---|---|---|---|---|
crumbs |
CrumbType []
|
undefined |
Required | crumb details passed along with routeProps. *Note*: Avaliable in RouteComponentProps only when used in components rendred by "Routes" component |
icon | React.ReactNode |
defaultIcon | Optional | override defaultIcon |
CrumbType
key name | Type | Description |
---|---|---|
key | string |
unique key |
content | string |
Content that identifies the the path |
active | boolean |
Indicates whether the path visited is this path or not |
href | string |
path |
Basic usage
// Breadcrumb.js
// ...
import { createBreadcrumb } from "@ssbdev/react-web-utilities";
import "@ssbdev/react-web-utilities/build/styles/components/Breadcrumb.min.css";
export const Breadcrumb = createBreadcrumb({
icon: <i className="angle right"></i>,
});
// MyRouteComponent.js
import { Breadcrumb } from "./Breadcrumb.js";
// ...
export default props => {
const { crumbs } = props;
return (
<div className="flex col">
<Breadcrumb crumbs={crumbs} />
<div>AuthorInfo</div>
</div>
);
};
License & copyright
© Sanath Sharma
Licensed under MIT License