1.0.57 • Published 6 months ago

buro-lib-ts v1.0.57

Weekly downloads
-
License
ISC
Repository
-
Last release
6 months ago

Boiler

Boiler is a frontend/react library that contains commonly used functionality such as fetching resources from an API.

Installation with NPM

You can install the library using npm with the following command:

npm i react react-ts-boiler --save

Quick start

After installing the package you should first read the section about routing & enabling functionalities. After that you can setup a basic app with some routing:

import React from 'react';
import { BoilerApp, Router, enableRouting } from 'react-ts-boiler';

import routes from 'somewhere';

enableRouting({ routes });

export const App: FunctionComponent = () => {
    return (
        <BoilerApp>
            <Router />
        </BoilerApp>
    );
};

Enabling functionalities

Boiler offers some things out of the box such as routing and authentication. But you have to enable them first if you want to use them. For this boiler has a few enable functions that can be called. A good place for this is your initial App.ts file after all the imports.

  • Auth: See the auth section
  • Routing: See the routing section
  • Networking: See the networking section
  • Localization: See the localization section
  • Themes: See the themes section

Events

Boiler has an event system which can be used to subscribe to events without depending on certain components. The following functions are available:

  • onEvent (event: string, callback: (payload?: any) => void) to subscribe to an event
  • emitEvent (event: string, payload?: any) to emit an event, payload can be anything from a number to an object and is optional
  • onEventOnce (event: string, callback: (payload?: any) => void) to subscribe to an event only once
  • detachCallback (event: string, fn: (payload?: any) => void) to unsubscribe from an event

Both onEvent and onEventOnce return a detachCallback that can be used in useEffects:

useEffect(() => {
  return onEvent('event', someFunction);
}, []);

This system is handy for toasts, but the preferred way is to use react hooks if possible.

Routing

Boiler offers a built in routing system. With this system you can add named routes like in laravel. Meaning you can give a name to routes and refer to them later on in your application.

To enable routing you have to use the enableRouting function.

Enable function

enableRouting({ 
    routes: Route[] // An array of route objects
});

A Route object looks as follows:

interface Route {
    name?: string;
    path: string;
    type: RouteType;
    component: FunctionComponent;
}

RouteContainer

The enableRouting function expects a list of route objects. Boiler has a RouteContainer build in that makes creating these routes easier, you can use that or use your own.

RouteContainer example:

import { RouteContainer, RouteType } from 'react-ts-boiler';

const routeContainer = new RouteContainer();

routeContainer.add('/home', HomeComponent, 'home'); // path, component, name

// Here all routes in the callback will be prefixed with /audits
routeContainer.group('/audits', () => {
  routeContainer.add('/new', CreateAuditComponent, 'create-audit');
  routeContainer.add('/:id', DetailComponent, 'audit-detail');
}, RouteType.PRIVATE); // These will be private routes, meaning you have to be logged in

routeContainer.addType('/home', HomeComponent, 'home', RouteType.PRIVATE); // path, component, name, routetype

export default routeContainer.getRoutes(); // Returns array of route objects

Currently, there are two route types: PUBLIC & PRIVATE, this will be changed in the future

Route helper function

After enabling the routing you can use the route helper function to get the url of a route:

import { route } from 'react-ts-boiler';

route('home') => /home
route('create-audit') => /audits/new

You can also give a second parameter to the helper function to fill in path params. For example if you have a route named detail with path /audits/:auditId/calls/:callId, you can do this:

import { route } from 'react-ts-boiler';

route('detail') => /audits/:auditId/calls/:callId
route('detail', { auditId: 1, callId: 2 }) => /audits/1/calls/2
route('detail', [3, 1]) => /audits/3/calls/1

You can either give an object or an array. Note that when using an array the helper function will fill the params in order that you give them.

Router component

All the above only enables you to refer to routes by name, but Boiler also offers a component that handles the actual routing with the routes you've given to the enable function.

For more details see the Router component in the Components section.

Components

BoilerApp

The BoilerApp component is essentially the entryPoint of your application. This component loads all necessary context components, so you can use things like theming. This doesn't mean that you can't use Boiler without this component, but it does take care of setting things up for you.

If for whatever reason you do want to set up everything yourself, maybe for custom functionality, you can check each section like Auth to see what each context and module needs.

Optional

  • children: (any) HTML/React components or text

Usage example:

export const App: FunctionComponent = () => {
    return (
        <BoilerApp />
    );
};

Input & Select

Input

Required

  • value: (any) The current value
  • onChange: (InputChangedCallback), The onchange callback

Optional

  • id: (string) The id to be given when the onChange method is called
  • className: (string) A class for the input element
  • type: (string) 'text' | 'password', default is text
  • placeholder: (string) A placeholder for when the value is empty
  • rules: (ValidationRule[]) Validation rules
  • onFocus: (() => void) Called when the field Focus
  • onBlur: (() => void) Called when the field blurs

Select

Required

  • children: (any) A list of options (must be Option components)
  • onChange: (OnSelectChangeCallback) Called when an option is selected
  • placeholder: (string) Placeholder for when no option is selected

Optional

  • id: (string) The id to be given when the onChange method is called
  • selected: (number) The current selected option
  • className: (string) A class for the select element
  • initialIndex: (number) The initial selected option
  • initialValue:: (any) The initial value
  • required: (boolean) If the value is required

Option

Required

  • children: (any) What will be displayed;
  • value: (any) The value that will be passed if selected

Optional

  • onClick: ((value?: any) => void) The callback when clicked

useInput & useForm

To make the use of inputs and even multiple inputs easier, boiler offers to custom hooks:

useInput

Returns

  • (string) The current value of the input
  • (InputChangedCallback) A function to be passed in to the Input component
  • (FormClear) A function that clears the input value

useForm

Params

  • initialState: (custom type see example) The initial state of the form
  • rules: (object) Optional, rules that validate an input value

Returns

  • (Form) A form object that contains the following:
    • clear: (FormClear) A function that clears all the values
    • data: (custom type see example) the current form data
    • valid: (boolean) True if all the values are valid otherwise false
    • errors: (InputError[]) A list of errors if the form is not valid
  • (FormChangedCallback) An onChange callback that can be given to an Input or Select component

Examples with the useInput & useForm hooks

import { Input, InputChangedCallback, Select, Option, useForm, useInput } from 'react-ts-boiler';

interface UserForm {
    username: string;
    password: string;
    gender: string;
}

const UseFormComponent = () => {
    const [form, onFormChange] = useForm<UserForm>({ username: '', password: '', gender: '' });
    
    return (
        <div>
            <Input value={form.data.username} onChange={onFormChange} id={'username'} />
            <Input value={form.data.password} onChange={onFormChange} id={'password'} />
            
            <Select onChange={onFormChange} placeholder={'Gender'} id={'gender'}>
                <Option value={'male'}>Male</Option>
                <Option value={'female'}>Female</Option>
            </Select>

            <button onClick={form.clear}>Clear form</button>
        </div>
    );
};

const UseInputComponent = () => {
    const [username, onChange, clearUsername] = useInput();
    
    return (
        <div>
            <Input value={username} onChange={onChange} />
            
            <button onClick={clearUsername}>Clear username</button>
        </div>
    );
};

Note that in the UseFormComponent an id is given to the inputs and selects, this is necessary for the useForm hook to know which field it should update.

Button

Required

  • children: (any) The text to display
  • onClick: (OnButtonClickCallback) Fires when the button is clicked

Optional

  • onMouseEnter: (OnButtonMouseEnterCallback) Fires when the mouse enters the button (hover)
  • onMouseLeave: (OnButtonMouseLeaveCallback) Fires when the mouse leaves the button
  • className: (string) A class for the button element

Page

This component should be used to wrap a component that acts as a page.

Required

  • children: (any) The page contents

Optional

  • className: (string) class for the page element

RouteSwitcher

The RouteSwitcher component wraps the react-router-dom Switch component with an AnimatedPresence component for page transitions. You use it the same way as you would use the Switch component:

<RouteSwitcher>
    <Route ..../>
    <Route ..../>
    <Route ..../>
</RouteSwitcher>

PublicRoute

This route component can be viewed by all users.

The props extend the react-router-dom RouteProps.

Optional

  • isRestricted: (boolean) If true the user cannot view this route if authenticated.

PrivateRoute

This route component can only be viewed by authenticated users.

The props extend the react-router-dom RouteProps.

Router

The Router component uses the routes that are given to the useRouting function to render al the routes. It uses the RouteSwitcher component under the hood to allow page transitions.

If you didn't enable routing but still want to use this component, then you can give it routes directly via it's routes prop.

Based on the type of route it will either render a PrivateRoute of PublicRoute. If you want to use different route components you can do so by passing a RenderRouteCallback to the renderRoute prop. This callback will be executed for every route.

You can also add routes directly to the Router component if you deem it necessary.

Example

const RouterExample = () => {
    const renderRoute = (route: Route, key: number) => {
        if(someLogicIsTrue) {
            return <CustomRoute />
        }

        return <OtherCustomRoute />
    };

    return (
        <Router 
            routes={[]} // Optional
            renderRoute={renderRoute} // Optional
            
            // Direct routes if necessary
            <Route .../>
            <Route .../>
        </Router>
    );
};

Use without optional props:

const RouterExample = () => {
    return (
        <Router />
    );
};

Tabs

If you need to implement tabs in your application than this section is for you. It covers three components that are needed to create tabs.

TabContent

The TabContent component is where all the different content is gonna go. For example if you have a list of users and one of cars, you would use two TabContent components for each list.

Required

  • children: (any) The content of the component

Tab

The tab component is what will trigger a TabContent to hide or show. For each TabContent you will need a Tab and vice versa. The Tab component is clickable for the user. Its usage is very simple, but order matters. The first Tab component will trigger the first TabContent component to show, and the rest to hide. The same goes for the second Tab and so on.

Required

  • children: (any) The text or elements that you wanna show to the user to indicate a tab

Optional

  • className: (string) A class for the element
  • activeClassName: (string) A class that will be appended to the element's class if it's active
  • onClick: ((index: number) => void) Override for the onClick event

TabControl

The TabControl component is the parent component for Tabs and TabContents and handles which TabContent is currently active. You MUST put your Tab and TabContent components in this component otherwise it will not work.

Required

  • children: (any) Any children

Optional

  • tabType: (FunctionComponent) Normally, the TabControl will look for any clickable components of type Tab, but if you want a custom one, this override is necessary.

Tab Example

import { TabControl, TabContent, Tab } from 'react-ts-boiler';

const TabExample: FunctionComponent = () => {
    return (
        <TabControl>
            <div class="tabs-header">
                <Tab>Users</Tab>
                <Tab>Cars</Tab>
            </div>
            
            <TabContent>
                <UserList />
            </TabContent>

            <TabContent>
                <CarList />
            </TabContent>
        </TabControl>
    );
};

In this example the TabContent components are direct children of TabControl, but this is not necessary. The TabControl component will recursively look through all children for the TabContent & Tab components.

Networking

Enabling networking & Http filters

This step is optional, but necessary if you want to add http filters to modify the request for things like adding an Authentication token. For clarification, Boiler uses axios under the hood and each time you make a request to an API with a repository (see the Repository section), axios creates a request object with information like the headers, data, url etc. With a http filter you can customize this object by modifying it.

Enable function

enableNetworking({ 
    baseUrl: 'http://', // The base url that repositories will use to fetch resources  
    httpFilters: [] // An array of http filters to modify request objects with
}) 

Http filters

A http filter is a class with one method that is executed for each request object and allows you to modify it if necessary.

interface HttpFilter {
    filter(request: AxiosRequestConfig, next: NextFilter): AxiosRequestConfig;
}

For example a filter that adds a bearer token to each request could look like this:

class AuthHttpFilter implements HttpFilter {

    public filter(request: AxiosRequestConfig, next: NextFilter) {
        if(isLoggedIn) {
            request.headers.Authorization = 'Bearer {sometoken}';
        }
        
        return next(request);
    }
}

The next parameter is a callback that calls the next Http filter, if for some reason you don't want it modified further you can just return the request object instead of returning next(request);

Repositories

Repositories are the Boiler way for making requests to an API. A Repository is a class that extends the Boiler Repository or ModelRepository class and contains methods that send requests to the API.

Repository

The Repository class is for making requests where you don't need the standard CRUD operations for a model.

The Repository class contains the following methods:

abstract class Repository {

    /**
     * Creates a new repository
     *
     * @param {string} baseUrl - The baseUrl that the repository will use to retrieve the resource
     */
    protected constructor(baseUrl: string);

    /**
     * Sets the abortController
     *
     * @param abortController
     */
    public setAbortController(abortController: AbortController);

    /**
     * Executes an HTTP request
     *
     * @param {string} url - The url to make the request to
     *
     * @param {RequestMethod} method - The request method
     *
     * @param {RequestDataType} data - Optional, data to be send with the request
     *
     * @return {Promise}
     */
    protected request<T extends unknown>(url: string, method: RequestMethod, data?: RequestDataType): Request<T>;

    /**
     * Executes an HTTP request with axios request options
     *
     * @param {string} url - The url to make the request to
     *
     * @param {RequestMethod} method - The request method
     *
     * @param {AxiosRequestConfig} config - Optional, data to be send with the request
     *
     * @return {Promise}
     */
    protected requestWithOptions<T extends unknown>(url: string, method: RequestMethod, config: AxiosRequestConfig): Request<T>;

    /**
     * Creates a url with the base url of the repo at front
     *
     * @param {string} url - A string to append to the base url
     *
     * @return {string}
     */
    protected url(url: string = ''): string;
}

Example

interface Car {
    name: string;
}

class MyRepository extends Repository {
    public  constructor() {
        super('/the-base-url');
    }
    
    public getCars() {
        this.request<Car[]>(this.url(), RequestMethod.GET).send(); // Sends a GET request to /the-base-url
    }
    
    public getOtherCars() {
        this.request<Car[]>('/custom-url', RequestMethod.GET).send(); // Sends a GET request to /custom-url
    }
}

Note the <Car[]> after the this.request. This is called a generic, if you don't know what that is, you can learn about it here: https://www.typescriptlang.org/docs/handbook/2/generics.html. By using a generic you can specify what return type you expect form the API and gives you type completion in your editor.

You can send post data as an optional third argument:

public postRequest() {
    this.request<Car>('/create-car', RequestMethod.POST, { brand: 'Mercedes' }).send(); // Sends a POST request
}

Since the repository uses axios under the hood it is also possible to set axios request options for a request:

public requestWithAxiosOptions() {
    this.requestWithOptions<Car>('/create-car', RequestMethod.POST, { 
        params: { orderBy: 'name' },
        data: { brand: 'Mercedes' }
    }).send(); // Sends a POST request
}

ModelRepository

The ModalRepository class is built on top of the previous listed Repository class and contains the default CRUD operations for you to use out of the box.

The available methods:

abstract class ModelRepository<T extends Model> extends Repository {

    /**
     * Creates a new repository
     *
     * @param {string} baseUrl - The baseUrl that the repository will use to retrieve the resource
     *
     */
    protected constructor(baseUrl: string);

    /**
     * Gets a list of the resource
     *
     * @return {Promise}
     */
    public async all(): Promise<T[]>;

    /**
     * Gets a single resource
     *
     * @param {number | number[]} id - The id of the resource
     */
    public async find(id: number | number[]): Promise<T | T[]>;

    /**
     * Creates a new resource or updates an existing one
     *
     * @param model - The model
     *
     * @return {Promise}
     */
    public save(model: T): Promise<T>;

    /**
     * Saves or creates new resources
     *
     * @param models - A list of modals
     *
     * @param {boolean} ordered - Flag to indicate of the objects should be ordered by their id
     */
    public async saveAll(models: T[], ordered: boolean = false): Promise<T[]>;

    /**
     * Destroys a single resource
     *
     * @param model
     *
     * @return {Promise}
     */
    public destroy(model: T);

    /**
     * Destroys a list of resources
     *
     * @param models - The list of resources
     *
     * @return {Promise}
     */
    public async destroyAll(models: T[]);

    /**
     * Creates a resource url with an id in it
     *
     * @param {string} url
     *
     * @param model
     *
     * @return {string}
     */
    protected eUrl(model: T, url: string): string;
}

The ModalRepository expects an interface when extending it (see example below) that extends the Model interface. The Modal interface contains fields that are shared across all models:

interface Model {
    id?: number;
    created_at?: string;
    updated_at?: string;
}

You can import the model interface from Boiler.

Example

import { Model, ModalRepository } from 'react-ts-boiler';

interface User extends Model {
    username: string;
    age: number;
}

class UserRepository extends ModelRepository<User> {
    public  constructor() {
        super('/users');
    }
    
    public customRequest(user: User) { // user with id 1
        this.request<User>(this.eUrl('/custom'), RequestMethod.GET).send(); // eUrl takes the id from the given model and uses it in te url: /users/1/custom
    }
}

// Usage
const UsersPage: FunctionComponent = () => {
    const [users, setUsers] = useState<User>([]);

    const userRepo = new UserRepository();

    useEffect(() => {
        userRepo.all().then(users => setUsers(users)); // Promise then
    }, []);
    
    const addUser = async () => {
        await userRepo.save({ username: 'Kale', age: 20 }); // async/await
    };

    const updateUser = async (user: User) => {
        await userRepo.save(user); // The repo will regonize that this model already exists and updates it instead of creating a new one
    };   

    return (
        <UserList users={users} />
    );
};

Pagination & Raw responses

You probably noticed that when making a request a send() method is called at the end. This is because this.request and this.requestWithOptions both return a Boiler Request object that gives you some extra options as how you want to receive the data (if there is data to retrieve) from the response.

Raw responses

A Laravel API response will most of the time looks like this:

{
  "data": [ // List of objects
    { "name": "Kale" }
  ]
}

OR

{
  "data": { // Single object
    "name": "Kale"
  }
}

This is because when using pagination laravel needs to send some additional data like the current page and the total amount of pages. So it "wraps" the actual data in a data field. But it would be a hassle to make a request and use the .data field every single time, it would be much easier if you got its contents directly. And that's exactly what send does. It will extract the data for you from the data field. This method takes the Generic (if there is one) from this.request and expects the data inside the data field to be of that type and then returns that data.

But if for some reason you know you will get a different response format or if you just want the whole response, you can use the sendRaw() method. This method takes the Generic (again if there is one) from this.request and expects the whole response to be of that type. It will return everything and doesn't return a specific part of the response.

interface User {
    username: string;
    age: number;
}

interface RegularResponse {
    data: User[];
}

interface CustomResponse {
    message: string;
}

// In Repo
public normalRequest() {
    this.request<User[]>(this.url(), RequestMethod.GET).send(); // Returns the users from the 'data' field
}

public requestWithRawResponse() {
    this.request<RegularResponse>(this.url(), RequestMethod.GET).sendRaw(); // Returns the whole response
}

public requestWithAnotherRawResponse() {
    this.request<CustomResponse>('/custom-response', RequestMethod.GET).sendRaw(); // Returns the whole response
}

Pagination

Boiler has a built in system for pagination that makes it easy to keep track of things like the current page, data and retrieving the next amount of models etc.

To use this system you need two things, the usePagination hook and calling .paginate() at the end of a request (instead of send or sendRaw).

The usePagination hook expects a generic type and returns a paginator, and a setPaginator function and works like the setState hook.

const [users, setUsers] = usePagination<User>();

The paginator has the following fields and methods:

{
    data: T[], // The current data, in our case a list of users (T is our generic type)
    next: (concat?: boolean) => Promise<void>, // A method for retrieving the next set of data
    previous: (concat?: boolean) => Promise<void>, // A method for retrieving the previous set of data
    hasNext: boolean, // Whether there are any pages left
    hasPrevious: boolean, // Wheter you can still go back to a previous page
    loading: boolean // True when the paginator is retrieving (new) data
};

Then in your repository you use the paginate method when making a request:

public paginatedRequest() {
    this.request<User[]>(this.url(), RequestMethod.GET).paginate();
}

The concat parameter is optional and simply means whether you want to append the new data to the data that the paginator already has (for an increasing list). Default is false.

Example

import {usePagination, Model, ModelRepository, RequestMethod } from 'react-ts-boiler';
 
interface User extends Model {
    username: string;
    age: number;
}

class UserRepo extends ModelRepository<User> {
    public constructor() {
        super('/users');
    }

    public allPaginated() {
        return this.request(this.url(), RequestMethod.GET).paginate();
    }
}

// Usage
const UsersPage: FunctionComponent = () => {
    const [users, setUsers] = usePagination<User>();

    const userRepo = new UserRepository();

    useEffect(() => {
        userRepo.allPaginated().then(users => setUsers(users)); // Promise then
    }, []);
    
    const next = async () => {
        await users.next(); // async/await
    };

    const previous = () => {
        await users.previous(); // async/await
    };

    return (
        <React.Fragment>
            { users.loading &&
                <p>Loading new users...</p>
            }

            <UserList 
                users={users.data} 
                hasNext={users.hasNext} 
                hasPrevious={users.hasPrevious} />
        </React.Fragment>
    );
};

Using repositories in components

Sometimes React unmounts a component before a request is completed when for example a user quickly clicks on another page. The callback however is still going to be executed after the request is completed and will try to set the state of an unmounted component, which will raise an error. To prevent this we need to tell the repositories that we want to abort the requests when the component unmounts. For this Boiler uses the JS AbortController: https://developer.mozilla.org/en-US/docs/Web/API/AbortController.

The Repository class has a setAbortController method that you can use to set an AbortController. Then you can call the abort method in a useEffect. Boiler has a hook that does this for you out of the box:

const [userRepo, carRepo] = useRepository([
    new UserRepo(),
    new CarRepo()
]);

Boiler will memoize the repo instances and add an AbortController instance so that when the component unmounts the callback won't be executed.

Localization

The localization module allows for easy translations within your application.

Enable function

enableLocalization({
    languages: any // Object with translations
});

The languages object needs to have the following structure for any language that you want to support:

const languages = {
    nl: {
        max_error: 'Teveel!'
    },
    en: {
        max_error: 'Too much!'
    }
};

The best to probably do this is to create separate files for each language and then add those exported objects.

useLocalization hook

After enabling localization you can use the useLocalization hook to use localized strings and to switch the current locale:

const Component: FunctionComponent = () => {
    const { __, setLocale } = useLocalization();
    
    return (
        <div>
            <p>{ __('max_error') }</p>
            <Button onClick={() => setLocale('nl')}>Nederlands</Button>
            <Button onClick={() => setLocale('en')}>English</Button>
        </div>
    );
};
Nested translations

To retrieve a nested translation you can use a dot in your string:

const languages = {
    nl: {
        errors: {
            empty: 'Veld mag niet leeg zijn!'
        }
    },
    en: {
        errors: {
            empty: 'Field cannot be empty!'
        }
    }
};

const translation = __('errors.empty');
Placeholders

You can also use placeholders in your translations:

const languages = {
    nl: {
        max_error: '{0} is teveel!',
        field_between: 'Veld {0} moet een waarde tussen {1} en {2} hebben'
    },
    en: {
        max_error: '{0} is too much!',
        field_between: 'Field {0} must have a value between {1} and {2}'
    }
};

// Translations
__('max_error', 12);
__('field_between', 'Name', 1, 2);

Themes

Boiler provides a theming system that can be used in combination with BEM naming conventions.

Enabling theming

enableTheming({
    themes: Theme[] // A list of Themes
});

The enableTheming function expects an array of themes. A theme object looks as follows:

interface Theme {
    name: string;
    modifier: string;
}

// Examples
const lightTheme: Theme = {
    name: 'LightTheme',
    modifier: 'light'
};

const darkTheme: Theme = {
    name: 'DarkTheme',
    modifier: 'dark'
};

useTheme hook & Theming with BEM

After enabling Theming you can use the useTheme hook to get the current theme and to set a new Theme. You can then use the modifier field to append to a BEM CSS class to indicate a different styling:

import { useTheme } from 'react-ts-boiler';

const Component: FunctionComponent = () => {
    const { theme, setTheme } = useTheme();
        
    return (
        <div className={`app app--${theme.modifier}`}>
            <Button onClick={() => setTheme('LightTheme')}>Set light</Button>
            <Button onClick={() => setTheme('DarkTheme')}>Set dark</Button>
        </div>
    );
};

Utils

Audio

AudioLoader

With the AudioLoader you can load an audio file into an AudioBuffer and generate audio bar data.

import { AudioLoader } from 'react-ts-boiler';

const audioLoader = new AudioLoader();

const buffer = audioLoader.loadAudioBuffer('file-path'); // Return an AudioBuffer

// First parameter is the file path
// Second parameter is the amount of chunks that the bar data should be divided into, in this case
// The function returns an array containing 70 numbers, each number represents the volume of the audio at that point.
const barData = audioLoader.generateAudioBarData('file-path', 70); // Returns an array with 

Children mapper

The children mapper is a recursive function that loops through a component's children and executes a callback on a desired child.

Params

  • childen: (any[]) The component's children
  • isDesiredElement: ((child: any) => boolean) Callback to determine if the callback should be executed on the child
  • callback: ((child: any) => ReactElement) A callback to modify the child, must return a (new) React element

Example

import { mapChildren } from 'react-ts-boiler';

const ChildToFind: FunctionComponent = ({ found }) => {
    return (
        <p>
            { found ? 'Found me!' : 'Did not find me!' }
        </p>
    );
};

const Page: FunctionComponent = ({ children }) => {
    const isDesiredChild = (child) => child.type === ChildToFind;

    const mappedChildren = mapChildren(children, isDesiredChild, (child) => {
        return React.cloneElement(child, { found: true });
    });
    
    return (
        <div>{ mappedChilden }</div>
    );
};

const UsersPage: FunctionComponent = () => {
    return (
        <Page>
            <div>
                <div>
                    <ChildToFind />
                </div>
        
                <p>Home</p>
            </div>
    
            <p>Text</p>
        </Page>
    );
};

The UsersPage component will contain the text 'Found me!' after rendering, because the mapChildren function executed the callback on the ChildToFind component that sets the found prop to true.

ClassBuilder

The ClassBuilder is useful for creating class names for elements with conditional classes. It contains the following methods:

class ClassBuilder {
    /**
     * Creates a new ClassBuilder instance
     *
     * @param className
     *
     * @return {ClassBuilder}
     */
    public static createClass(className: string);
    
    /**
     * Adds a class to the class builder
     *
     * @param {string} className
     *
     * @return {ClassBuilder}
     */
    public add(className: string);
    
    /**
     * Adds a class to the class builder when the given expression is true
     *
     * @param {string} className
     *
     * @param {boolean} expression
     *
     * @return {ClassBuilder}
     */
    public addIf(className: any, expression: any);

    /**
     * Adds the first given class to the class builder when the given expression is true otherwise it adds the second one
     *
     * @param {string} className1
     *
     * @param {string} className2
     *
     * @param {boolean} expression
     *
     * @return {ClassBuilder}
     */
    public addIfElse(className1: string, className2: string, expression: any);
    
    /**
     * Returns the build class
     *
     * @return {string}
     */
    public build(): string;
}

Example

import { ClassBuilder } from 'react-ts-boiler';

const UsersPage: FunctionComponent = ({ open, active }) => {

    const className = ClassBuilder.createClass('users-page')
        .add('background-white')
        .add('padding-10')
        .addIf('users-page--active', active)
        .addIfElse('users-page--open', 'users-page--closed', open)
        .build();

    return (
        <div className={className}>
            <p>Users page</p>
        </div>
    );
};

Object helpers

getRecursiveValue

Returns a nested value from an object.

Params

  • object: (object) The object
  • key: (string) The key to retrieve, retrieve a nested value by adding a '.' as a seperator

Example

import { getRecursiveValue } from 'react-ts-boiler';

const object = {
    user: {
        room: {
            door: {
                brand: 'blussen'
            }
        }
    }
};

const doorBrand = getRecursiveValue(object, 'user.room.door.brand'); // 'blussen'
const room = getRecursiveValue(object, 'user.room');

Env

getEnv

Returns a .env.local value by key.

import { getEnv } from 'react-ts-boiler';

const endpoint = getEnv('REACT_APP_API_ENDPOINT');

getApiEndpoint

Returns the set API_ENDPOINT from .env.local

import { getApiEndpoint } from 'react-ts-boiler';

const endpoint = getApiEndpoint();

Types

1.0.39

8 months ago

1.0.44

6 months ago

1.0.43

6 months ago

1.0.42

6 months ago

1.0.41

6 months ago

1.0.47

6 months ago

1.0.46

6 months ago

1.0.45

6 months ago

1.0.49

6 months ago

1.0.51

6 months ago

1.0.55

6 months ago

1.0.54

6 months ago

1.0.53

6 months ago

1.0.52

6 months ago

1.0.57

6 months ago

1.0.56

6 months ago

1.0.38

1 year ago

1.0.37

1 year ago

1.0.36

1 year ago

1.0.29

1 year ago

1.0.28

1 year ago

1.0.33

1 year ago

1.0.32

1 year ago

1.0.31

1 year ago

1.0.30

1 year ago

1.0.35

1 year ago

1.0.34

1 year ago

1.0.27

1 year ago

1.0.26

1 year ago

1.0.25

1 year ago

1.0.24

1 year ago

1.0.23

1 year ago

1.0.22

1 year ago

1.0.21

1 year ago

1.0.20

1 year ago

1.0.19

1 year ago

1.0.18

1 year ago

1.0.17

1 year ago

1.0.16

2 years ago

1.0.15

2 years ago

1.0.14

2 years ago

1.0.13

2 years ago

1.0.12

2 years ago

1.0.11

2 years ago

1.0.10

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago