Not epic but handy helpers for conditional React rendering. Functional utilities to quickly implement recurring rendering patters in a readable way.
Jump directly to the epic.
npm install react epic-react
import React from 'react'
import { when } from 'epic-react'
export const DaytimeTheme = (time: number) =>
when(
time > 6 && time < 18,
() => <Daylight />,
() => <Nighttime />
)
import { not, when, epic, until, list, random } from 'epic-react'
If the provided condition is true nothing will be rendered.
export const CartButton = (stock: number) =>
not(stock === 0, <Button onClick={Store.addToCart}>Buy</Button>)
If the condition is true render the component and if it's false render the fallback if one is provided.
export const DaytimeTheme = (time: number) =>
when(
time > 6 && time < 18,
() => <Daylight />,
() => <Nighttime /> // Optional
)
Usually there is more than an if else required for rendering. In this case an epic will help:
// Usage as an object: specifying conditions along with components.
epic
.loading(() => <p>Loading...</p>, false)
.error(() => <p>Error...</p>, false)
.fallback(() => <p>Fallback...</p>, false)
.done(() => <p>Epic done</p>)
// Usage as a function: specifying conditions first.
epic({
loading: false,
error: false,
fallback: false,
})
.loading(() => <p>Loading...</p>)
.error(() => <p>Error...</p>)
.fallback(() => <p>Fallback...</p>)
.done(() => <p>Epic done</p>)
The second option is especially handy if you already have an object with the conditions available or can create a matching state.
Asynchronous rendering depending on the state of a Promise.
until<string, null>(
new Promise<string>((done) => setTimeout(() => done('resolved!'), 3000)),
(result) => <p>{result}</p>,
<p>loading...</p>
)
If the Promise is rejected an optional error handler will be rendered.
until<string, string>(
new Promise<string>((done, fail) =>
setTimeout(() => fail('rejected...'), 3000)
),
result => (
<p>{result}</p>
),
<p>loading...</p>,
error => (
<p>{error}</p>
)
)}
const ListElement = ({ value }: { value: number }) => <span>{value}</span>
This epic makes rendering lists quicker.
list<{ value: number }>([{ value: 1 }, { value: 2 }, { value: 3 }], ListElement)
As the third parameter you can pass an element which will be rendered in case list is empty.
list<{ value: number }>([], ListElement, <span>It's an empty list ;)</span>)
An optional separator element can be inserted in between elements, similar to the join() function for regular Arrays.
list<{ value: number }>(
[{ value: 1 }, { value: 2 }, { value: 3 }],
ListElement,
<span>List is empty...</span>
<span>,</span>
);
Randomly picks a component from the list of arguments.
random(
() => <p>first</p>,
() => <p>second</p>
)
Simply writing all the logic yourself works just fine. These epics however have been created due to very similar parts occurring over and over again.
// Vanilla JS
export const AsyncFetchedData = (data) => {
if (data.loading) {
return <Loading />
}
if (data.error) {
return <Error />
}
return <Data data={data.result} />
}
// with an epic
export const AsyncFetchedData = (data) => epic
.loading(() => <Loading />, data.loading)
.error(() => <Error>, data.error)
.done(() => <Data data={data.result} />);
import { Suspense } from 'react'
const LazyComponent = React.lazy(() => import('./ProfilePage'))
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
)
import { until } from 'epic-react'
return until(import('./lazy'), (result) => <result.default />, <p>Loading...</p>)
Suspense (HOC): 4 Lines of Code
until (react-epic): 1 Line of Code 🤓
Shortcuts to do something when a certain key is pressed. To be used with onKeyDown
, onKeyPress
or onKeyUp
.
import { onEnter, onEscape } from 'epic-react'
<input onKeyUp={onEnter((event) => submit())} />
<input onKeyDown={onEscape((event) => close())} />
<input
onKeyPress={(event) => {
onEnter(() => submit())(event)
onEscape((event) => close(event))(event)
}}
/>