I am trying to code something that maps through text and individually styles each word based on state. And when the user taps a word, I would like to rerender all instances of that word on the page with different styling. However, the way I currently have it working rerenders ALL of the text, which is causing a ton of lag in the app.
Is there a way I can restyle mapped components based on some parameter passed to each component, or maybe creating a memo for each word?
Here is an example:
import React, {useState} from 'react';
import { Text, View, StyleSheet} from 'react-native';
export default function RenderedText() {
const [yellowHighlightedWords, setYellowHighlightedWords] = useState(["west", "where", "neighborhood", "trouble", "cool"])
const [redHighlightedWords, setRedHighlightedWords] = useState(["playground", "school", "most"])
const str = "In West Philadelphia born and raised, On the playground is where I spent most of my days Chillin' out, maxin', relaxin' all cool And all shootin' some b-ball outside of the school When a couple of guys who were up to no good Started makin' trouble in my neighborhood I got in one little fight and my mom got scared"
let arr = str.split(/\s/g)
const handlePress = (el) => {
let word = el.toLowerCase()
if (yellowHighlightedWords.includes(word) === true) {
setYellowHighlightedWords((prevState) => {
return prevState.filter(element => {
return element !== word
})
})
} else if (redHighlightedWords.includes(word) === true) {
setRedHighlightedWords((prevState) => {
return prevState.filter(element => {
return element !== word
})
})
} else {
setYellowHighlightedWords((prevState) => {
return (
[word, ...prevState]
)
})
}
}
return (
<Text style={styles.paragraph}>
{arr.map((el) => {
let lc = el.toLowerCase()
return (
<Text style={redHighlightedWords.includes(lc) ? styles.redHighlighted
: yellowHighlightedWords.includes(lc) ? styles.yellowHighlighted : styles.paragraph}
onPress={() => handlePress(el)}
>
{el + " "}
</Text>
)
})}
</Text>
)
}
const styles = StyleSheet.create({
paragraph: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
yellowHighlighted: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
backgroundColor: "yellow",
},
redHighlighted: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
backgroundColor: "red",
}
});
And
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import RenderedText from './components/RenderedText'
export default function App() {
return (
<View style={styles.container}>
<Text>
<RenderedText />
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Use memo to stop the text from re-rendering unnecessarily link:
import React, { memo } from 'react';
import { Text, StyleSheet } from 'react-native';
// function that decides when to rerender component
const areEqual = (prevProps, nextProps) => {
// since text wont change only worry about backgroundColor
return prevProps.backgroundColor == nextProps.backgroundColor;
// RenderedText is wrapped in a Text element so this is here
// && prevProps.children == nextProps.children
};
const MemoText = memo(({ backgroundColor, children, style, ...textProps }) => {
return (
<Text style={[styles.paragraph, { backgroundColor }]} {...textProps}>
{children}
</Text>
);
}, areEqual);
const styles = StyleSheet.create({
paragraph: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
export default MemoText;
RenderedText
import React, { useState, useMemo, useCallback, memo } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import MemoText from './MemoText';
const yellowWords = ['west', 'where', 'neighborhood', 'trouble', 'cool'];
const redWords = ['playground', 'school', 'most'];
const str =
"In West Philadelphia born and raised, On the playground is where I spent most of my days Chillin' out, maxin', relaxin' all cool And all shootin' some b-ball outside of the school When a couple of guys who were up to no good Started makin' trouble in my neighborhood I got in one little fight and my mom got scared";
const strObj = str.split(/\s/g).map((word) => {
let color = null;
if (yellowWords.find((t) => t.toLowerCase() == word.toLowerCase())) {
color = 'yellow';
} else if (redWords.find((t) => t.toLowerCase() == word.toLowerCase())) {
color = 'red';
}
return {
text: word,
color: color,
};
});
export default function RenderedText() {
const [textStyle, setTextStyle] = useState(strObj);
const handlePress = index=>{
const newText = [...textStyle]
let currentColor = newText[index].color
newText[index].color = currentColor ? null : 'yellow'
setTextStyle(newText)
}
return (
<>
{textStyle.map(({ text, color },index) => {
return (
<MemoText backgroundColor={color} onPress={() => handlePress(index)}>
{text + ' '}
</MemoText>
);
})}
</>
);
}
Related
I have a counter that counts down, when the counter hits zero, I would like a style to change. The counter displays properly but the style never changes. How can I get the style to change when the counter hits a certain value?
Below is the code for a very basic example.
import React, { useState, useEffect } from "react";
import { Text, StyleSheet, SafeAreaView, TouchableOpacity, View } from "react-native";
const ActionScreen = () => {
const [timeLeft, setTimeLeft] = useState(4);
const [opac, setOpac] = useState(0.8);
useEffect(() => {
const interval = setInterval(() => setTimeLeft(timeLeft - 1), 1000);
if (timeLeft > 2) {
setOpac(0.8);
console.log("GT");
} else {
setOpac(0.5);
console.log("LT");
}
console.log("opac: ", opac);
if (timeLeft === 0) {
// clearInterval(interval);
setTimeLeft(4);
// setOpac(0.5);
}
return () => {
clearInterval(interval);
};
}, [timeLeft]);
return (
<SafeAreaView style={styles.container}>
<View style={styles.subActionContainer}>
<TouchableOpacity
style={[
styles.topButtons,
{
opacity: opac,
}
]}
>
<Text
style={styles.buttonText}>
{timeLeft}
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 25,
backgroundColor: 'black',
},
subActionContainer: {
flexDirection: "row",
alignContent: 'space-between',
},
topButtons: {
flex: 1,
backgroundColor: 'orange',
},
buttonText: {
fontSize: 18,
textAlign: 'center',
padding: 10,
color: 'white',
},
buttonFail: {
borderWidth: 1,
marginHorizontal: 5,
}
});
export default ActionScreen;
I'm fairly new to React-Native but read somewhere that some styles are set when the app loads and thus don't change but I have also implemented a Dark Mode on my app that sets inline styling, which works well. Why isn't the styling above changing?
Thank you.
you can create your timer and create a method isTimeOut and when it is true you can change the background
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import {useTimerCountDown} from './useCountDownTimer'
export default function App() {
const {minutes, seconds, setSeconds} = useTimerCountDown(0, 10);
const isTimeOut = Boolean(minutes === 0 && seconds === 0);
const handleRestartTimer = () => setSeconds(10);
return (
<View style={styles.container}>
<View style={[styles.welcomeContainer,isTimeOut &&
styles.timeOutStyle]}>
<Text>welcome</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: 10,
backgroundColor: '#ecf0f1',
padding: 8,
},
welcomeContainer:{
backgroundColor:'red',
},
timeOutStyle:{
backgroundColor:'blue',
}
});
See the useTimerCountDown hook here in this
working example
When running the code below in react-native, I get a "buttonColor is read-only" error.
I've been reviewing documentation from various places on useState but am still not sure what is causing this particular error.
I would expect for the button to alternate between 'green' (the initial state) and 'red' after each subsequent tap, but it just show green on the first render and then I get the error message after the first tap.
import React, {useState} from 'react';
import {StyleSheet, Text, View, TouchableOpacity} from 'react-native';
const ColorSquare = () => {
const[buttonColor, setColor] = useState('green');
const changeColor = () => {
if (buttonColor='green') {
setColor('red')
} else if (buttonColor='red') {
setColor('green')
} else {
setColor('blue')
}
}
return (
<View style={styles.container}>
<TouchableOpacity
style={{backgroundColor:buttonColor, padding: 15}}
onPress={()=>changeColor()}
>
<Text style={styles.text}>Change Color!</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
text:{
color:'white'
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
export default ColorSquare;
Problem is you are assigning the value of button color instead of comparing it i.e using = instead of ===. You are basically setting a value to button color which is wrong.
import React, {useState} from 'react';
import {StyleSheet, Text, View, TouchableOpacity} from 'react-native';
const ColorSquare = () => {
const[buttonColor, setColor] = useState('green');
const changeColor = () => {
if (buttonColor==='green') { // use === instead of == //
setColor('red')
} else if (buttonColor==='red') { // use === instead of == //
setColor('green')
} else {
setColor('blue')
}
}
return (
<View style={styles.container}>
<TouchableOpacity
style={{backgroundColor:buttonColor, padding: 15}}
onPress={()=>changeColor()}
>
<Text style={styles.text}>Change Color!</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
text:{
color:'white'
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
export default ColorSquare;
In addition to the answer posted by Atin above, I also thought of a way to do this using numerical values and an array of colors which I ultimately chose to go with due to needing some underlying binary representation of the data.
import React, {useState} from 'react';
import {StyleSheet, Text, View, TouchableOpacity} from 'react-native';
const ColorSquare = () => {
const[buttonNumber, setNumber] = useState(0);
const colors = ['green','red']
const changeColor = () => {
if (buttonNumber<1) {
setNumber(buttonNumber => buttonNumber + 1)
} else {
setNumber(buttonNumber => buttonNumber - 1)
}
}
return (
<View style={styles.container}>
<TouchableOpacity
style={{backgroundColor:colors[buttonNumber], padding: 15}}
onPress={()=>changeColor()}
>
<Text style={styles.text}>Change Color!</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
text:{
color:'white'
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
export default ColorSquare;
HI there if you are in 2021 having this problem I want to show you something that I was doing wrong
In my case I was trying to use a handleChange function to setState look here below
const handleChange = (e) => {
setCustomerLogin({...customerLogin, [e.target.name]: e.target.value})
I was getting a setCustomerLogin is ready only and its because I wrapped the (e) in the above example.
Solution is
const handleChange = e => {
setCustomerLogin({...customerLogin, [e.target.name]: e.target.value})
I used nested Text to add an image between the texts.
After that I tried to get the original string value but the string in the image is replaced with I.
import * as React from 'react';
import { Text, View, StyleSheet,TextInput,Button,Image } from 'react-native';
import Constants from 'expo-constants';
// or any pure javascript modules available in npm
export default class App extends React.Component {
state = {
text: '',
images:[],
}
changeText = (text) => {
this.setState({
text,
});
}
parseText = () => {
const { text,images } = this.state;
if (images.length !== 0){
return(
<Text>
{text}
{images}
</Text>
);
}
return text;
}
addImage = () => {
const { images } = this.state;
const newImage = (<Image
style={{width:20,height:20}}
source={require('./assets/snack-icon.png')} />);
this.setState({
images:[...images , newImage],
})
}
render() {
return (
<View style={styles.container}>
<TextInput
style={{flex:1}}
onChangeText={this.changeText}
placeholder="test"
>
{this.parseText()}
</TextInput>
<Button
title="add Image"
onPress={this.addImage}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
https://snack.expo.io/rkjP70hUS
This example is intended to illustrate the approximate situation but not the same as mine.
In this example, when you add an image and import text, the image comes out as I.
Can I change to the string instead of I?
Iam new to react-native, and are playing around with UI Kitten (https://akveo.github.io/react-native-ui-kitten/)
It has a default ChatListScreenBase that renders a list. It looks like this:
import React, {Component} from 'react';
import {
StyleSheet,
View,
ListView
} from 'react-native';
import ThemeService from '../util/ThemeService';
import api from '../util/ApiMock';
export default class ChatListScreenBase extends Component {
constructor(props) {
super(props);
let ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
});
let data = api.getUserMsgList(api.userId);
this.state = {
dataSource: ds.cloneWithRows(data)
};
}
render() {
let Header = ThemeService.getChatListHeaderComponent();
return (
<View style={styles.container}>
<Header/>
<ListView
style={styles.list}
automaticallyAdjustContentInsets={false}
dataSource={this.state.dataSource}
renderRow={(row) => this._renderMsgItem(row)}
/>
</View>
)
}
_renderMsgItem(msg) {
let user = api.getUserInfo(msg.from);
let ChatItem = ThemeService.getChatItemComponent();
msg.text = msg.text.length > 25 ? msg.text.substring(0,23)+'...' : msg.text;
return (
<ChatItem
user={user}
message={msg}
onClick={(user) => this._openChat(user)}
/>
);
}
_openChat(user) {
this.props.navigator.push({
screen: ThemeService.getChatScreen(true),
passProps: {
userId: user.id
}
});
}
}
const styles = StyleSheet.create({
container:{
flex: 1,
},
list: {
paddingTop: 10
},
});
I would like to substitute the data this screen renders, with my own code, like this:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
FlatList
} from 'react-native';
import ajax from '../util/ApiMock';
export class ChatListScreenBase extends Component {
constructor(props) {
super(props);
this.state = {
themeIndex: ThemeService.getCurrentThemeIndex()
}
}
state = {
data: []
}
async componentDidMount() {
const data = await ajax.getDataTest();
this.setState({data});
}
render() {
return (
<View style={styles.container} >
<Text style={styles.h2text}>
Black Order
</Text>
<FlatList
data={this.state.data}
showsVerticalScrollIndicator={false}
renderItem={({item}) =>
<View style={styles.flatview}>
<Text style={styles.name}>{item.title}</Text>
</View>
}
keyExtractor={item => item.title}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
h2text: {
marginTop: 10,
fontFamily: 'Helvetica',
fontSize: 36,
fontWeight: 'bold',
},
flatview: {
justifyContent: 'center',
paddingTop: 30,
borderRadius: 2,
},
name: {
fontFamily: 'Verdana',
fontSize: 18
},
email: {
color: 'red'
}
});
When I do this, I keep getting this error:
"Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of ChatListScreenBase."
I think that the problem is with the <FlatList>, but I'am not sure what type its expecting?
When I call the webservice it returns:
{"users":[{"_id":"5d0fcd8fe1dbfd7c23424e09","title":"180710","type":1,"published":"5"},{"_id":"5d0fcd8fe1dbfd7c23424e0a","title":"180705","type":3,"publiched":"5"},{"_id":"5d0fcd8fe1dbfd7c23424e0b","title":"Mr.
Nick","type":2,"publiched":"5"}]}
Kind regards.
For the looks of your API response you are setting data as object with key users and then trying to loop over it.
Instead update componentDidmount:
async componentDidMount() {
const resp = await ajax.getDataTest();
this.setState({ data: resp.users });
}
native . I made the status component and router.js file .The emulator giving me error Cant find varible i do not know what is the problem but im getting this error in emulator .
here is my code
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import StatusComponent from './component/StatusComponent';
import HeaderComponent from './component/headerComponent';
import Router from './component/Router';
import MainPage from './component/MainPage';
export default class Point extends Component {
render() {
return (
<View style={{flex: 1,backgroundColor: 'white'}}>
<StatusComponent/>
<HeaderComponent/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
} );
AppRegistry.registerComponent('Point', () => Point);
and here is my status component
import React,{ Component } from 'react';
import {
Text,
View,
StyleSheet
} from 'react-native';
export class StatusComponent extends Component{
render()
{
return(
<View style={styles.Bar}>
</View>
)
};
}
export default StatusComponent;
const styles=StyleSheet.create({
Bar:{
backgroundColor: 'white',
height: 20
}
})
here is code for Router.Js this file cousing the issue
import React, { Component } from 'react'
import {
StyleSheet,
Text,
Navigator,
TouchableOpacity
} from 'react-native'
import MainPage from './MainPage'
import Sports from './Sports'
export default class Router extends Component {
constructor(){
super()
}
render() {
return (
<Navigator
initialRoute = {{ name: 'MainPage', title: 'MainPage' }}
renderScene = { this.renderScene }
navigationBar = {
<Navigator.NavigationBar
style = { styles.navigationBar }
routeMapper = { NavigationBarRouteMapper } />
}
/>
);
}
renderScene(route, navigator) {
if(route.name == 'MainPage') {
return (
<MainPage
navigator = {navigator}
{...route.passProps}
/>
)
}
if(route.name == 'Sports') {
return (
<Sports
navigator = {navigator}
{...route.passProps}
/>
)
}
}
}
var NavigationBarRouteMapper = {
LeftButton(route, navigator, index, navState) {
if(index > 0) {
return (
<TouchableOpacity
onPress = {() => { if (index > 0) { navigator.pop() } }}>
<Text style={ styles.leftButton }>
Back
</Text>
</TouchableOpacity>
)
}
else { return null }
},
RightButton(route, navigator, index, navState) {
if (route.openMenu) return (
<TouchableOpacity
onPress = { () => route.openMenu() }>
<Text style = { styles.rightButton }>
{ route.rightText || 'Menu' }
</Text>
</TouchableOpacity>
)
},
Title(route, navigator, index, navState) {
return (
<Text style = { styles.title }>
{route.title}
</Text>
)
}
};
const styles = StyleSheet.create({
navigationBar: {
backgroundColor: 'blue',
},
leftButton: {
color: '#ffffff',
margin: 10,
fontSize: 17,
},
title: {
paddingVertical: 10,
color: '#ffffff',
justifyContent: 'center',
fontSize: 18
},
rightButton: {
color: 'white',
margin: 10,
fontSize: 16
}
})
In the StatusComponent file, you have export in front of the class StatusComponent extends Component. You should remove that export and leave the export default StatusComponent; at the bottom.
If after that, it still doesn't work then check the absolute path you are using to import the StatusComponent. Make sure it's correct
there is mistake in statusComponent file and thats very stupid mistake