@mongez/react-utils
v1.3.12
Published
Some Good utilities for React
Downloads
76
Maintainers
Readme
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()
orresponse.text()
, the data will be returned in the response object asdata
.
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 thepreload
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;
});