react-native highlight word and make it clickable - reactjs

I'm developing a react-native app in which I've to highlight #tagged word in paragraph and make that word clickable. I used react-native-highlight-words library and it works fine except click event. I also changed it core library for click event but it hangs my system and not work perfectly as solution is given in this link. I also got an array of #tagged words are coming in paragraph but how to give style to that particular word that's I don't know.
My Code
import Highlighter from 'react-native-highlight-words';
export default class LikeComponent extends Component {
constructor(props) {
super(props);
this.state = {highlightWordArray: []};
}
componentDidMount() {
postText = this.props.postData.details;
var regexp = new RegExp('#([^\\s]*)','g');
postText = postText.match(regexp);
if(postText != null) {
this.setState({highlightWordArray: postText});
}
}
render() {
return (
<Highlighter
highlightStyle={{color: 'red'}}
searchWords={this.state.highlightWordArray}
textToHighlight={this.props.postData.details}
onPress={(value) => console.warn(value)}
/>
)}
}
Is there any solution to highlight #taggeed word in this.props.postData.details and make it clickable?
Thank you.

Actually currently react-native-highlight-words is just a react-native wrapper of highlight-words-core. It gives a component to use in react-native. I checked its library and there is no onPress event is attached to Text components in react-native-highlight-words.
If you want to perform onPress then you have to write onpress functions in core library that is react-native-highlight-words.
Create two new onPress function in Highlighter.js as,
Highlighter.propTypes = {
...
...
onPressNormalText: PropTypes.func,
onPressHighlightedText: PropTypes.func
};
Then add this onPress functions in Highlighter as,
export default function Highlighter({..., ..., onPressNormalText, onPressHighlightedText}) {
...
...
...
}
Finally add this functions on Text components of Highlighter.js,
return (
<Text style={style} {...props} onPress={onPressNormalText}>
{chunks.map((chunk, index) => {
const text = textToHighlight.substr(chunk.start, chunk.end - chunk.start);
return !chunk.highlight ? (
text
) : (
<Text onPress={onPressHighlightedText} key={index} style={chunk.highlight && highlightStyle}>
{text}
</Text>
);
})}
</Text>
);
So finally your Highlighter.js with onPress events looks like,
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { findAll } from "highlight-words-core";
import PropTypes from "prop-types";
Highlighter.propTypes = {
autoEscape: PropTypes.bool,
highlightStyle: Text.propTypes.style,
searchWords: PropTypes.arrayOf(PropTypes.string).isRequired,
textToHighlight: PropTypes.string.isRequired,
sanitize: PropTypes.func,
style: Text.propTypes.style,
onPressNormalText: PropTypes.func,
onPressHighlightedText: PropTypes.func
};
/**
* Highlights all occurrences of search terms (searchText) within a string (textToHighlight).
* This function returns an array of strings and <Text> elements (wrapping highlighted words).
*/
export default function Highlighter({
autoEscape,
highlightStyle,
searchWords,
textToHighlight,
sanitize,
onPressNormalText,
onPressHighlightedText,
style,
...props
}) {
const chunks = findAll({ textToHighlight, searchWords, sanitize, autoEscape });
return (
<Text style={style} {...props} onPress={onPressNormalText}>
{chunks.map((chunk, index) => {
const text = textToHighlight.substr(chunk.start, chunk.end - chunk.start);
return !chunk.highlight ? (
text
) : (
<Text onPress={onPressHighlightedText} key={index} style={chunk.highlight && highlightStyle}>
{text}
</Text>
);
})}
</Text>
);
}
Now you can use Highlighter.js as,
<Highlighter
highlightStyle={{ backgroundColor: "yellow" }}
searchWords={["and", "or", "the"]}
textToHighlight="The dog is chasing the cat. Or perhaps they re just playing?"
onPressNormalText={() => console.log("normal text is clickeddd!")}
onPressHighlightedText={() => console.log("highlighted text is clickked!")
/>
And all done :)
Or if you dont want to do all this, just use my forked version of this library, https://github.com/adityasonel/rn-highlight-words

Related

TextInput /Input element looses focus on key press

I'm currently developing an application using React Native.
This trial app has a component that has a TextInput and two buttons (ADD and DELETE).
When I press the ADD Button, a new component appears. If I press the DELETE Button that the same component disappears.
The screen is like the photo bellow:
I control the TextInput with the index which is the same number as the index of the component.
My question is: why can't I enter some text as usual in this code?
I have to focus the cursor every time I enter 1 word.
I lose a flashing vertical bar(I check in the photo below) in the input area every time I press a key.
How can I resolve this problem?
And, I want to control the inputted value from TextInput with array[] not object{} because in case of an array is easier to delete a component sliding index and value like an explanation below:
I have no idea to control index and value with an, object and it's complicated for my skill now, but if there are some nice ways to resolve using object, I hope to know it.
Here is the code:
import React, { useState } from "react";
import { View, Text, Button, TextInput, StyleSheet } from "react-native";
function Item({ number, handleInput, handleAdd, handleDelete, index }) {
return (
<View style={styles.list}>
<Text>{index}</Text>
<TextInput
style={{ borderWidth: 1 }}
value={number[index]}
onChange={(e) => {
handleInput(index, e.nativeEvent.text);
}}
></TextInput>
<Button
title="ADD"
onPress={() => {
handleAdd();
}}
/>
<Button
title="DELETE"
onPress={() => {
handleDelete(index);
}}
/>
</View>
);
}
export default function TestStateArray() {
const [count, setCount] = useState(1);
const [number, setNumber] = useState([]);
function handleAdd() {
setCount((v) => v + 1);
}
function handleDelete(index) {
setCount((v) => v - 1);
setNumber((v) => {
const ret = v.slice();
ret.splice(index, 1);
return ret;
});
}
function handleInput(index, text) {
setNumber((v) => {
const ret = v.slice();
ret[index] = text;
return ret;
});
}
return (
<View>
{Array.from({ length: count }, (_, i) => (
<Item
number={number}
handleInput={handleInput}
handleAdd={handleAdd}
handleDelete={handleDelete}
key={i + "-" + number}
index={i}
/>
))}
</View>
);
}
const styles = StyleSheet.create({
list: {
margin: 10,
padding: 10,
backgroundColor: "#ddd",
},
});
After I have some answer and some comment, I tried changing the code like bellow, but it still has the same problem...
// onChange={(e) => {
// handleInput(index, e.nativeEvent.text);
onChangeText={(text) => {
handleInput(index, text);
}}
node : 12.18.3
react native : 4.10.1
expo : 3.22.3
The above issue occurred because your handle change function is wrong.
Please change...
const [number, setNumber] = useState({}); // change array to Object in useState.
Replace your handler with below function:
function handleInput(index, text) {
setNumber({ ...number, index: text });
}
React Native has onChangeText event on TextInput component can you try that one?

React Native scrollview infinitely rerendering

I'm new to react native and I'm creating a to do list app. the component I wrote keeps infinitely re-rendering when I initialize Todos state to something that isn't an empty array. This results to an error where react stops it. I have no idea what is causing the infinite loop as I haven't included any recursion or explicit loops. I added a few console.log()'s everywhere and found that deleteTodo and toggleTodo keep getting called automatically until the application crashes.
When Todos state is initialized to an empty array I get an error saying that getTodos.map is not a function.
The component is only expected to re-render whenever the state is updated to either modify , add or delete a task.
import React, { useState } from 'react'
import { Platform, StyleSheet, Text, View, TouchableHighlight } from 'react-native'
import { State, ScrollView } from 'react-native-gesture-handler'
/* All required imports are correctly added */
let id = 0
function TaskRow(props) {
return (
<View>
{/* empty button acting as checkbox */}
<TouchableHighlight>
<Text />
</TouchableHighlight>
{/* main text area, displays task and all details + tags*/}
<TouchableHighlight onPress={props.toggle(props.key)}>
<Text>{props.task}</Text>
</TouchableHighlight>
{/* delete button */}
<TouchableHighlight onPress={props.delete(props.key)}>
<Text>Delete</Text>
</TouchableHighlight>
</View>
)
}
export default function TodoList() {
const [
getTodos,
setTodos,
] = useState([
{ key: id, task: 'this is a task', checked: false },
])
const addTodo = (taskInput = 'sample') => {
id++
setTodos(getTodos.push({ key: id, task: 'task', checked: false }))
}
const deleteTodo = (deleteID) => {
setTodos(getTodos.filter((match) => match.key !== deleteID))
}
const toggleTodo = (toggleID) => {
setTodos(
getTodos.map((match) => {
if (match.key == toggleID) return { key: match.key, task: match.task, checked: !match.checked }
}),
)
}
return (
<View>
<TouchableHighlight onPress={addTodo}>
<Text>Add task</Text>
</TouchableHighlight>
<ScrollView>
{getTodos.map((task) => (
<TaskRow
key={task.key}
task={task.task}
delete={deleteTodo}
toggle={toggleTodo}
/>
))}
</ScrollView>
</View>
)
}
You have a problem in your TaskRow component, on each render you are actually calling the functions instead of declaring an handler for events. Please chenge these two lines in this way:
// ...
<TouchableHighlight onPress={() => props.toggle(props.key)}>
// ...
<TouchableHighlight onPress={() => props.delete(props.key)}>
Another problem is this:
setTodos(getTodos.push({ key: id, task: 'task', checked: false }))
the .push() does not return the array so you should do something like:
setTodos([...getTodos, { key: id, task: 'task', checked: false }])

React Window How to Pass in Components?

I am trying to implement react-window but Ia m not sure how to pass in a component that takes in it's own properties
If I have something like this
{
items.map(
(item, index) => {
<MyComponent
key={key}
item={item}
/>;
}
);
}
how do I make a variable list?
The example does not show how to do this
import { VariableSizeList as List } from 'react-window';
// These row heights are arbitrary.
// Yours should be based on the content of the row.
const rowHeights = new Array(1000)
.fill(true)
.map(() => 25 + Math.round(Math.random() * 50));
const getItemSize = index => rowHeights[index];
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const Example = () => (
<List
height={150}
itemCount={1000}
itemSize={getItemSize}
width={300}
>
{Row}
</List>
);
The example is indeed confusing. This example shows how you can use react-window with an array of data instead of the generative example that the homepage shows:
class ComponentThatRendersAListOfItems extends PureComponent {
render() {
// Pass items array to the item renderer component as itemData:
return (
<FixedSizeList
itemData={this.props.itemsArray}
{...otherListProps}
>
{ItemRenderer}
</FixedSizeList>
);
}
}
// The item renderer is declared outside of the list-rendering component.
// So it has no way to directly access the items array.
class ItemRenderer extends PureComponent {
render() {
// Access the items array using the "data" prop:
const item = this.props.data[this.props.index];
return (
<div style={this.props.style}>
{item.name}
</div>
);
}
}
While itemsArray is not provided in the code sample, ostensibly you would include the props you need to pass to ItemRenderer in it, such as name as shown here. This would leave your usage looking something like this:
<ComponentThatRendersAListOfItems
itemsArray={[{ name: "Test 1" }, { name: "Test 2" }]}
/>

React Native - setNativeProps() on parentElement.props.children = undefined

I'm developing a school management app for myself.
All students in my class are listed in a Flatlist with their parents' phone numbers beside to enable me to send them text messages when a student is absent.
I have a FlatList with Listitems, each of which contains a Touchopacity component with Text child inside.
On successful sending an sms to a student's parent (smsParent method) I want to setNativeProps on both TouchOpacity and its Text child (manipulate their style props). I use ref=(()=>...) to have reference to Touchopacity and then this.props.children (only 1 child) to get to its Text child.
Then however I cannot use setNativeProps (=undefined).
However, when I use ref=(()=>...) on Text as well and then refer to it, setNativeProps works /like in case of its parent/.
Why can't I use setNativeProps() on a child when refering to it by parentEl.props.children? (only 1 child, checked in debugger, it's properly identified)
Please read comments in smsParent method
/*sorry for inserting a snippet - code insertion was crazily formatted/
/**code simplified/
class SingleClassPage extends Component {
buttons = new Array();
constructor(props) {
super(props);
this.state = { students: [] };
this.smsParent = this.smsParent.bind(this);
}
componentDidMount() {
//fetch students from api and setState..
this._getStudentsList();
}
_getStudentsList() {
// ...
}
//flatlist item
ListEl(props) {
return (
<View>
<TouchableOpacity ref={el => { let a = props.item.attId + 'att'; props.buttons[a] = el; }}
style={[styles.buttonDef, (props.item.phone_parent ? styles.buttonBlue : styles.buttonGray)]}
onPress={() => { props.smsSendHandler(props.item, 'attendance', a) }}>
<Text style={props.item.phone_parent ? styles.buttonTextLight : styles.buttonTextDark}>
{props.item.smsAttSent ? 'sms sent' : 'sms send'}
</Text>
</TouchableOpacity>
</View>
)
}
render() {
return (
<View style={{ flex: 1, }}>
<FlatList
data={this.state.students}
extraData={this.state}
keyExtractor={item => item.attId}
renderItem={({ item }) => <this.ListEl buttons={this.buttons} item={item} smsSendHandler={this.smsParent} />}
/>
<BusyIndicator />
</View>
);
}
smsParent(student, msgCategory, smsButton) {
//call myjava module and upon successful callback call below:
let parEl = this.buttons[smsButton];
//childEl is an object with props.children set to text 'sms sent/send' when I watch its value in debugger
//so it's correctly identified
let childEl = parEl.props.children;
// WORKS
parEl.setNativeProps({ style: { backgroundColor: 'green' } });
// OOPS
childEl.setNativeProps({ style: { color: 'black' } });
}
}
edit1
Posting a screenshot of the error (also as response to Dyo's suggestion below - the same error Dyo...)
I think you have to iterate throught children to pass them nativeProps (even if there's only one child) :
smsParent(student, msgCategory, smsButton) {
//call myjava module and upon successful callback call below:
let parEl = this.buttons[smsButton];
React.Children.forEach(parEl.props.children, child => { child.setNativeProps({ style: { color: 'black' } }) });
parEl.setNativeProps({ style: { backgroundColor: 'green' } });
}

remove text from button that created in loop

I'm using react native to build an App
I setup a loop that create button with letter inside each button
I want that onPress will run function that delete the letter inside the button that pressed
So far my code is as follow:
let LettersBoxes = [];
var test = [];
for (let i = 0; i < answerlen; i++) {
console.log(test)
let letter = answer[mixingLetters[i]]
console.log('check',letter)
LettersBoxes.push(
<TouchableOpacity onPress={() => this.onClick(letter, test)} style={styles.boxStyle} key={i}>
<Image
source={require('../../img/parchment3.gif')}
style={{width: 40, height: 40, alignItems: 'center'}}
>
<View>
<Text
style={{fontSize:28, fontWeight: 'bold',}}>
{letter}
</Text>
</View>
</Image>
</TouchableOpacity>
);
}
return(
<View style={styles.viewStyle}>{LettersBoxes}</View>
)
}
};
Is it possible to do what I am trying to do?
If you want to create components dynamically, with a button created for each letter in an array of letters, you can do something like the following (inside the render method).
const letters = ['a', 'b', 'c'];
const buttons = letters.map(letter => (
<Button>{letter}</Button>
);
return (
<View>
{buttons}
</View>
);
I have omitted many things to focus on the functionality. Also, instead of "Button" you can use TouchableOpacity and add the missing props.
Essentially, given a list of letters, you want to map each letter to a button component whose content includes the letter. Then, React knows how to render an array of components by simply saying the variable name in the return part in JSX.
Now, to delete the content of a button that is clicked ("deleting the letter"), you would have to move the list of letters to the component state. There you could define a data structure like:
// component state
{
letters: {
a: true,
b: false,
c: true,
// ...
}
}
In the above structure, if this.state.letters['a'] is true, you show the letter; otherwise, you don't show it. Simply put, do the following when you map:
const buttons = Object.keys(this.state.letters).map(letter => {
if (this.state.letters[letter]) {
return <Button onClick={() => onSomethingClick(letter)}>{letter}</Button>;
}
else { // no letter should appear, so empty content
return <Button onClick={() => onSomethingClick(letter)}></Button>;
}
}
where the onClick event handler method is defined as something like:
onSomethingClick = (letter) => {
this.setState({
letters: {
...this.state.letters, // use Object.assign if you can't use spread operator
[letter]: !this.state.letters[letter] // switches T to F and vice-versa
}
}
}

Resources