react-native useState error ("is read-only") - react-native

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})

Related

Only rerender components based on some parameter?

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>
);
})}
</>
);
}

Change component style when state is set to certain value

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

Why is React Native FlatList not working on Android but it is for Web?

I am using Expo to build a react native app with AWS for the backend.
I am trying to display a list of friends using FlatList and the AWS data. The list works and is visible on my web browser, but for some reason, it is not displaying on my Android phone. What might the issue be?
FriendsList.tsx
import { API, graphqlOperation } from 'aws-amplify';
import * as React from 'react';
import {useEffect, useState} from 'react';
import { FlatList, View, ScrollView, Text, StyleSheet } from 'react-native';
import { listUsers } from '../graphql/queries';
import FriendsListItem from '../components/FriendsListItem';
export default function FriendsList() {
const [ users, setUsers ] = useState([]);
useEffect( () => {
const fetchUsers = async () => {
try {
const usersData = await API.graphql(
graphqlOperation(
listUsers
)
)
setUsers(usersData.data.listUsers.items);
} catch (e) {
}
}
fetchUsers();
},[])
return (
<View style={ styles.container }>
<FlatList
style={{ width: '100%' }}
data={users}
renderItem={({ item }) => <FriendsListItem user={item} />}
keyExtractor={(item) => item.id}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
FriendsListItem.tsx
import * as React from 'react';
import { View, Text, StyleSheet, Image, TouchableWithoutFeedback } from 'react-native';
import { API, graphqlOperation, Auth } from "aws-amplify";
import { User } from './types';
export type FriendsListItemProps = {
user: User;
}
const FriendsListItem = ( props: FriendsListItemProps ) => {
const { user } = props;
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={styles.lefContainer}>
<Image source={{ uri: user.imageUri }} style={styles.avatar}/>
<View style={styles.midContainer}>
<Text style={styles.username}>{user.name}</Text>
<Text numberOfLines={2} style={styles.status}>{user.email}</Text>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
export default FriendsListItem;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: "100%",
justifyContent: 'space-between',
//height: '100%',
},
lefContainer: {
flexDirection: 'row',
padding: 16,
},
midContainer: {
justifyContent: 'space-around'
},
avatar: {
width: 60,
height: 60,
borderRadius: 50,
marginRight: 15,
},
username: {
fontWeight: 'bold',
fontSize: 16,
},
status: {
fontSize: 16,
color: 'grey',
},
});
Looking at the code example, in your FriendsListItem component, any time you use your "user" prop, you need to change it. For example, change this:
user.imageUri
to this:
user.item.imageUri
What you are passing in is an object (e.g. user), which then contains another object (e.g. item), which finally contains your data (e.g. imageUri).
I figured it out. Turns out I am just an idiot. The expo app on my phone was logged into a different account so that's why it wasn't showing any friends for that user.

Force unmounting on screen change

I recently integrated React Redux and Redux Thunk into my application in the hope that it would better allow me to manage state across screens.
However, using my navigation library (react native router flux), when ever I navigate between screens I get warnings of trying to set state across unmounted components and I am not sure what I would even need to unmount in componentWillUnmount as no calls should happen after a screen navigation.
My question then is, how can I force unmount everything on componentWillUnmount? Is there something built into React Native that I should use? Or, in my navigation library?
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import * as Font from 'expo-font'
class CustomText extends Component {
async componentDidMount() {
await Font.loadAsync({
'varelaround-regular': require('../../../assets/fonts/varelaround-regular.ttf'),
'opensans-regular': require('../../../assets/fonts/opensans-regular.ttf'),
'opensans-bold': require('../../../assets/fonts/opensans-bold.ttf'),
});
this.setState({ fontLoaded: true });
}
state = {
fontLoaded: false,
};
setFontType = type => {
switch (type) {
case 'header':
return 'varelaround-regular';
case 'bold':
return 'opensans-bold';
default:
return 'opensans-regular';
}
};
render() {
const font = this.setFontType(this.props.type ? this.props.type : 'normal');
const style = [{ fontFamily: font }, this.props.style || {}];
const allProps = Object.assign({}, this.props, { style: style });
return (
<View>
{
this.state.fontLoaded ? (
<Text {...allProps}>{this.props.children}</Text>
) : <Text></Text>
}
</View>
);
}
}
export default CustomText;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
And one of my screens:
import React from "react";
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
import { Actions } from "react-native-router-flux";
import { connect } from "react-redux";
import * as profile from "../actions/profile";
import {
CustomText
} from "../components/common/";
class Home extends React.Component {
componentDidMount() {
this.props.loadProfile();
}
renderScreen() {
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 0.3 }}>
<CustomText type="header" style={styles.headerTextStyle} onPress={() => Actions.home()}>
Hello {this.props.name}!
</CustomText>
</View>
</View>
);
}
renderWaiting() {
return (
<GradientBackground type="purple">
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<ActivityIndicator size="large" color="#FFF" />
</View>
</GradientBackground>
);
}
render() {
return (
<View style={{ flex: 1 }}>
{this.props.isLoading == true
? this.renderWaiting()
: this.renderScreen()}
</View>
);
}
}
function mapStateToProps(state) {
return {
name: state.profile.profile.friendly_name,
isLoading: state.profile.isLoading,
error: state.profile.error
};
}
function mapDispatchToProps(dispatch) {
return {
loadProfile: () => dispatch(profile.loadProfile())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home);
const styles = StyleSheet.create({
headerTextStyle: {
color: "#FFFFFF",
fontSize: 40,
textAlign: "center",
marginVertical: 50
},
basicViewStyle: {
flex: 1
}
});

Using react-spring for react-native

I want to use this library for react-native but couldn't figure out how. The documentation link for react-native is broken. Can i use the library for react-native?
React-Spring can be used for react-native. They have updated it for all platform.
Try this out:-
yarn add react-spring#5.3.0-beta.0
import React from 'react'
import { StyleSheet, Text, View, TouchableWithoutFeedback } from 'react-native'
import { Spring, animated } from 'react-spring/dist/react-spring-native.esm'
const styles = {
flex: 1,
margin: 0,
borderRadius: 35,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
}
export default class App extends React.Component {
state = { flag: true }
toggle = () => this.setState(state => ({ flag: !state.flag }))
render() {
const { flag } = this.state
return (
<Spring native from={{ margin: 0 }} to={{ margin: flag ? 100 : 0 }}>
{props => (
<TouchableWithoutFeedback onPressIn={this.toggle}>
<animated.View style={{ ...styles, ...props }}>
<Text>It's working!</Text>
</animated.View>
</TouchableWithoutFeedback>
)}
</Spring>
)
}
}
`
For anyone with issues, try using a different import. This worked for me on expo's react native.
import React, { Component } from 'react';
import { Text, View, TouchableWithoutFeedback } from 'react-native';
import { Spring, animated } from 'react-spring/renderprops-native';
const AnimatedView = animated(View);
const styles = {
flex: 1,
margin: 0,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
}
export default class App extends Component {
state = { flag: true }
toggle = () => this.setState(state => ({ flag: !state.flag }))
render() {
const { flag } = this.state
return (
<Spring
native
from={{ margin: 0 }}
to={{ margin: flag ? 100 : 0 }}
>
{props => (
<TouchableWithoutFeedback onPressIn={() => this.toggle()}>
<AnimatedView style={{ ...styles, ...props }}>
<Text>{flag ? "true" : 'false'}</Text>
</AnimatedView>
</TouchableWithoutFeedback>
)}
</Spring>
);
}
}
The example below works.
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, TouchableWithoutFeedback} from 'react-native';
import { Spring, animated } from 'react-spring'
const AnimatedView = animated(View)
const styles = {
flex: 1,
margin: 0,
borderRadius: 35,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
}
type Props = {};
export default class App extends Component<Props> {
state = { flag: true }
toggle = () => this.setState(state => ({ flag: !state.flag }))
render() {
const { flag } = this.state
return (
<Spring native from={{ margin: 0 }} to={{ margin: flag ? 100 : 0 }}>
{props => (
<TouchableWithoutFeedback onPressIn={this.toggle}>
<AnimatedView style={{ ...styles, ...props }}>
<Text>It's working!</Text>
</AnimatedView>
</TouchableWithoutFeedback>
)}
</Spring>
);
}
}