React Native conditionally setting state - react-native

I have an array of "favorite" listings per user. I want to set a state variable depending on if it is rendering a favorite item or a regular (non-favorite) item. This is a little heart icon that is either filled or unfilled. It is incorrectly setting the state on first render. Correctly sets it after refresh.. I'm doing something wrong and have tried many things to no avail so if someone could just look at this code and tell me if you see any glaring flaws. If not then I will look elsewhere for the problem.
Behavior when app loads: I am doing console.log just after the state is set to show whether it's a favorite or not, and the contents of the favorite state variable (an image url, but in the console it shows it as either 21 for non-fav, or 22 for a fav). I can see that it is correctly pulling the array of favorites, and correctly identifying those that are and are not favorited (1 means its a favorite). It is however not setting the state variable correctly. Furthermore, it IS setting it correctly for only some of the listings. Currently I have all listings marked as a favorite, and it only messes up the first 10. After that they all set right. MORE bizarre, is upon refreshing the screen, it correctly sets all of them.
MainHeader.js (pulling data from db, setting initial array of favorite listings, and passing it to the messagesScreen component)
const [favsArray, setFavsArray] = useState("");
useEffect(() => {
lookupUser()
.then((snapshot) => {
if (snapshot.hasChildren()) {
snapshot.forEach(function(childSnapshot) {
let favs = childSnapshot.child("favorites").val();
setFavsArray(favs);
})
}
})
.catch((error) => {console.error('Error:', error)});
}, []);
return (
<NavigationContainer>
<View style={styles.headerContainer}>
<Image
style={styles.image}
source={require("../assets/newheader4.png")}
/>
</View>
<Tab.Navigator
tabBarOptions={{
activeTintColor: "blue",
inactiveTintColor: "black",
style: {},
tabStyle: {
width: "auto",
backgroundColor: "#e0d5f3",
borderTopWidth: 3,
borderBottomWidth: 3,
borderRightColor: "gray",
},
labelStyle: {
fontSize: 14,
fontWeight: "bold",
},
scrollEnabled: true,
}}
>
<Tab.Screen name="All Deals" children={()=><MessagesScreen favsArray={favsArray} setFavsArray={setFavsArray}/>} />
</Tab.Navigator>
</NavigationContainer>
MessagesScreen, receives favsArray and renders a FlatList with component Card which it feeds favsArray to.
<FlatList
data={messagesShow}
keyExtractor={(messagesShow) => messagesShow.id.toString()}
renderItem={({ item }) => (
<Card
price={item.currentPrice}
title={item.title}
image={item.image}
posted={item.postedDate}
discAmount={item.discountAmount}
discType={item.discType}
expiration={item.expiration}
promoCode={item.promoCode}
affLink={item.amzLink}
indexStore={item.indexStore}
store={item.store}
favsArray = {favsArray}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
onRefresh={() =>
db.ref('deals').once('value', (snapshot) =>{
let testData = [];
snapshot.forEach((child)=>{
// if (child.val().hasOwnProperty('title')){
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
currentPrice: child.val().price,
discountAmount: child.val().discAmt,
discType: child.val().discType,
promoCode: child.val().promoCode,
expiration: child.val().expDate,
postedDate: child.val().postDate,
image: { uri: child.val().imageLink},
amzLink: child.val().affLink,
category: child.val().category,
indexStore: child.val().indexStore,
store: child.val().store
})
// }
checkMessages(testData);
})
})
.then()
.catch((error) => {console.error('Error:', error)})
}
/>
Card component, this is in a FlatList where favsArray is passed as a prop (correctly verified by console), along with the individual listing data. If it finds the listing in the fav array, it should set to HeartFilled (1), if not set to HeartEmpty (0).
let test = [];
test = favsArray.split(',');
let isFav = 0;
let found = test.find(function (element) {
return element == indexStore;
});
if (found != undefined){
isFav = 1;
}
const [heartFilled, setHeartFilled] = useState( isFav == 1 ? require('../assets/heartFilled.png') : require('../assets/heartEmpty.png'));
console.log(isFav + ' ' + heartFilled);
Looking at my console, you can see it correctly shows each listing as a favorite, but for the first 10 listings it sets the state to the wrong image (21, shown in red). These should all be 22.

Related

React Native - Appended component in hook not responding as expected

I am working on a project that uses Google autocomplete to set locations. The project allows users to set pickup and destination location, and then they can also enter stop-by places up to additional 3, making it a total of 5.
Here's my sample code:
const placesRef = useRef([]);
const [stopspots, setStopSpots] = useState([]);
const [state, setState] = useState({
defaultPlacesInput: 'flex',
//and others
});
useEffect(() => {
placesRef.current = placesRef.current.slice(0, 5);
}, []);
const placesComponent = (i, placeholder) => {
return (<PlacesFrame key={i}>
...
<GooglePlacesAutocomplete
placeholder={placeholder}
minLength={2}
ref={el => placesRef.current[i] = el}
onPress={(data, details = null) => {
placesRef.current[i]?.setAddressText(data?.structured_formatting?.main_text);
setState({...state, defaultPlacesInput: 'flex'})
}}
enablePoweredByContainer={false}
fetchDetails
styles={{
textInput: [styles.input1,{paddingLeft:30}],
container: [styles.autocompleteContainer,{display:placesRef.current[i]?.isFocused() ? 'flex' : state.defaultPlacesInput}],
listView: styles.listView,
listView: styles.listView,
row: styles.row,
predefinedPlacesDescription: {
color: '#1faadb',
},
}}
query={{
key: GOOGLE_PLACES_API_KEY,
language: profile.language,
components: 'country:' + profile.iso,
}}
textInputProps={{
//value: '',
onChangeText: alterOtherFields
}}
renderRow={(data) => <PlaceRow data={data} />}
/>
...
</PlacesFrame>)
}
const stopByLocation = () => {
var counter = stopspots.length, obj = placesComponent(counter + 2, 'Drop off location');
setStopSpots([...stopspots, {
id: counter,
place: obj
}
])
}
And here is how the autocomplete component is rendered
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
</View>
...
)
The output look like this
Everything works perfect when I call the placesComponent() function directly. But like I mentioned earlier, I want the users to be able to add up to 3 additional stop by locations, and because it is optional, additional fields is added by appending to hook, and then rendered. the code looks like this.
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
//This will append more placed fields
{stopspots != '' ?
stopspots.map((item : {}) => ((item.place)))
: null}
<ClickableButton>
<TouchableOpacity activeOpacity={0.6} onPress={() => stopByLocation()}><AddPlaces><AntDesign name="plus" size={10} color="#444" /> Add</AddPlaces></TouchableOpacity>
</ClickableButton>
</View>
...
)
The outcome looks like this
I observed that each component binded to the hooks takes the early properties, and does not effect additional changes. While the first two fields rendered by calling the function directly does.
When I make changes to state.defaultPlacesInput (observe this in styles property of GooglePlacesAutocomplete), the changes only effect on the two components called directly.
Is there a module, or a systematic way to append the renderer function call, without using useState hooks to append the 3 additional fields?
Is it possible to expose stored properties in useState hooks to respond as the other two which observe the state changes? If yes, how?
Any contribution, suggestion will be accepted

React Native useState not working on a AsyncStorage fuction

My code is the following:
function ArScreen({ navigation }) {
const [open_tabLugar, setOpen_tabLugar] = useState(false);
const [titulo, setTitulo] = useState('');
const updateAsyncStorage = async () => {
AsyncStorage.getItem('data')
.then((res) => {
//console.log({ res : JSON.parse(res) });
console.log(JSON.parse(res).open_tabLugar);
console.log(JSON.parse(res).titulo);
setOpen_tabLugar(JSON.parse(res).open_tabLugar);
setTitulo(JSON.parse(res).titulo);
console.log(titulo);
console.log(open_tabLugar);
});
}
useEffect(() => {
updateAsyncStorage()
const interval=setInterval(()=>{
updateAsyncStorage()
},10000)
return()=>clearInterval(interval)
}, []);
Output:
console.log(JSON.parse(res).open_tabLugar); ====== true
console.log(JSON.parse(res).titulo); ===== hello
after setOpen_tabLugar and setTitulo
console.log(titulo); ===== ""
console.log(open_tabLugar); ===== false
Why does that happen? I am changing the state (or trying to change it), but it doesn't work. What am I doing wrong?
If you notice, I have a bucket that updates every 10 seconds in useEffect. But, that's not the problem, that works fine (in the update I retract myself, that is the problem, only I don't know how to solve it). The problem is in the change of states.
What am I doing wrong?
UPDATE 1:
If I put this:
setTitulo("HEEEEEEEEEEEEY");
console.log(titulo);
Doesn't work, keeps coming up blank.
UPDATE 2:
I think the problem is inside useEffect(() => {
Because I just created a button to run updateAsyncStorage manually and it did work.
<TouchableOpacity
style={{width: 50, borderRadius: 50, height: 50, backgroundColor: 'white', alignItems: 'center', justifyContent: 'center'}}
onPress={() => updateAsyncStorage()} >
<Text style={{color: 'black', fontSize: 20, fontWeight: 'bold'}}>Load</Text>
</TouchableOpacity>
The button does work fine. Yes the state changes. How do I make it work automatically with useEffect or another alternative?
UPDATE 3:
My new code:
const [open_tabLugar, setOpen_tabLugar] = useState(false);
const [tabLugar, setTabLugar] = useState(false);
useEffect(() => {
AsyncStorage.getItem('data')
.then((res) => {
//console.log({ res : JSON.parse(res) });
console.log(JSON.parse(res).open_tabLugar);
console.log(JSON.parse(res).tipo);
console.log(JSON.parse(res).titulo);
console.log(JSON.parse(res).descripcion);
console.log(JSON.parse(res).distance);
console.log(JSON.parse(res).imagen);
console.log(JSON.parse(res).imagen_1);
// string to boolean in AsyncStorage
setOpen_tabLugar(JSON.parse(res).open_tabLugar);
setTipo(JSON.parse(res).tipo);
setTitulo(JSON.parse(res).titulo);
setDescripcion(JSON.parse(res).descripcion);
setDistance(JSON.parse(res).distance);
setImagen(JSON.parse(res).imagen);
setImagen_1(JSON.parse(res).imagen_1);
if(open_tabLugar){
console.log('-------------- OPEN MODAL --------------');
setTabLugar(true);
setOpen_tabLugar(false);
console.log(open_tabLugar);
}
});
},[open_tabLugar, tipo, titulo, descripcion, distance, imagen, imagen_1]);
return(
...
<Modal animationType="slide" transparent={true} visible={tabLugar}>
<View style={styles_modal.modalViewTabL}>
<TouchableOpacity onPress={() => setTabLugar(!tabLugar)} style={[styles_modal.button, styles_modal.buttonClose]} >
<Text style={styles_modal.buttonClosetextStyle}>X</Text>
</TouchableOpacity>
<Text style={styles_modal.place_description} numberOfLines={7}>
La Casa de la Cultura es una de las más importantes obras de arte de la ciudad de Madrid.
</Text>
</View>
</Modal>
That works halfway, the problem is that it runs infinitely, it has no end. That started when I put: [open_tabLugar, type, title, description, distance, image, image_1]
After I open my modal with setTabLugar(true); I write setOpen_tabLugar(false);
That should prevent it from opening it again, but then again it runs as true, infinitely.
UPDATE 4:
My error is in the following snippet. Since it runs endlessly. If I remove it the loop stops. How do I fix that problem? Remember that I want the modal to open, but not infinitely in a loop.
if(open_tabLugar){
console.log('-------------- OPEN OPEN OPEN TAB LUGAR --------------');
setTabLugar(true);
setOpen_tabLugar(false);
console.log(open_tabLugar);
}
Unfortunately, hooks cannot be used this way. After the set operation is done, the rendering process takes place and the new value you have assigned will then be available. If you have processes that will work according to these values, you should create useEffect for this and give the variable you will use to the dependency part.
For example, you can print the current values in the following way.
useEffect(() => {
console.log(titulo);
console.log(open_tabLugar);
},
[titulo, open_tabLugar],
);
You can show it through a real example for better help.

How can I use react-native-redash to translateZ with react-native-reanimated v2?

I have a list of cards. I am selecting a sublist from the cards and transforming their x and y location to another point in the screen. I am trying to update their zIndex using a shared variable that updates based upon the order I select them. However, this works fine on Android but not on iOS. (Please look at the image for visual clarification).
Visual representation of ideal scenario
This is a simplified code of what I have.
This is the render component.
<View>
<TapGestureHandler onGestureEvent={tapGestureEvent}>
<Animated.View style={[cardStyle]}>
<Animated.View style={[frontImageStyle]}>
<Image
source={require('../../../assets/frontImage.png')}
style={styles.card}
/>
</Animated.View>
<Animated.View style={[backImageStyle]}>
<Image
source={require('../../../assets/backImage.png')}
style={styles.card}
/>
</Animated.View>
</Animated.View>
</TapGestureHandler>
</View>
This is my gestureTapHander where I am updating the x and y locations and also updating the index for each card based upon the total cards drawn so far, this total cards drawn is a shared variable and defined in my parent component.
const tapGestureEvent = useAnimatedGestureHandler({
onStart: (event, context) => {
context.y = yPosition.value; // It seems I am not even using this.
},
onActive: (event, context) => {},
onEnd: (event, context) => {
newYPosition.value = someValue
cardDrawnIndex.value = cardsDrawn.value;
cardsDrawn.value += 1;
},
});
This is the style that I have on the outerComponent.
cardDrawnIndex = useSharedVariable(-1);
const cardStyle = useAnimatedStyle(() => {
return {
transform: [
{translateX: someXValue},
{translateY: someYValue},
{rotateZ: `${someVariable.value}deg`},
{rotateY: `${someVariable.value}deg`},
{translateX: wasToChangeAnchorPointForMyZRotation}, // I don't understand why there is a need to do it and why isn't it as simple as saying, rotate with this anchor point.
{translateY: wasToChangeAnchorPointForMyZRotation},
],
zIndex: cardDrawnIndex.value,
// elevation: drawnCardZPosition.value, // Also tried this, it works only for Android, not for iOS.
};
});
I see there is a transformZ utility in react-native-redash but I am unable to figure out how to use it correctly for my use case. Or alternatively, if I can fix this zIndex issue on iOS, (maybe it has to do something with creating new instances of my card.)

React-admin: Can I truncate a <TextField> within a <List>?

Is there a way to truncate text returned by the <TextField> and show ...?
See title column within image below (I want to truncate the title after 20 chars).
Is there a specific prop? Unfortunately, I didn't find any clue within the react-admin doc.
Thank you in advance
You can actually manipulate any field outside a <List>,and then get the <Datagrid> to render that field, as long as it's iterative.
const CustomTitleField = ({ record }) => {
// "record" is a prop received from the Datagrid
let str = record.title;
return record ? (
{/* If length is greater than 20 characters, slice and add ellipsis.*/}
<span>{str.length > 20 ? str.slice(0, 20) + "..." : str}</span>
) : null;
};
// Then, within your list, do this...
export const CommentList => (
<List {...props}>
<Datagrid>
<CustomTitleField /> // this should render with your truncate logic
// ...
</Datagrid>
</List>
);
Let me know how it goes, after try this out!!
You can use the cellClassName to customize the cell style inside a Datagrid.
https://marmelab.com/react-admin/Fields.html#styling-fields
I had a similar question and here is what works for me.
const useStyles = makeStyles((theme) => ({
tableHeader: {
fontWeight: "bold",
textTransform: "capitalize",
},
tableCell: {
maxWidth: 300,
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}
}));
const MyList = ({ ...props }) => {
const classes = useStyles();
return (
<List
{...props}
>
<Datagrid>
<TextField
headerClassName={classes.tableHeader}
cellClassName={classes.tableCell}
source="detail"
label="Detail"
/>
</Datagrid>
</List>
);
};

React Native: Extra empty space on top of the screen

I have a bug where a user clicks on a survey and then opens up what is called supporting information that expands the UI further, then the user selects his or her answer and clicks on the NEXT QUESTION button, at that point the whole top part of the screen drops down exposing this huge gap. This is the code I believe governs all that behavior:
class BallotSurveyDetails extends PureComponent {
componentDidUpdate(prevProps) {
if (prevProps.currentWizardPage !== this.props.currentWizardPage) {
this.scroll.props.scrollToPosition(0, 0, true);
}
}
render() {
const {
currentWizardPage,
selectedSurvey,
handleNextQuestionButtonPress,
handleResponseChanged,
loading,
responses,
handleSubmitButtonPress,
saving,
wizardPages
} = this.props;
if (!saving && loading) {
return <Loading />;
}
const isWizard = selectedSurvey.Layout !== "Wizard";
const isList = selectedSurvey.Layout !== "List";
const displayNextQ = isWizard && currentWizardPage < wizardPages;
const displaySubmit =
isList || (isWizard && currentWizardPage === wizardPages);
const sortedGroups = (selectedSurvey.QuestionGroups || []).sort(
(a, b) => a.Order - b.Order
);
const wizardGroup = isWizard ? sortedGroups[currentWizardPage - 1] : null;
return (
<SafeAreaView style={styles.container}>
{isWizard && wizardPages.length > 1 && (
<Card style={styles.pagination}>
<RadioPagination
numberOfPages={wizardPages}
currentPage={currentWizardPage}
/>
</Card>
)}
<KeyboardAwareScrollView
showsVerticalScrollIndicator={false}
extraScrollHeight={45}
innerRef={ref => {
this.scroll = ref;
}}
enableOnAndroid={true}
contentContainerStyle={{ paddingBottom: 90 }}
>
<View style={styles.headerContainer}>
<Text style={styles.ballotTitle}>{selectedSurvey.Name}</Text>
<Text style={styles.ballotSubtitle}>
{selectedSurvey.Description}
</Text>
</View>
{isList &&
What I tried to do to resolve this was add automaticallyAdjustContentInsets={false} inside the KeyboardAwareScrollView, did nothing to resolve the bug. Any ideas anyone?
I'm not sure what's causing this for you, but here are a few things that have corrected similar problems I've had in the past:
It can help to wrap every screen in a container with flex:1.
I had a similar case with conditionally rendering a search bar above a FlatList and I used this to fix it:
I added this to the top of my file.
import { Dimensions, other stuff you need} from 'react-native';
const deviceHieght = Dimensions.get('window').height;
and then I wrapped my FlatList in a view like this
<View style={this.state.showBar === false ? styles.containFlatlist : styles.containSearchFlatlist}>
and this is the styling it was referencing
containFlatlist: {
height: deviceHieght
},
containSearchFlatlist: {
height: deviceHieght-100
},
In a different similar case I had an issue like this with a screen that displayed photos on click within a scrollview. In that case I did this:
<ScrollView
ref={component => this._scrollInput = component}
>
Then in componentDidMount I put
setTimeout(() => {
this._scrollInput.scrollTo({ x: 0, animated: false })
}, 100)
I was also using react navigation in this case so I also did
return(<View style={styles.mainFlex}>
<NavigationEvents
onWillBlur={payload => this._scrollInput.scrollTo({x:0})}
/>
Followed by the rest of my code.
I hope one of those helps. Given that you're also dealing with a scrollview, my best guess is that the third fix is most likely to work in your situation.
So the appear is with this code snippet here:
componentDidUpdate(prevProps) {
if (prevProps.currentWizardPage !== this.props.currentWizardPage) {
this.scroll.props.scrollToPosition(0, 0, true);
}
}
In particular, this.scroll.props.scrollToPosition(0, 0, true);. In removing the whole component lifecycle method, the bug went away.