@ssbdev/react-web-utilities
TypeScript icon, indicating that this package has built-in type declarations

0.3.1 • Public • Published

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

Package Sidebar

Install

npm i @ssbdev/react-web-utilities

Weekly Downloads

1

Version

0.3.1

License

MIT

Unpacked Size

136 kB

Total Files

69

Last publish

Collaborators

  • sanathsharma