Open CaptainStiggz opened 6 years ago
Hey @CaptainStiggz, yeah this lib does not handle nested replacements. The reason being, it's meant to be roughly equivalent to String.prototype.replace
in it's functionality, however, it is true that we run into limitations since replacements can change the data type to something other than strings.
If you look here you'll see that it only runs replacements on strings. Once your first replacement has been run the element in the result array will be an object (i.e. <b>...</b>
) so no replacements will be run on the inner string.
I'm open to suggestions if you have thoughts about how we might support more robust replacements.
To your second point, it just depends on the regex. If you can craft a regex that handles unbalanced/malformed tags then it should work fine. But there's no internal logic here to augment the functionality of the regex.
Thanks @iansinnott! I've been fooling around with a recurisive augmentation as follows. It's still pretty rough, but it might be useful. This modification, combined with a slight modification to the regex I originally suggested (/\[i\]([\s\S]+)\[\/i\]/g
should be lazy: /\[i\]([\s\S]+?)\[\/i\]/g
) seems to work pretty well.
const reactStringReplaceRecursive = (input, pattern, fn, key=0) => {
const isEmpty = (item) => {
if(!item) return true
if(item.hasOwnProperty('props')) {
return false
} else {
return (item.length) ? false : true
}
}
if(!input) {
return null
} else if(typeof input === "string") {
return reactStringReplace(input, pattern, fn)
}
var output = []
for(var i=0; i<input.length; i++) {
const item = input[i]
if(item) {
if(typeof item === "string") {
const next = reactStringReplace(item, pattern, fn)
if(!isEmpty(next)) output.push(next)
} else if(typeof item === "object") {
if(item.hasOwnProperty('props') && item.props.hasOwnProperty('children')) {
const next = reactStringReplaceRecursive(item.props.children, pattern, fn, key+1)
if(!isEmpty(next)) {
const props = Object.assign({key: "k"+key+"i"+i}, item.props)
output.push(React.createElement(
item.type,
props,
next
))
}
} else {
const next = reactStringReplaceRecursive(item, pattern, fn, key+1)
if(!isEmpty(next)) output.push(next)
}
}
}
}
return output
}
This is a decent start, but still runs into some troublesome edge cases. For example, depending on the ordering of the bold/italic tags, I might end up with something like this:
const text = "[i][b]this should be bold and italic[/b][/i]"
// const text = "[b][i]this should be bold and italic[/i][/b]" - this would work
let replaced = reactStringReplace(text, /\[b\]([\s\S]+?)\[\/b\]/g, (match, i) => {
return <b key={i}>{match}</b>
})
replaced = reactStringReplace(replaced, /\[i\]([\s\S]+?)\[\/i\]/g, (match, i) => {
return <i key={i}>{match}</i>
})
// replaced => [
// "[i]",
// <b>this should be bold and italic</b>,
// "[/i]"
// ]
The problem gets pretty tricky here. Not exactly sure how I want to handle such cases.
You might consider using the React.Children
API for the recursion in this case, since it will handle children regardless of whether it's a single element or an array. You would just need to check if the current item being iterated over was a react element.
That being said, you're use case is essentially a new rich text markup, which might be beyond the scope of simple regex and string replacement. You might consider using a stack to help you track opening/closing brackets and build up a deeply nested data structure representing your markup. Or do something like DraftJS does and annotate each character in the string with metadata which you subsequently use to render the markup. In either case you'll probably end up having to revert to strings and dangerouslySetInnerHTML
.
DraftJS character metadata: https://draftjs.org/docs/api-reference-character-metadata.html
I'm having trouble using
reactStringReplace
to format text with bold/italic tags. I have it set up as follows:I'm not sure if this is a problem with
reactStringReplace
, with the regex I'm using, or something else. If I apply the italic replace first, I get italic tags where I'd expect them to be, but the [b] tags remain unchanged. Is this use case possible usingreactStringReplace
or do I need to usedangerouslySetInnerHtml
?Bonus: is
reactStringReplace
capable of handling unbalanced tags, or improperly formatted tags as intext2
or should I be doing some pre-processing to ensure the strings are properly balanced and formatted?