@mongez/react-utils
TypeScript icon, indicating that this package has built-in type declarations

1.3.12 • Public • Published

React Utils

Some good utilities to use in your React Js/ React Native Apps

Installation

yarn add @mongez/react-utils

Or

npm i @mongez/react-utils

Usage

Map Elements

Loop over the given array and render the given component for each element in the array.

mapElements( data[], Component: React.ElementType, as: string | ((item, index: number) => object) = "item"): React.ReactNode

Before:

const posts = [
  {
    id: 1,
    title: 'Post Title 1',
    image: 'image-path.jpg'
  },
  {
    id: 2,
    title: 'Post Title 2',
    image: 'image-path-2.jpg'
  },
  {
    id: 3,
    title: 'Post Title 3',
    image: 'image-path-3.jpg'
  },
];

function LatestPosts() {
  return (
    <>
      <h3>Latest Posts</h3>
      {posts.map((post) => (
        <PostCard key={post.id} post={post} />
      ))}
    </PostCard>
  )
}

function PostCard({ post }) {
  return (
    <div>
        <div className="title">{post.title}</div>
        <img src={post.image} />
    </div>
  )
}

After:

import { mapElements } from '@mongez/react-utils';

const posts = [
  {
    id: 1,
    title: 'Post Title 1',
    image: 'image-path.jpg'
  },
  {
    id: 2,
    title: 'Post Title 2',
    image: 'image-path-2.jpg'
  },
  {
    id: 3,
    title: 'Post Title 3',
    image: 'image-path-3.jpg'
  },
];

function LatestPosts() {
  return (
    <>
      <h3>Latest Posts</h3>
      {mapElements(posts, PostCard, 'post')}
    </PostCard>
  )
}

function PostCard({ post }) {
  return (
    <div>
        <div className="title">{post.title}</div>
        <img src={post.image} />
    </div>
  )
}

You can also decide what to be passed to the component

import { mapElements } from '@mongez/react-utils';

const posts = [
  {
    id: 1,
    title: 'Post Title 1',
    image: 'image-path.jpg'
  },
  {
    id: 2,
    title: 'Post Title 2',
    image: 'image-path-2.jpg'
  },
  {
    id: 3,
    title: 'Post Title 3',
    image: 'image-path-3.jpg'
  },
];

function LatestPosts() {
  return (
    <>
      <h3>Latest Posts</h3>
      {mapElements(posts, PostCard, post => ({
        title: post.title,
        image: post.image
      }))}
    </PostCard>
  )
}

function PostCard({ title, image }) {
  return (
    <div>
        <div className="title">{title}</div>
        <img src={image} />
    </div>
  )
}

Of course this function will add a unique key automatically so you don't have to bother yourself about it.

Unique Keys

Added in V1.1.0

Generating keys for the elements in the array is a good practice, but it's not always easy to do it specially if you're dealing with inputs inside the array, luckily, uniqueKeys utility will generate a unique id for each object.

import { uniqueKeys } from '@mongez/react-utils';

export default function TableInputs({data}) {
  const [rows, setRows] = useState(uniqueKeys(data));

  const deleteRow = (index: number) => {
    setRows(rows.filter((row, i: number) => i !== index));
  }

  const addRow = () => {
    setRows(uniqueKeys([...rows, {
      name: ''
    }]));
  }

  return (
    <table>
      <tbody>
        {rows.map((row, index) => (
          <tr key={row.uniqueId}>
          <td>
            <input name="name[]" placeholder="Name" />
          </td>
          <td>
          </td>
            <button type="button" onClick={deleteRow}>Delete</button>
          </tr>
        ))}
        <tr>
          <td colSpan={2}>
            <button type="button" onClick={addRow}>Add</button>
          </td>
        </tr>
      </tbody>
    </table>
  )

The uniqueId key is generated automatically and it's unique for each object in the array, so you can use it as the key for the element, if the uniqueId already exists, then it will be used instead of generating new one.

To see the difference between using uniqueId and the index, use the index instead of the uniqueId as key for the tr tag then try to delete the first row, you'll notice that the second row will be deleted instead of the first one.

If the array is a list of non-objects, then each element will be wrapped with an object with the key value and the value will be the element itself.

import { uniqueKeys } from '@mongez/react-utils';

export default function TableInputs({data}) {
  const [rows, setRows] = useState(uniqueKeys([1, 2, 3, 4]));

  const deleteRow = (index: number) => {
    setRows(rows.filter((row, i: number) => i !== index));
  }

  return (
    <table>
      <tbody>
        {rows.map((row, index) => (
          <tr key={row.uniqueId}>
            <input name="name[]" placeholder="Name" value={row.value} />

            <button type="button" onClick={deleteRow}>Delete</button>
          </tr>
        ))}
      </tbody>
    </table>
  )

You can also assign a different key name instead of uniqueId by passing it as the second argument.

import { uniqueKeys } from '@mongez/react-utils';

export default function TableInputs({data}) {
  const [rows, setRows] = useState(uniqueKeys([1, 2, 3, 4], 'id'));

  const deleteRow = (index: number) => {
    setRows(rows.filter((row, i: number) => i !== index));
  }

  return (
    <table>
      <tbody>
        {rows.map((row, index) => (
          <tr key={row.id}>
            <input name="name[]" placeholder="Name" value={row.value} />

            <button type="button" onClick={deleteRow}>Delete</button>
          </tr>
        ))}
      </tbody>
    </table>
  )

Preload

Sometimes you need to preload some data before rendering the component, for example, you want to fetch the user data before rendering the profile page, or you want to fetch the list of categories before rendering the posts page, this is where preload utility comes in handy.

preload( Component: React.ComponentType<any>, promise: Promise<any>, configurations: string = "data"): React.ReactNode

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, () => fetch('/api/user'));

So we passed to the preloader the component that will be rendered after the data is fetched, and the promise that will be resolved with the data, and the name of the prop that will be passed to the component.

Now the loadingError component will be called if the request is being loaded or any of the requests failed.

If the sent request is a fetch api the preload will handle the response automatically so you don't have to await response.json() or response.text(), the data will be returned in the response object as data.

You can define the LoadingError component by passing it to the preload function as the third argument.

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, fetch('/api/user'), {
  loadingErrorComponent: ({isLoading, error}) => <div>Something went wrong</div>
});

You can define the default loadingErrorComponent using setPreloadConfiguration function

import { preload, setPreloadConfiguration } from '@mongez/react-utils';

setPreloadConfiguration({
  loadingErrorComponent: ({isLoading, error}) => <div>Something went wrong</div>
});

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, fetch('/api/user'));

You will receive isLoading flag to tell you if the request is being loaded, and error object if the request failed.

If loadingErrorComponent is not defined, then null will be returned during loading or on error occurs

Multiple Requests

You can also send multiple requests to be loaded before rendering the component.

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  const [userResponse, user2Response] = response;

  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {userResponse.data.name}</div>
      <div>Email: {user2Response.data.email}</div>
    </div>
  )
}

export default preload(Profile, [
  () => fetch('/api/user'),
  () => fetch('/api/user/2'),
]);

Requests will be loaded

This will load both requests simultaneously before rendering the component, and the data will be passed as an array to the component.

Dependant Requests

Added in V1.1.0

Sometimes there are requests that depend on each other, for example, you want to fetch the user data first, then fetch the user's posts, this is where pipeline comes in handy.

import { preload, pipeline } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, pipeline([
  (props) => fetch('/api/user'),
  (props, userResponse, responsesList) => fetch('/api/posts?userId=' + userdata.id),
]));

This will load the first request, then the second request, and then the third request, each request will receive the component props besides the previous responses, by receiving responses or if you want only to receive the previous response of the current request, you may receive response property, finally the data will be passed as an array from all requests to the component.

Response Cache

Now you can cache the response of a request, so if the request is already cached, it will be returned from the cache instead of sending the request again.

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, () => fetch('/api/user'), {
  cache: {
    key: 'user',
    expiresAfter: 60 * 5, // 5 minutes
  }
});

Now when the user hits this page, the request will be sent, and the response will be cached, so if the user hits this page again, the request will not be sent, and the response will be returned from the cache.

You can also customize the cache key as it can receive the component props to determine which key to use.

import { preload } from '@mongez/react-utils';

function ViewUser({ data }) {
  const user = data.user;

  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {user.name}</div>
      <div>Email: {user.email}</div>
    </div>
  )
}

export default preload(ViewUser, (props) => fetch('/api/user/' + props.id), {
  cache: {
    key: ( {params} ) => 'user-' + params.id,
    expiresAfter: 60 * 5, // 5 minutes
  }
});

You can also set the default expire time using setPreloadConfiguration function.

import { preload, setPreloadConfiguration } from '@mongez/react-utils';

setPreloadConfiguration({
  cache: {
    expiresAfter: 60 * 5, // 5 minutes
  }
});

Listen for success or error

You can listen for success or error of the request by passing the onSuccess or onError callbacks to the preload function.

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, () => fetch('/api/user'), {
  onSuccess: (response) => {
    // do something
  },
  onError: (error) => {
    // do something
  }
});

If the request (2nd argument) is an array of requests, then the onSuccess will receive an array of responses, and the onError will receive an array of errors.

These can defined in the setPreloadConfiguration function as well.

If defined in both preload function and setPreloadConfiguration function, the one defined in the preload function will be used.

Caching

Added in V1.3.0

You can cache the request response by passing the cache option to the preload function, which defaults to true.

import { preload } from '@mongez/react-utils';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, () => fetch('/api/user'), {
  cache: true
});

Now whenever the component is rendered, the request will be loaded only once, and the response will be cached, and the next time the component is rendered, the cached response will be used.

This will enhance the performance significantly, and it will be useful when you have a component that is rendered multiple times, for example, a list of items, and each item has a request to be loaded.

The cache key is the signature of the request call, so if any parameter is changed it will generate a new cache and send a new request.

You can also set default cache option using setPreloadConfiguration function.

import { preload, setPreloadConfiguration } from '@mongez/react-utils';

setPreloadConfiguration({
  cache: true
});

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default preload(Profile, () => fetch('/api/user'));

Guarded

Added in V1.3.0

The guarded function is used to guard the component from being rendered if the given condition is not met.

import { guarded } from '@mongez/react-utils';
import user from 'somewhere';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default guarded(Profile, () => user.isLoggedIn());

If the output of the callback is true, then the component will be rendered, otherwise the returned value of the callback will be returned instead.

import { guarded } from '@mongez/react-utils';
import user from 'somewhere';

function Profile({ data }) {
  return (
    <div>
      <h1>Profile</h1>
      <div>Name: {data.name}</div>
      <div>Email: {data.email}</div>
    </div>
  )
}

export default guarded(Profile, () => {
  if (!user.isLoggedIn()) {
    return <Redirect to="/login" />
  }

  return true;
});

/@mongez/react-utils/

    Package Sidebar

    Install

    npm i @mongez/react-utils

    Weekly Downloads

    7

    Version

    1.3.12

    License

    MIT

    Unpacked Size

    37.3 kB

    Total Files

    39

    Last publish

    Collaborators

    • hassanzohdy