React Native stored array data with asyncstorage returns nothing - react-native

I am trying to build a lunch picker app that allows user to add their own menu. I want to save user data into array by using AsyncStorage. However, my value returns nothing even though the array has values. Below is my code.
//Main screen
class HomeScreen extends React.Component {
//initial
constructor(props) {
super(props);
this.state = {
isReady: false,
myMenu: '????',
menutext: '',
randomArray: ['a', 'b', 'c'],
visibility: false,
};
}
_loadMenu = async () => {
try{
const loadMenu = await AsyncStorage.getItem("menuInStorage")
const parsedLoadMenu = JSON.parse(loadMenu)
const myReturn = [...this.state.randomArray, parsedLoadMenu]
this.setState({randomArray: myReturn})
}
catch(err){
alert(err)
}
}
//get input from textinput field and add to array
addMenu = newMenu => {
//...
this._saveMenu(this.state.randomArray)
};
_saveMenu = (saving) => {
const saveMenu = AsyncStorage.setItem("menuInStorage", JSON.stringify(saving))
}
//control modal
setModalVisibility(visible) {
this.setState({visibility: visible});
}
//UI
render() {
return (
<View style={styles.mainContainer}>
<View style={[styles.container, {flexDirection: 'row', justifyContent: 'center'}]}>
<TextInput
style={{ height: 40, fontSize: 20, paddingLeft: 15, textAlign: 'left', width: 250, borderBottomColor: '#D1D1D1', borderBottomWidth: 1 }}
placeholder=".."
onChangeText={menutext => this.setState({ menutext })}
value={this.state.menutext}
/>
<Button
title=".."
onPress={() => this.addMenu(this.state.menutext)}
buttonStyle={{width:100}}
backgroundColor="#2E282A"
/>
</View>
<Text>{'\n'}</Text>
<Button
onPress={() => this.setModalVisibility(true)}
title=".."
buttonStyle={{width: 150}}
backgroundColor="#2E282A"
/>
</View>
<Modal
onRequestClose={() => this.setState({ visibility: false })}
animationType={'slide'}
transparent={false}
visible={this.state.visibility}
>
<View style={[styles.modalContainer, {marginBottom: 100}]}>
<Text style={[styles.text, { fontWeight: 'bold', padding: 20, backgroundColor: '#9090DA', borderBottomColor: '#5C5C8B',
borderBottomWidth: 1,}]}>
{'<'}List will be here{'>'}
</Text>
<ScrollView style={{height: "94%"}}>
<View style={styles.row}>{this.state.randomArray}</View>
</ScrollView>
<Button
buttonStyle={{justifyContent: 'center', marginTop: 5}}
backgroundColor="#2E282A"
onPress={() => this.setModalVisibility(!this.state.visibility)}
title="Close"
/>
</View>
</Modal>
</View>
);
}
}
How the app supposed to work is, when user clicks a button, the modal shows all data in array called 'randomArray'. After user added their custom text, it should be added at the end of the randomArray. I want to save this data to the disk and load from the disk when the app is launched. At this moment, I can load array data, but it doesn't keep user data. My current code returns nothing. I need your help. Thanks.

It looks like the logic in _loadMenu is slightly incorrect on this line:
const myReturn = [...this.state.randomArray, parsedLoadMenu]
If I understand correctly, you're expecting parsedLoadMenu to be a value of type Array. The line above will basically append the value parsedLoadMenu to the resulting array stored in myReturn - in the case of your code, this will mean the last item of myReturn will be an array, which would be incorrect from what I see in your code. Consider updating this line as shown:
/*
Add ... before parsedLoadMenu to concatenate the two arrays in myReturn
*/
const myReturn = [...this.state.randomArray, ...parsedLoadMenu]
By adding the ... as shown, this causes the two arrays this.state.randomArray and parsedLoadMenu to be concatenated together in myReturn. It would also be worth checking the parse result from JSON.parse() to ensure that it is an array before attempting this concatenation:
_loadMenu = async () => {
try{
const loadMenu = await AsyncStorage.getItem("menuInStorage")
let parsedLoadMenu = JSON.parse(loadMenu)
/*
Consider an additional check here to ensure the loaded data is of
correct Array type before proceeding with concatenation
*/
if(!Array.isArray(parsedLoadMenu)) {
parsedLoadMenu = [];
}
/* Concatenate the two arrays and store result in component state */
const myReturn = [...this.state.randomArray, ...parsedLoadMenu]
this.setState({randomArray: myReturn})
}
catch(err){
alert(err)
}
}
Also, consider revising the addMenu logic, so that the entire array of menu items in your is persisted to AsyncStorage rather than the newly added menu item only, as you are currently doing:
addMenu = (newMenu) => {
/*
Persist current randomArray with newMenu item appended
*/
this._saveMenu([...this.state.randomArray, newMenu])
};
Hope this helps!

Related

mobile app want graphical text to change font size per line automatically, like on tiktok/instagram alternating lines different sizes

On TikTok and Instagram, they can generate automatically text font size like this image, where alternating lines have different font sizes automatically. I'm trying to figure out how to code that in React Native for mobile IOS and Android: [[enter image description here](https://i.stack.imgur.com/vkhIo.jpg)](https://i.stack.imgur.com/XcjLq.jpg)
I couldn't figure it out. I made something that I'm not crazy about, which is just having a larger font on the first three lines and then a smaller font. See image: But I don't like it. enter image description here
I totally misunderstood what PixelRatio.getFontScale did. I thought it would provide the average width a single character takes up on screen. If you can find a way to get a rough estimate of the width of a single character, then this method will work link:
import { useEffect, useState } from 'react';
import { View, StyleSheet, Text, PixelRatio } from 'react-native';
import rnTextSize from 'react-native-text-size';
import reduce from 'awaity/reduce';
import useViewLayout from './useViewLayout';
const fontScale = PixelRatio.getFontScale();
const showLayoutValues = true
export default function MultiLineText({
width,
containerStyle,
textStyle1 = { fontSize: 16 },
textStyle2 = { fontSize: 22 },
str,
...textProps
}) {
// containerLayout will provide the max width each line can have
const [containerLayout, onContainerLayout] = useViewLayout();
// lines was created in a useMemo hook but I wasnt sure if
// useMemo could handle async
const [lines, setLines] = useState([]);
useEffect(() => {
const calcLines = async () => {
let words = str.split(' ').filter((s) => s.trim().length);
let newLines = await words.reduce(
async (prevPromise, curr, index) => {
const prev = await prevPromise;
let lineIndex = prev.length - 1;
let style = index % 2 == 0 ? textStyle1 : textStyle2;
const fontSize = style.fontSize;
// I wanted to use this https://github.com/aMarCruz/react-native-text-size/
// to measure text width but expo doesnt support it
const config = {
// if you exported from expo and link rnTextSize set this to true
useMeasureModule:false,
fontProps:style
}
const useMeasureModule = false;
let lineWidth = await getTextWidth(
prev[lineIndex],
fontSize,
config
);
let wordWidth = await getTextWidth(curr, fontSize, config);
// if currentLine can fit the next word add it
if (lineWidth + wordWidth < (width || containerLayout.width))
prev[lineIndex] += curr + ' ';
// or put it on the next line
else {
prev[lineIndex + 1] = curr + ' ';
}
return prev;
},
['']
);
setLines(newLines);
};
calcLines();
}, [str, containerLayout, width, textStyle1, textStyle2]);
return (
<>
{showLayoutValues && <Text>Container Layout: {JSON.stringify(containerLayout,null,4)}</Text>}
<View
style={[styles.container, containerStyle]}
onLayout={onContainerLayout}>
{lines.map((line, i) => (
<Text
{...textProps}
// to ensure that lines dont wrap
numberOfLines={1}
style={[textProps.style, i % 2 == 0 ? textStyle1 : textStyle2]}>
{line}
</Text>
))}
</View>
</>
);
}
const getTextWidth = async (str, fontSize, config={}) => {
const {fontProps, useMeasureModule} = config;
if (!useMeasureModule) {
// random mathing
return str.length * fontScale * fontSize ** 0.8;
}
let measure = await rnTextSize.measure({
...fontProps,
text: str,
});
return measure.width;
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
});
And then in use:
export default function App() {
return (
<View style={styles.container}>
<MultiLineText
containerStyle={{
width: '100%',
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
textStyle1={styles.text}
textStyle2={styles.text2}
str="I am really not sure as of how long this text needs to be to exceed at least 3 lines. I could copy and paste some stuff here but I think if I just type and type it would be quicker than googling, copying, and pasting"
/>
</View>
);
}
I just found out that onTextLayout is a thing. It gives about each line in the Text component, including info about the characters present on each line. This could be used to figure out where to break lines of text (no planned web support):
After tying the str prop to a text input it became very clear that it is ideal to prevent this component from re-rendering as much as possible so I made additional changes (demo)
import { useState, useCallback, useEffect, memo } from 'react';
import { View, StyleSheet, Text, ScrollView } from 'react-native';
// text lines will alternate between these styles
const defaultLineStyles = [
{ color: 'red', fontSize: 16 },
{ color: 'blue', fontSize: 22 },
{ color: 'green', fontSize: 28 },
];
function MultiLineText({
containerStyle,
lineStyles = defaultLineStyles,
str,
...textProps
}) {
const [lines, setLines] = useState([]);
// each time a substring is added to line,
// remove the substring from remainingStr
const [remainingStr, setRemainingStr] = useState('');
const onTextLayout = useCallback((e) => {
// the first line of text will have the proper styling
let newLine = e.nativeEvent.lines[0].text;
setLines((prev) => {
return [...prev, newLine];
});
// remove newLine from remainingStr
setRemainingStr((prev) => prev.replace(newLine, ''));
}, []);
// when str changes reset lines, and set remainingStr to str
useEffect(() => {
setLines([]);
setRemainingStr(str);
}, [str]);
return (
<>
<View style={[styles.container, containerStyle]}>
<ScrollView style={{ flex: 1 }}>
{lines.map((line, i) => (
<Text
{...textProps}
style={[textProps.style, lineStyles[i % lineStyles.length]]}>
{line}
</Text>
))}
</ScrollView>
{/* this view will be invisible*/}
{remainingStr.length > 0 && (
<View style={{ opacity: 0 }}>
<Text
{...textProps}
onTextLayout={onTextLayout}
style={[
textProps.style,
// use lines.length to get proper style
lineStyles[lines.length % lineStyles.length],
]}>
{remainingStr}
</Text>
</View>
)}
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
height:'50%'
},
});
// be careful when passing non memoized array/objects
export default memo(MultiLineText)
Its important to note that objects/arrays that arent memoized/state/refs will cause the memoized component to re-render, even if the values are static e.g
<MultiLineText
containerStyle={{
width: '100%',
height: 200,
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
style={styles.defaultTextStyle}
str={text}
lineStyles={[styles.text,styles.text2]}
/>
containerStyle and lineStyles are getting new objects and arrays every time its parent component re-render, which will make MultiLineText re-render (even though its memoized). After moving the containerStyle to the stylesheet and memoizing lineStyles re-rendering becomes better:
const lineStyles = React.useMemo(()=>{
return [styles.text,styles.text2]
},[])
return (
<View style={styles.container}>
<TextInput onChangeText={setText} label="Enter some text" value={text} />
<MultiLineText
containerStyle={styles.textContainer}
style={styles.defaultTextStyle}
str={text}
lineStyles={lineStyles}
/>
</View>

how to stop images from rendering on setState

I have images associated with a counter and based on this increment or decrement in counter, a calculation is done and displayed in a text at the bottom.
The problem is that when I render, the images get rendered again and are loaded again and again and again. which I dont want.
If I dont render, the text will not update with the calculated amount.
For the counter I am using react-native-counter.
I have already tried with shouldcomponentupdate, but I want to stop only image rendering, the rest should work.
Please advise.
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Header
backgroundColor="#25D366"
leftComponent={
<Icon
name="menu"
size={40}
color={"#fff000"}
onPress={() => this.props.navigation.openDrawer()}
/>
}
centerComponent={{
text: "Veg & Fruits",
style: {
color: "#ffffff",
fontSize: 25,
fontWeight: "bold",
},
}}
rightComponent={<Icon name="home" color={"#ff0000"} />}
></Header>
/// this is searchbar component,
<SearchBar
fontColor="#ffffff"
fontWeight="bold"
fontSize={20}
iconColor="#c6c6c6"
shadowColor="#ffffff"
cancelIconColor="#c6c6c6"
backgroundColor="#25D366"
placeholder="Search here"
onChangeText={(text) => {
this.setState({ photos: [] });
this.state.search = text;
this.filterList(this.state.search);
console.log("text changed");
}}
onPressCancel={(text) => {
text = "";
//this.filterList(text);
}}
onPress={(text) => {
console.log("rendering");
console.log("now text is: ", this.state.search);
}}
/>
/// in this view images are displayed using functions
<View>
<ScrollView
style={{
height: Dimensions.get("window").height - 200,
}}
>
<View
key={Date.now()}
style={{
flex: 1,
flexDirection: "column",
flexWrap: "wrap",
}}
>
{this.filterList(this.state.search)}
{this._renderImages()}
</View>
</ScrollView>
<CalcText tt={total_num} />
</View>
</div>
);
}
}
class CalcText extends Component {
constructor(props) {
super(props);
this.state = {
ta: 0,
};
}
shouldComponentUpdate(nextProps) {
console.log(nextProps.tt);
if (this.props.tt !== nextProps.tt) {
console.log("changed");
return true;
} else {
console.log("Not changed");
return false;
}
}
render() {
return (
<View style={{ height: 40, backgroundcolor: "ff0000" }}>
<Text style={{ fontSize: 26, fontWeight: "bold" }}>
Total : {this.props.tt}
</Text>
</View>
);
}
}
You can create a TextBox component and split it from ImageComponent. In this way the images will not be bound to the state of the component rendering text, and you can safely change TextBox state and text preventing ImageComponent to re-render.
Edit:
Okei, now i get it. I think you have no possibility to do it like this.
Let's formalize the problem:
<Parent>
<Images calc={functionToCalc} />
<CalcText totalAmount={this.state.totalAmount}/>
</Parent>
functionToCalc is defined in in <Parent> and update parent state, something like:
const funcToCalc = () => {
// when condition occurs
this.setState({
totalAmount : computedAmount
})
}
The state of <Parent> has:
this.state : {
totalAmount: none
}
Whenever condition (buttonClick?) occurs in <Images/> you run functionToCalc update <Parent> state and rerender <CalcText>. Problem here is that also <Images> will be rerender again as all the parent component will be rerender.
this is one of the way to pass info from siblings in React.
You only have a possibility if you want to prevent <Images> rerendering.
Redux or similar libraries for centralize state
You will move the computed calculation in a Store and <CalcText/> will read that from there. <Images/> component will trigger an action modifying that state but won't listen to that so not being rerender.

Force FlatList to re-render after sorting data

I have a jobs app. In my JobsComponent, where I display the jobs, I have added a sort option that allows users to sort the list of jobs by different criteria. The flow is this: 1) I get the jobs from the server -> 2) the user sorts the jobs -> 3) the sorted list of jobs is re-rendered on the screen.
The problem is that step 3) is not working. The actual list of jobs is being sorted (I can see that in the logs), but my FlatList is not being re-rendered.
What I have tried
I have a flag, sortOrderChanged, set in my state. Whenever the user selects a sorting option, I change this flag in my componentDidMount() method:
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
and pass it to FlatList as extraData:
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
This does not help though. I have also tried sending the whole state to the FlatList and passing it to extraData, but it also didn't work. I assume the problem is that my data is not actually being changed, but sorted. However, I do not know how to force it to re-render. Can someone help me out, please?
Below is my JobsComponent.js:
function RenderJobs(props) {
var json = JSON.parse(props.jobsData);
var sort_array = [];
for (var _id in json) {
sort_array.push({
_id:_id,
jobtitle: json[_id].jobtitle,
company: json[_id].company,
duration_driving_value:json[_id].duration_driving.value,
duration_transit_value: json[_id].duration_transit.value,
duration_walking_value: json[_id].duration_walking.value,
duration_driving:json[_id].duration_driving.text,
duration_transit:json[_id].duration_transit.text,
duration_walking:json[_id].duration_walking.text,
date: json[_id].date,
formatedDescription: json[_id].formatedDescription,
applyUrl: json[_id].applyUrl
});
}
//sort the list based on user selection
if (props.sortOrder === props.sortArray[0]) {
sort_array.sort(function(x,y){return new Date(y.date) - new Date(x.date)});
}
else if (props.sortOrder === props.sortArray[1]) {
sort_array.sort(function(x,y){return x.duration_driving_value - y.duration_driving_value});
}
else if (props.sortOrder === props.sortArray[2]) {
sort_array.sort(function(x,y){return x.duration_transit_value - y.duration_transit_value});
}
else {
sort_array.sort(function(x,y){return x.duration_walking_value - y.duration_walking_value});
}
const renderJobItem = ({item}) => {
var durationCarApi, durationPublicTransportApi, durationWalkApi, formattedApiDate, formattedJobDescription;
//format data
return (
<Panel //custom component used to display each job
jobTitle={item.jobtitle}
company={item.company}
durationCar={durationCarApi}
durationTram={durationPublicTransportApi}
durationWalking={durationWalkApi}
dateAdded={formattedApiDate}
onPress={() =>
{
props.navigation.navigate('JobDetails', {
jobTitle: item.jobtitle,
company: item.company,
durationCar: durationCarApi,
durationTram: durationPublicTransportApi,
durationWalking: durationWalkApi,
jobDescription: formattedJobDescription,
applyUrl: item.applyUrl
})
}
}/>
);
}
//handle loading/error scenarios
return (
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
);
}
class Jobs extends Component {
constructor(props) {
super(props);
this.state = {
jobTitle: this.props.navigation.getParam('jobTitle', ''),
address: this.props.navigation.getParam('address', 'error'),
sortOrderChanged: false,
sortArray: [0,1,2,3],
selectedSortOrder: 1 //default is sort_driving
};
}
componentDidMount() {
handleSorting = (dataFromChild) => {
console.log('Sort order clicked: ' + dataFromChild);
this.RBSheet.close();
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
}
render() {
return(
<ScrollView contentContainerStyle={styles.bkg}>
<RenderJobs
jobsData={JSON.stringify(this.props.jobs.jobs)}
isLoading={this.props.jobs.isLoading}
errMess={this.props.jobs.errMess}
navigation={this.props.navigation}
sortOrder={this.state.selectedSortOrder}
sortArray={this.state.sortArray}
sortOrderChanged={this.state.sortOrderChanged}
/>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<RBSheet //custom component used to render the sorting options
ref={ref => {this.RBSheet = ref;}}
height={200}
duration={250}
customStyles={{
container: {
justifyContent: "center",
alignItems: "center"
}
}}>
<SortSheet //this is the child component used to render the sorting options
sortOrder={this.handleSorting}
sortArray={this.state.sortArray}/>
</RBSheet>
</View>
</ScrollView>
)
}
}
Move your data from local variable to state.
Or add forceUpdate after else.
The solution was to change the keyExtractor to item._id:
<FlatList
data={props.jobsData}
extraData={props.sortOrderProps}
renderItem={renderJobItem}
keyExtractor={(item, index) => item._id}
style={{marginTop: 10}}
/>

How to use checkbox in loop in react naive

How to use checkbox in loop with diffrent key when i use in loop and click on any one after than check all loop checkbox give me solution
You can create an array of solutions in state:
...
state.solutions = [
{value:false},
{value:false},
{value:false}
]
Create a changes event handler:
changeEvent = (ev, index) => {
let tmp_solution = [...state.solutions];
tmp_solutions[index].value = !tmp_solutions[index].value;
this.setState({solutions: tmp_solution})
}
Create the checkboxes render function:
const checkBoxes = this.state.solutions.map((index) =>{
return(
<CheckBox
value={this.state.solutions[index].value}
onValueChange={(ev) => this.changeEvent(ev, index)} key={index}
/>
)
});
Render all the checkboxes:
render() {
<View>
{checkBoxes}
</View>
}
...
sorry if there are errors
Well, suppose that you have a data, so you can use map to do that. In order to save the answers also you can use index in setSetate as below. I use the react-native-checkbox-heaven component to have the check Box:
import CheckBox from 'react-native-checkbox-heaven';
renderContentCheckBox=()=>{
console.log("[renderContent] your data", this.state.data);
return Object.keys(this.state.data).map(function (item, index) {
return (<View key={index} style={{ alignItems: 'flex-start', justifyContent: 'flex-start', width: Dimensions.get('window').width / 1.1, }}>
<CheckBox
label={item.title}
labelStyle={styles.labelStyle}
iconSize={28}
iconName='matMix'
checked={this.state.check1}
checkedColor='#44FF00'
uncheckedColor='#FFFFFF'
onChange={(val) => {(value) => { that.setState({ ["CheckBox" + index]: value, }); console.log("radio" + index, value);
}}
/>
</View>
);
}
}
Then you can use it inside the render:
render() {
return (<View>
{this.renderContentCheckBox(this)}
</View>
);
}

Precalculate component in react native

I have a piece of components which will be rendered after a user click on a button. However, these components involving too many calculation. It takes 3~5 seconds to complete it.
I want to compute it in advance and store into my state.
So I can show it on the fly.
But somehow, the content always returns null to my state.
Any idea?
prepopulateDetail(){
let displayedSources = {}
this.setState({setState: (
<View>
{
[... new Set(this.props.definition)].map(wordDef => {
if (wordDef.content.length >= 0 && displayedSources.hasOwnProperty(wordDef.source) === false) {
displayedSources[wordDef.source] = true
return (
<View style={styles.definitions} key={guid()} i={wordDef.source}>
<Badge containerStyle={{
backgroundColor: PRIMARY_BG,
width: 100,
marginTop: 5,
marginBottom: 5,
borderRadius: 0
}}>
<Text style={{color: 'white'}}>{wordDef.source.toUpperCase()}</Text>
</Badge>
{
wordDef.content.map((content, j) => {
let data = JSON.parse(content)
return this.displayWordDefinition(data)
})
}
</View>
)
}
})
}
</View>
)}, this.showState)
}