Text splitting components for React
npm install @squib/react-split
or
yarn add @squib/react-split
import { Split, SplitCharacters, SplitWords } from "@squib/react-split";
or:
import Split from "@squib/react-split/Split";
import SplitCharacters from "@squib/react-split/SplitCharacters";
import SplitWords from "@squib/react-split/SplitWords";
The <Split>
component is a low level component that gives you full control
over how to render the individual substrings.
The component uses the function-as-child-component render prop pattern, which
means that instead of passing React elements as children, <Split>
expects
its only child to be a function with the signature:
interface Substring {
substring: string;
key: string;
}
(substrings: Substring[]) => ReactElement;
If you are not familiar with this pattern, the following sections will give some examples of how it can be used.
For more information on the render prop pattern itself, see the React documentation on the topic.
By default, <Split>
will split the input string using an empty string.
<Split string="foo">
{substrings =>
substrings.map(({ substring, key }) => <span key={key}>{substring}</span>)
}
</Split>
// <span>f</span>
// <span>o</span>
// <span>o</span>
Note that unlike String.prototype.split
, <Split>
will preserve surrogate
pairs:
"⚛️✂".split("");️
// -> [ '⚛', '️', '✂', '️' ] ☹️
<Split string="⚛️✂">
{substrings =>
substrings.map(({ substring, key }) => <span key={key}>{substring}</span>)
}
</Split>
// <span>⚛️</span>
// <span>✂️</span>
You can specify a custom separator using the separator
prop. Like
String.prototype.separator
, you can use either a string or RegExp
.
<Split string="Howdy, friend!" separator=" ">
{substrings =>
substrings.map(({ substring, key }) => <span key={key}>{substring}</span>)
}
</Split>
// <span>Howdy,</span>
// <span>friend!</span>
<Split string="Howdy, friend!" separator={/\s+/}>
{substrings =>
substrings.map(({ substring, key }) => <span key={key}>{substring}</span>)
}
</Split>
// <span>Howdy,</span>
// <span>friend!</span>
One of the benefits of using the render prop pattern is that it makes it easy to animate the individual substrings.
For example, using react-css-stagger
, you can easily make the
substrings have a staggered fade-in animation:
<Split string="Howdy!">
{substrings => (
<Stagger transition="fadeIn" delay={150}>
{substrings.map(({ substring, key }) => (
<span key={key}>{substring}</span>
))}
</Stagger>
)}
</Split>
.fadeIn-enter {
opacity: 0;
transition: 0.3s ease-in-out all;
}
.fadeIn-enter.fadeIn-enter-active {
opacity: 1;
}
The <SplitCharacters>
component takes a string as children
, and splits them
into separate elements.
<SplitCharacters>foo</SplitCharacters>
// <span>f</span>
// <span>o</span>
// <span>o</span>
<SplitCharacters>
uses <Split>
under the hood, so it handles surrogate pairs
correctly:
<SplitCharacters>⚛️✂️</SplitCharacters>
// <span>⚛️</span>
// <span>✂️</span>
You can customize the wrapper element using the as
prop:
<SplitCharacters as="p">foo</SplitCharacters>
// <p>f</p>
// <p>o</p>
// <p>o</p>
All other props will be forwarded to each wrapped element:
<SplitCharacters as="p" className="fancy">
foo
</SplitCharacters>
// <p class="fancy">f</p>
// <p class="fancy">o</p>
// <p class="fancy">o</p>
The <SplitWords>
component takes a string as children
, and splits them
by whitespace:
<SplitWords>Howdy, friend!</SplitCharacters>
// <span>Howdy, </span>
// <span>friend!</span>
Note that whitespace between words is collapsed to a single whitespace.
<SplitWords>
Howdy, friend!
How are you?
</SplitCharacters>
// <span>Howdy, </span>
// <span>friend! </span>
// <span>How </span>
// <span>are </span>
// <span>you?</span>
You can customize the wrapper element using the as
prop:
<SplitWords as="p">Howdy, friend!</SplitWords>
// <p>Howdy, </p>
// <p>friend!</p>
All other props will be forwarded to each wrapped element:
<SplitWords as="p" className="fancy">
Howdy, friend!
</SplitWords>
// <p class="fancy">Howdy, </p>
// <p class="fancy">friend!</p>
<div>
{"y tho 🙍🏽♂️".split("").map(char => (
<span>{char}</span>
))}
</div>
// Warning: Each child in an array or iterator should have a unique "key" prop.
<div>
{"y tho 🙍🏽♂️".split("").map((char, index) => (
<span key={index}>{char}</span>
))}
</div>
// react/no-array-index-key
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/no-array-index-key.md
<div>
{"y tho 🙍🏽♂️".split("").map((char, index) => (
// let's hope this text is never dynamic...
// eslint-disable-next-line react/no-array-index-key
<span key={index}>{char}</span>
))}
</div>
// <span>y</span>
// <span> </span>
// <span>t</span>
// <span>h</span>
// <span>o</span>
// <span> </span>
// <span>�</span>
// <span>�</span>
// <span>�</span>
// <span>�</span>
// <span></span>
// <span>♂</span>
// <span>️</span>
<div>
{[..."y tho 🙍🏽♂️"].map((char, index) => (
// eslint-disable-next-line react/no-array-index-key
<span key={index}>{char}</span>
))}
</div>
// <span>y</span>
// <span> </span>
// <span>t</span>
// <span>h</span>
// <span>o</span>
// <span>🙍</span>
// <span>🏽</span>
// <span></span>
// <span>♂</span>
// <span>️</span>
🤕
import { SplitCharacters } from "@squib/react-split";
<SplitCharacters>y tho 🙍🏽♂️</SplitCharacters>;
// <span>y</span>
// <span> </span>
// <span>t</span>
// <span>h</span>
// <span>o</span>
// <span> </span>
// <span>🙍🏽♂️</span>