Dispatch one action multiple times - reactjs

I m trying to fetch list of artist from asyncStorage and assign that artist to state name "getArtist" (result is in string so i need to parse to convert into array, it look something like this ["abc", "xyz" , "uvw"]). After that i m mapping it and passing id to AlbumList.js. In AlbumList i m calling a redux-action "searchArtistAlbum(id)" passing id in it and getting list of all albums of that artist, but as i have console.log("showSearchData ===", showSearchData) for artist name, there are 3 artist so it should be called 3 times i.e artist1, artist2, artist3 but its been called like 1.call artist1, artist1, artist1 , 2.call artist2, artist2, artist2 and 3.call artist3, artist3, artist3 and atlast values are same i.e artist3, artist3, artist3. and if i try to pass "showSearchData" as variable in useEffect([showSearchData]) it go into infinite loop.
Home.js
import React, {useEffect, useState} from 'react';
import {View, Text, ScrollView, AsyncStorage} from 'react-native';
import {connect} from 'react-redux';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import Label from '../components/Label';
import HomeHeader from '../components/HomeHeader';
import AlbumList from '../components/AlbumList';
export const Home = ({ navigation, mainNavigation}) => {
const [getArtist, setGetArtist] = useState([])
useEffect(() => {
getTokenResult()
},[])
const getTokenResult = async () => {
let getResult = await AsyncStorage.getItem('access_token')
let getCat = await AsyncStorage.getItem('categories')
let getArt = await AsyncStorage.getItem('artists')
setGetArtist(JSON.parse(getArt))
console.log("getResult ", getResult)
console.log("getArt ", typeof JSON.parse(getArt), JSON.parse(getArt))
console.log("getCat ", getCat)
if (!getResult || !getResult == undefined && !getResult == null){
mainNavigation.navigate('Login')
} else if(!getArt || !getArt === undefined && !getArt == null){
mainNavigation.navigate('Artist')
} else if(!getCat || !getCat === undefined && !getCat == null){
mainNavigation.navigate('Category')
}
}
return (
<ScrollView
style={{flex: 1, backgroundColor: 'rgb(30,30,30)', padding: 15}}>
<HomeHeader />
<View
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
}}>
</View>
{
getArtist && getArtist !== undefined && getArtist.length > 0 ?
getArtist?.map((item, index) => {
return (
<AlbumList id={item && item !== undefined ? item : ""} key={index} />
)
})
:
null
}
</ScrollView>
);
};
const mapStateToProps = state => ({
getToken: state.getToken,
});
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
AlbumList.js
import React, {useEffect} from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
ScrollViewComponent,
} from 'react-native';
import {searchArtistAlbum} from '../redux/actions/searchAction';
import AlbumCard from './AlbumCard';
import {connect} from 'react-redux';
function AlbumList({id, searchArtistAlbum, showSearchData}) {
const arr = [1, 2, 3, 4, 5];
useEffect(() => {
searchArtistAlbum(id);
}, []);
console.log('showSearchData === ',showSearchData && showSearchData !== undefined && showSearchData?.items && showSearchData?.items[0]?.artists[0]?.name);
return (
<View style={styles.contView}>
<Text style={styles.textView}>{showSearchData && showSearchData !== undefined && showSearchData?.items !== undefined ? showSearchData?.items[0]?.artists[0]?.name : ""}</Text>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{showSearchData && showSearchData !== undefined
? showSearchData?.items?.map((item, index) => {
return <AlbumCard image={item && item !== undefined ? item?.images[1]?.url : ""} key={index} />;
})
: null}
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
textView: {
color: '#fff',
fontSize: 23,
fontWeight: '700',
},
contView: {
paddingTop: 20,
width: '100%',
},
});
const mapStateToProps = state => ({
showSearchData: state.showSearchData,
});
const mapDispatchToProps = {searchArtistAlbum};
export default connect(mapStateToProps, mapDispatchToProps)(AlbumList);
AlbumCard.js
import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';
function AlbumCard({image}) {
return (
<View style={styles.contView}>
<Image
style={styles.img}
source={{
uri:image && image !== undefined ? image : 'https://images4.alphacoders.com/476/thumb-350-47698.png',
}}
/>
<View style={styles.textView}>
<Text style={styles.textView1}>AlbumCard</Text>
<Text style={styles.textView2}>Beast Playlist</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
img: {
width: 150,
height: 150,
},
contView:{
paddingVertical: 20,
marginRight: 20,
},
textView:{
marginVertical: 10
},
textView1:{
fontSize:14,
fontWeight: '700',
color:'#fff'
},
textView2:{
fontSize:14,
color:'rgb(150,150,150)',
paddingVertical: 3,
}
});
export default AlbumCard;

Related

TypeError: text.toLowerCase is not a function. (In 'text.toLowerCase()', 'text.toLowerCase' is undefined)

I am doing a project using MERN STACK in this I faced the above error.
ProductContainer.js
import React, { useState, useEffect } from 'react'
import { View, StyleSheet, ActivityIndicator, FlatList, Text} from 'react-native'
import { Container, Header, Icon, Item, Input } from 'native-base';
import ProductList from './ProductList';
import SearchedProduct from './SearchedProducts';
const data = require('../../assets/data/products.json');
const ProductContainer = () => {
const [products, setProducts ] = useState([]);
const [productsFiltered, setProductsFiltered] = useState([]);
const [focus, setFocus] = useState();
useEffect(() => {
setProducts(data);
setProductsFiltered(data);
setFocus(false);
return () => {
setProducts([])
setProductsFiltered([])
setFocus()
}
}, [])
const SearchProduct = (text) => {
setProductsFiltered(
products.filter((i) => i.name.toLowerCase().includes(text.toLowerCase()))
);
};
const openList = () => {
setFocus(true);
};
const onBlur = () => {
setFocus(flase);
};
return (
<Container>
<View style = {{ flexDirection: "row"}}>
<Input
width = "100%"
variant = "rounded"
placeholder="Search"
onFocus={openList}
onChangeText={(text) => SearchProduct(text)}
/>
</View>
{focus == true ? (
<SearchProduct
productsFiltered={productsFiltered}
/>
) : (
<View style={styles.container}>
<Text>Product Container</Text>
<View style={styles.listContainer}>
<FlatList
data={products}
numColumns={2}
renderItem={({item}) => <ProductList
key={item.brand}
item={item}/>}
keyExtractor={item => item.brand}
/>
</View>
</View>
)}
</Container>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default ProductContainer
SearchedProducts.js
import React from 'react';
import { View, StyleSheet, Dimensions} from 'react-native'
import { Content, Left, Body, ListItem, Thumbnail, Text } from 'native-base';
const SearchedProduct = (props) => {
const { productsFiltered } = props;
return(
<Content >
{productsFiltered.length > 0 ? (
productsFiltered.map((item) => (
<ListItem
key={item._id.$oid}
avatar
>
<Left>
<Thumbnail
source={{uri: item.image ?
item.image : 'https://cdn.pixabay.com/photo/2012/04/01/17/29/box-23649_960_720.png'
}}
/>
</Left>
<Body>
<Text>{item.name}</Text>
<Text note>{item.description}</Text>
</Body>
</ListItem>
))
) : (
<View style={styles.center}>
<Text style={{ alignSelf: 'center' }}>
No products match the selected criteria
</Text>
</View>
)}
</Content>
);
};
const styles = StyleSheet.create({
center: {
justifyContent: 'center',
alignItems: 'center',
height: 100
}
})
export default SearchedProduct;
Please can anyone help me to solve this error .If you want I will provide other details of my code.
Thanks in advance
Not sure but you are using SearchProduct name as a component and at the same time as a method. Maybe try to rename changing either component name or method name to see if it works.

Information not passing into component

In the code sample below, I am trying to show results.length but it does not show. The information is coming from the App.js screen, a filter function which I have pasted below. When I call <ResultsList reults={filtersResultsByPrice('$')} title='Cost Effective'/>, there may not be anything that is actually being sent into the ResultsList.js screen.
Here is the repo:
https://github.com/elaitman1/React-Native-Hooks-Project-1
ResultsList.js:
import React from 'react'
import {Text, View, StyleSheet} from 'react-native'
const ResultsList = ({title, results}) => {
if (results == null){
return null
}else{
return (
<View>
<Text style={styles.title}>{title}</Text>
<Text style={styles.title}>results: {results.length}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
title:{
fontSize: 18,
fontWeight: 'bold'
}
})
export default ResultsList
App.js:
import { StyleSheet, Text, View } from 'react-native';
import SearchBar from './src/Components/SearchBar'
import React, {useState} from 'react'
import useResults from './src/hooks/useResults'
import ResultsList from './src/Components/ResultsList'
export default function App() {
const [term, setTerm] = useState('')
const [searchApi, results, errorMessage] = useResults()
const filtersResultsByPrice = (dollarSign) =>{
return results.filter((result) => {
return result.price === dollarSign
})
}
return (
<View>
<SearchBar
term={term}
onTermChange={setTerm}
onTermSubmit={() => searchApi(term)}
/>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text>We have found {results.length} results</Text>
<ResultsList reults={filtersResultsByPrice('$')} title='Cost Effective'/>
<ResultsList reults={filtersResultsByPrice('$$')} title='Bit Pricier'/>
<ResultsList reults={filtersResultsByPrice('$$$')} title='Big Spender'/>
</View>
);
}
const styles = StyleSheet.create({
container: {
borderColor: 'black',
height: 40,
borderWidth: 3,
marginTop: 40
},
});

Component not updating/rerendering when redux toolkit state changes

I'm using redux toolkit to manage state and am finding that the components are not updating when changes are made to the state.
I do see though, that when I listen to changes in my redux state within a useEffect that it will trigger the callback, meaning it is aware that the redux state has changed but the component is not rerendering.
Reading other questions it seems like a lot of the issues were about mutating the state, but I do know that using redux tool kit allows you to write "mutable" code and this is handled by Immer. Not sure if this is an issue that applies here...
My Slice:
const initialState = {
watchlists: []
}
export const watchlistsSlice = createSlice({
name: 'watchlists',
initialState,
reducers: {
updateWatchlists: (state, { payload }) => {
state.watchlists = payload;
},
},
})
export const { updateWatchlists } = watchlistsSlice.actions;
export default watchlistsSlice.reducer;
This is the component that calls the function that changes the state:
import React from 'react';
import { StyleSheet, View, Image } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import Text from '../core/Text';
import Pressable from '../core/Pressable';
import { AddIcon } from '../core/Icons';
import { formatPrice, formatPercent } from '../../utils/formatNumber';
import { shortenLongText } from '../../utils/formatText';
import { updateWatchlists } from '../../redux/watchlistsSlice';
import { addToWatchlist } from '../../storage/watchlists';
export default function ListItem({data, theme, navigation, watchlistID }) {
const dispatch = useDispatch();
const { currency } = useSelector(state => state.userPreference)
const addCryptoToWatchlist = async () => {
if (watchlistID) {
addToWatchlist(watchlistID, {
slug: data.slug,
})
.then(result => dispatch(updateWatchlists(result)))
.catch(err => console.log(err))
} else {
console.log('not ready yet')
}
}
return (
<Pressable
onPress={() => navigation.navigate('CoinDetails', {
data,
})}
>
<View style={styles.searchResult}>
<View style={styles.nameContainer}>
<Image style={styles.image} source={{uri: data.logo}} />
<View style={{marginLeft: 15}}>
<Text type={"big"} size={18} theme={theme.text}>{data.symbol}</Text>
<Text type={"regular"} size={14} theme={theme.text} style={{paddingTop: 1}}>{shortenLongText(data.name,25)}</Text>
</View>
</View>
<View style={styles.rightContainer}>
<View style={styles.priceContainer}>
<Text type={"big"} theme={theme.text}>{formatPrice(data.price, currency)}</Text>
<Text type={"big"} theme={data.direction === 'up' ? theme.percent.up : theme.percent.down}>{formatPercent(data.percent_change_24h)}</Text>
</View>
<Pressable onPress={() => addCryptoToWatchlist()}>
<AddIcon
size={30}
/>
</Pressable>
</View>
</View>
</Pressable>
)
}
const styles = StyleSheet.create({
searchResult: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 30,
},
nameContainer : {
flexDirection: 'row',
alignItems: 'center',
},
rightContainer: {
flexDirection: 'row',
alignItems: 'center',
},
priceContainer: {
alignItems: 'flex-end',
marginRight: 20
},
image: {
width: 28,
height: 28,
}
})
This is one the components that I expect to rerender when the state changes:
The useEffect does get trigged so the component is recognizing a change in state, but the component does not rerender.
I dont know if this is insightful, but the data in the state is an array of objects and in this case a single property one of the objects is getting changed.
import React, { useState, useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import { useSelector } from 'react-redux';
import GetStarted from './GetStarted';
import AddCryptoCard from './AddCryptoCard';
import CardList from './CardList';
import Text from '../core/Text';
export default function WatchlistCardsSection({ setModalVisible, navigation }) {
const { theme } = useSelector(state => state.userPreference);
const { watchlists } = useSelector(state => state.watchlists)
const { cryptoData } = useSelector(state => state.cryptoData);
const [ watchlistCryptoDataLoaded, setWatchlistCryptoDataLoaded ] = useState(false);
const checkIfWatchlistDataLoaded = () => {
const watchlistNames = watchlists.map(watchlist => watchlist.name);
const checkIfLoaded = cryptoData.map(data => watchlistNames.some(name => data.tags.includes(name))).includes(true);
setWatchlistCryptoDataLoaded(checkIfLoaded);
}
useEffect(() => {
checkIfWatchlistDataLoaded();
},[cryptoData])
useEffect(() => console.log("watchlist updated"), [watchlists])
return (
watchlists &&
watchlists.length === 0 ?
<GetStarted
setModalVisible={setModalVisible}
/>
:
watchlists.filter(item => item.viewOnHome).map(watchlist => (
watchlist.data.length === 0 ?
<AddCryptoCard
key={watchlist.id}
id={watchlist.id}
name={watchlist.name}
navigation={navigation}
/>
:
watchlistCryptoDataLoaded ?
<View
key={watchlist.id}
style={styles.sectionContainer}
>
<Text style={{paddingLeft: 20}} type={'big'} size={24} theme={theme.text}>{watchlist.name}</Text>
<CardList
name={watchlist.name}
config={{type: 'price'}}
navigation={navigation}
/>
</View>
: null
))
)
}
const styles = StyleSheet.create({
sectionContainer: {
flex: 1,
marginTop: 25,
}
})

React native [TypeError: undefined is not an object (evaluating 'iter[Symbol.iterator]')] error

So I have a an image selector that when I press, it launches the phone image library and then when I select the image it is supposed to fill that image selector but every time i select an image, I get this error
[TypeError: undefined is not an object (evaluating 'iter[Symbol.iterator]')]
This is how my code looks like this is the App file
import React, {useState} from 'react'
import { Button, StyleSheet, Text, View } from 'react-native'
import Screen from './app/components/Screen'
import ImageInputList from './app/components/ImageInputList'
export default function App() {
const [imageUris, setImageUris] = useState();
const handleAdd = (uri) => {
setImageUris([...imageUris, uri])
}
const handleRemove = uri => {
setImageUris(imageUris.filter(imageUri => imageUri !== uri))
}
return (
<Screen>
<ImageInputList imageUris={imageUris} onAddImage={handleAdd} onRemoveImage={handleRemove} />
</Screen>
)
}
And then this is my 2nd component the "imageInputList"
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import ImageInput from './ImageInput'
export default function ImageInputList({imageUris = [], onRemoveImage, onAddImage}) {
return (
<View style={styles.container}>
{imageUris.map((uri) => (
<View key={uri} style={{marginRight: 10}}>
<ImageInput
imageUri={uri}
onChangeImage={() => onRemoveImage(uri)}
/>
</View>
))}
<ImageInput onChangeImage={(uri) => onAddImage(uri)} />
</View>
)
}
And lastly, this is the imageInput component
import React, {useEffect} from 'react'
import { Image, StyleSheet, TouchableWithoutFeedback, Alert, View } from 'react-native'
import {MaterialCommunityIcons} from "#expo/vector-icons"
import * as ImagePicker from "expo-image-picker"
export default function ImageInput({imageUri, onChangeImage}) {
useEffect(() => {
requestPermission();
}, [])
const requestPermission = async () => {
const {granted} = await ImagePicker.requestMediaLibraryPermissionsAsync();
if(!granted) alert("Allow this app to access your image library")
}
const handlePress = () => {
if(!imageUri) selectImage();
else Alert.alert("Delete",
"Are you sure you want to delete this image?",
[{text: "Yes", onPress: onChangeImage(null)}, {text: "No"},])
};
const selectImage = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.5,
});
if(!result.cancelled) onChangeImage(result.uri);
}
catch (error) {
console.log("Something went wrong in your code", error)
}
}
return (
<TouchableWithoutFeedback onPress={handlePress}>
<View style={styles.container} >
{!imageUri && <MaterialCommunityIcons name="camera" size={40} color="grey" />}
{imageUri && <Image source={{uri: imageUri}} style={styles.image} />}
</View>
</TouchableWithoutFeedback>
)
}
const styles = StyleSheet.create({
container: {
backgroundColor: "#f1f1f1",
borderRadius: 15,
marginTop: 40,
alignItems: 'center',
justifyContent: 'center',
overflow: "hidden",
width: 100,
height: 100,
},
image: {
height: "100%",
width: "100%",
}
})
I know the issue has something to do with "handleAdd" or the "onChangeImage" part but I do not know exactly how to fix them. Thanks in Advance.
The problem is here:
const [imageUris, setImageUris] = useState();
You're trying to spread imageUris but it is not an array when it is first initialized.
Simply replace useState(); with useState([]);

Why can not I enter texts continually when using TexInput component with a props

I am trying to make a TextInput component as a child with a function which is from the parent component.
but then, I can't enter texts as usual(I have to set the cursor every after I enter 1 text )
would you mind tell me how to resolve this problem?
thank you in advance.
the parent component
import React, { useState, useEffect } from "react";
import { View, StyleSheet } from "react-native";
import TestListInputItem from "../components/TestListInputItem";
export default function HomeScreen() {
const [name, setName] = useState("");
const setInputName = (text) => {
setName(text);
};
return (
<View style={styles.container}>
<TestListInputItem name={name} setInputName={setInputName} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
},
});
the child component
import React, { useState } from "react";
import {
View,
StyleSheet,
Text,
TouchableOpacity,
TextInput,
} from "react-native";
export default function TestListInputItem(props) {
const [count, setCount] = useState(1);
function handleMakeNew() {
setCount((v) => v + 1);
}
function RenderList() {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={String(props.name)}
onChangeText={(text) => {
props.setInputName(text);
}}
></TextInput>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
}
return [...Array(count).keys()].map((i) => <RenderList key={i} />);
}
const styles = StyleSheet.create({
list: {
width: "100%",
backgroundColor: "#ddd",
padding: 10,
},
InputName: {
borderWidth: 1,
height: 40,
backgroundColor: "#fff",
},
buttonAdd: {
backgroundColor: "orange",
width: 80,
height: 40,
margin: 3,
justifyContent: "center",
alignItems: "center",
alignSelf: "center",
},
});
Node: 12.18.3
React-Native: 4.10.1
Expo: 3.22.3
As I commented, the issues is that your function is creating a new array on every render resetting your state, here's a quick modification that should set you in the right direction
export default function AddItem(props) {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={props.value}
onChangeText={(text) => {
props.setValue(text);
}}
></TextInput>
</View>
);
}
export default function TestListInputItem(props) {
const [list, setList] = useState([]);
const [value, setValue] = useState("");
function handleMakeNew() {
setList((v) => [...v, value]);
}
return (
<View>
{
list.map((item, i) => <View key={i}><Text>{item}</Text></View>)
}
<AddItem
value={value}
setValue={setValue}
/>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
)
}
This is known as cursor jumping. To avoid this, in your Input component, maintain a local state and use that state value as input value. when onChange is fired, update the local state value first and then send it to the parent component
Try this in the child component,
import React, { useState } from "react";
import {
View,
StyleSheet,
Text,
TouchableOpacity,
TextInput,
} from "react-native";
export default function TestListInputItem(props) {
const [count, setCount] = useState(1);
const [inputValue, setInputValue] = useState('');
function handleMakeNew() {
setCount((v) => v + 1);
}
const handleInput =(text) =>{
setInputValue(text)
props.setInputName(text)
}
function RenderList() {
return (
<View style={styles.list}>
<TextInput
style={styles.InputName}
value={inputValue}
onChangeText={(text) => {
handleInput(text);
}}
></TextInput>
<TouchableOpacity
onPress={() => {
handleMakeNew();
}}
style={styles.buttonAdd}
>
<Text>Add</Text>
</TouchableOpacity>
</View>
);
}

Resources