0.3.1 • Published 4 years ago

@ssbdev/react-web-utilities v0.3.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

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 NameTypeDefaultRequired (or) OptionalDescription
onResponseFulfilled( res: AxiosResponse ) => AxiosResponse | undefinedundefinedOptionalCallBack that can be used to modify response
onResponseRejected( error: AxiosError ) => AxiosError | undefinedundefinedOptionalCallBack that can be used to update / make ui changes based on errors
onRequestFulfilled( req: AxiosRequestConfig ) => AxiosRequestConfig | undefinedundefinedOptionalCallback that can be use to set headers before the request goes to server
onRequestRejected( error: AxiosError ) => AxiosError | undefinedundefinedOptionalCallBack that can be used to update / make ui changes based on errors
custom{ [name: string]: ( ( this: AxiosInstance, ...args: any ) => any ) | string | number | any[] | undefined | null> }{}Optionaladds addition methods and properties on client (axios intance)
...restAxiosRequestConfig--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 NameTypeDefaultRequired (or) OptionalDescription
onResponseFulfilled( res: AxiosResponse ) => AxiosResponse | undefinedundefinedOptionalCallBack that can be used to modify response
onResponseRejected( error: AxiosError ) => AxiosError | undefinedundefinedOptionalCallBack that can be used to update / make ui changes based on errors
onRequestFulfilled( req: AxiosRequestConfig ) => AxiosRequestConfig | undefinedundefinedOptionalCallback that can be use to set headers before the request goes to server
onRequestRejected( error: AxiosError ) => AxiosError | undefinedundefinedOptionalCallBack that can be used to update / make ui changes based on errors
...restAxiosRequestConfig--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 NameTypeDefaultRequired (or) OptionalDescription
pathstringundefinedRequiredAny valid URL path
componentReact.ComponentType<any>undefinedOptionalA React component to render only when the location matches. It will be rendered with route props
activebooleantrueOptionalWhen false, route is equivalent to not existing
namestringundefinedOptionalA Name for the route, used by the crumb
...restRouteProps--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 NameTypeDefaultRequired (or) OptionalDescription
conditionbooleanfalseOptionalboolean or expression that evaluates to true or false. true -> render the children, false -> render loadingPlaceholder
errorConditionbooleanfalseOptionalboolean or expression that evaluates to true or false. true -> render the errorPlaceholder, false -> render loadingPlaceholder / children based on condition
loadingPlaceholderReact.ComponentType<any>undefinedRequiredComponent to rendered if loading is true. Constructor of component. i.e, LoaderComponent instead of <LoaderComponent />
multipliernumber1OptionalThe number of placeholders to be rendered
errorPlaceholderReact.ReactNodeundefinedOptionalComponent to rendered if error occurs. Instance of a component, unlike loadingPlaceholder. i.e, <Retry onClick={ ... } /> instead of Retry
childrenReact.ReactNodeArray | React.ReactNodeundefinedOptionalThe actual component(s) that will be rendered when the condition evaluates to true
initialDelaynumber0OptionalMinimum 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 NameTypeDefaultRequired (or) OptionalDescription
componentNamestringundefinedRequiredName 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

optionTypeDefaultRequired (or) OptionalDescription
method( ...args: any ) => Promise<any>undefinedRequiredReference to the function which returns a Promise
argsParameters<method>undefinedRequiredArguments to the function refered for "method"
dependenciesany[][]OptionalRefetch based on dependency value change, useEffect dependency array
normalizeboolean | stringfalseOptionalnormalizes 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 ) => voidundefinedOptionalCallback that gets called on api request gets rejected with an error
conditionbooleantrueOptionalCondition to fetch. true -> make the api request on fetch Call (or) false -> donnot make api request on fetch call
defaultDataanynullOptionalDefault state of data
transformResponse( res: any ) => anyundefinedRequiredTransform 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
onCancelMsgstringundefinedOptionalmessage of the error thrown on request cancel
onCancel( e: AxiosError | Error ) => voidundefinedOptionalcallback which is called when an ongoing request is canceled. onError is not called when onCancel is present and request is canceled

Return object

keysTypeDefaultDescription
fetchedFetched"FALSE"Tells at what state the api call is in. One of "FALSE" | "FETCHING" | "ERROR" | "TRUE"
dataanynullResponse data or normalized response data
setDataReact.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

optionTypeDefaultRequired (or) OptionalDescription
iconReact.ReactNodeundefinedRequiredicon 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 nameTypeDefaultRequired (or) OptionalDescription
touchedbooleanundefinedOptionalBoolean which tells whether the form field for focused or not
errorstringundefinedOptionalString which contains error message
align"start" | "center" | "end""start"Optionalallignment 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

optionTypeDefaultRequired (or) OptionalDescription
defaultIconReact.ReactNodeundefinedRequiredicon node.
activeLinkClassstring"breadcrumb--active"OptionalClassName given to active crumb link
inactiveLinkClassstring"breadcrumb--anchor"OptionalClassName given to inactive crumb link
iconWrapperClassstring"breadcrumb--icon-wrapper"OptionalClassName 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 nameTypeDefaultRequired (or) OptionalDescription
crumbsCrumbType[]undefinedRequiredcrumb details passed along with routeProps. *Note*: Avaliable in RouteComponentProps only when used in components rendred by "Routes" component
iconReact.ReactNodedefaultIconOptionaloverride defaultIcon

CrumbType

key nameTypeDescription
keystringunique key
contentstringContent that identifies the the path
activebooleanIndicates whether the path visited is this path or not
hrefstringpath

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

0.3.1

4 years ago

0.3.0

4 years ago

0.2.3

4 years ago

0.2.4

4 years ago

0.2.1

4 years ago

0.2.2

4 years ago

0.2.0

4 years ago

0.1.0

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago