Why adding extra state helps to update other state? - javascript

Here is the full code:
import * as React from 'react';
import { View, ScrollView, StyleSheet } from 'react-native';
import {
Appbar,
Searchbar,
List,
BottomNavigation,
Text,
Button,
} from 'react-native-paper';
const AccordionCollection = ({ data }) => {
var bookLists = data.map(function (item) {
var items = [];
for (let i = 0; i < item.total; i++) {
items.push(
<Button mode="contained" style={{ margin: 10 }}>
{i}
</Button>
);
}
return (
<List.Accordion
title={item.title}
left={(props) => <List.Icon {...props} icon="alpha-g-circle" />}>
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
backgroundColor: 'white',
}}>
{items}
</View>
</List.Accordion>
);
});
return bookLists;
};
const MusicRoute = () => {
const DATA = [
{
key: 1,
title: 'Zain dishes',
total: 21,
},
{
key: 2,
title: 'Sides',
total: 32,
},
{
key: 3,
title: 'Drinks',
total: 53,
},
{
key: 4,
title: 'Aesserts',
total: 14,
},
];
const [data, setData] = React.useState(DATA);
const [searchQuery, setSearchQuery] = React.useState('');
const [sortAZ, setSortAZ] = React.useState(false);
const onChangeSearch = (query) => {
setSearchQuery(query);
const newData = DATA.filter((item) => {
return item.title.toLowerCase().includes(query.toLowerCase());
});
setData(newData);
};
const goSortAZ = () => {
setSortAZ(true);
setData(
data.sort((a, b) => (a.title > b.title ? 1 : b.title > a.title ? -1 : 0))
);
};
const goUnSort = () => {
setSortAZ(false);
setData(DATA);
};
return (
<View>
<Appbar.Header style={styles.appBar}>
<Appbar.BackAction onPress={() => null} />
<Searchbar
placeholder="Search"
onChangeText={onChangeSearch}
value={searchQuery}
style={styles.searchBar}
/>
<Appbar.Action
icon="sort-alphabetical-ascending"
onPress={() => goSortAZ()}
/>
<Appbar.Action icon="library-shelves" onPress={() => goUnSort()} />
</Appbar.Header>
<ScrollView>
<List.Section title="Accordions">
<AccordionCollection data={data} />
</List.Section>
</ScrollView>
</View>
);
};
const AlbumsRoute = () => <Text>Albums</Text>;
const MyComponent = () => {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'music', title: 'Music', icon: 'queue-music' },
{ key: 'albums', title: 'Albums', icon: 'album' },
]);
const renderScene = BottomNavigation.SceneMap({
music: MusicRoute,
albums: AlbumsRoute,
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
/>
);
};
const styles = StyleSheet.create({
appBar: {
justifyContent: 'space-between',
},
searchBar: {
width: '60%',
shadowOpacity: 0,
borderRadius: 10,
backgroundColor: '#e4e3e3',
},
});
export default MyComponent;
Expo Snack Link
There are two weird mechanisms.
First
If I remove sortAZ(true) in goSortAZ() and sortAZ(false) in goUnSort(), the state data stops updating after you press on (1) sort and (2) unsort buttons more than three times.
Second
If I remove DATA array outside the component, sort and unsort buttons does not work/update.
If I do not remove these two, I can sort and unsort the list.
I feel that the code is messy although it achieves the function.
My questions is:
Why adding extra state (sortAZ) helps to update other state (data)?

Just totally remove sortAZ variable (no need to use it unless you somehow want to have a loading status, but since you are not making http requests, that's not necessary) and replace goSortAZ with the following:
Remember to clone the original array in order to create a new copy and then sort that copy.
This is working fine.
const goSortAZ = () => {
setData(
[...data].sort((a, b) => (a.title > b.title ? 1 : b.title > a.title ? -1 : 0))
);
};
i would suggest using the same technique for the unSort method too.

Related

React Native Searchable Flatlist using Nested JSON

I am trying to make a searchable flatlist for skills using the below JSON file:
const employeeList = [
{
id: "1",
name: "John",
image: require("../images/John.png"),
skills: [
{ id: 1, name: "Cooking" },
{ id: 2, name: "Climbing" },
],
},
{
id: "2",
name: "Pat",
image: require("../images/Pat.png"),
skills: [
{ id: 1, name: "Cooking" },
{ id: 2, name: "Rowing" },
],
},
];
export default employeeList;
I was successful in making a screen that will display and allow me to search for employee names but I would like to make a searchable flatlist with all the skills while also displaying the employee name who has that skill. I don't need them to be unique. In my code below I have managed to display all employees and their skills however my search feature only filters for the employee name.
// Searching using Search Bar Filter in React Native List View
// https://aboutreact.com/react-native-search-bar-filter-on-listview/
// import React in our code
import React, { useState, useEffect } from "react";
// import all the components we are going to use
import {
SafeAreaView,
Text,
StyleSheet,
View,
FlatList,
TextInput,
Image,
TouchableOpacity,
} from "react-native";
// import employee json
import employeeList from "../json/employee";
const AllListScreen = ({ navigation, route }) => {
const [search, setSearch] = useState("");
const [filteredDataSource, setFilteredDataSource] = useState([]);
const [masterDataSource, setMasterDataSource] = useState([]);
// set employee json as filter source
useEffect(() => {
setFilteredDataSource(employeeList);
setMasterDataSource(employeeList);
// skills show as undefined unless index is specified
console.log(JSON.stringify(employeeList[0].skills));
}, []);
const searchFilterFunction = (text) => {
// Check if searched text is not blank
if (text) {
// Inserted text is not blank
// Filter the masterDataSource
// Update FilteredDataSource
const newData = masterDataSource.filter(function (item) {
const itemData = item.name ? item.name.toUpperCase() : "".toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilteredDataSource(newData);
setSearch(text);
} else {
// Inserted text is blank
// Update FilteredDataSource with masterDataSource
setFilteredDataSource(masterDataSource);
setSearch(text);
}
};
const ItemView = ({ item, index }) => {
return (
// Flat List Item
<View>
// use map to display all skills under employee
{item.skills.map((v, i) => (
<>
<TouchableOpacity
onPress={() => console.log(v.name)}
style={styles.itemStyle}
key={item.id}
>
<Image
source={{ uri: "https://source.unsplash.com/random" }}
style={{ height: 50, width: 50 }}
/>
<View style={styles.textPortion}>
<Text>{item.name}</Text>
<Text>{v.name.toUpperCase()}</Text>
</View>
</TouchableOpacity>
<ItemSeparatorView />
</>
))}
</View>
);
};
const ItemSeparatorView = () => {
return (
// Flat List Item Separator
<View
style={{
height: 0.5,
width: "100%",
backgroundColor: "#C8C8C8",
}}
/>
);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<TextInput
style={styles.textInputStyle}
onChangeText={(text) => searchFilterFunction(text)}
value={search}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={filteredDataSource}
keyExtractor={(item, index) => index.toString()}
renderItem={ItemView}
/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: "#FFFFFF",
},
itemStyle: {
flex: 1,
padding: 8,
flexDirection: "row",
},
textInputStyle: {
height: 50,
borderWidth: 1,
paddingLeft: 20,
margin: 6,
borderColor: "#009688",
backgroundColor: "#FFFFFF",
borderRadius: 5,
},
textPortion: {
flexWrap: "wrap",
flexShrink: 1,
marginLeft: 6,
},
});
export default AllListScreen;
Here is an image of how it is displayed but as stated the search only works on the employee name while I want it to work on the skill:
Any help is much appreciated. Thanks.
I managed to get this working by restructuring the JSON in an array:
skillArray = [];
for (var key in employeeList) {
if (employeeList.hasOwnProperty(key)) {
for (item in employeeList[key].skills) {
skillArray.push({
name: employeeList[key].name,
skill: employeeList[key].skills[item].name,
});
}
}
}
Then I changed my search filter to target the skill instead of the name:
// set employee json as filter source
useEffect(() => {
setFilteredDataSource(skillArray);
setMasterDataSource(skillArray);
// skills show as undefined unless index is specified
console.log(JSON.stringify(employeeList[0].skills));
}, []);
const searchFilterFunction = (text) => {
// Check if searched text is not blank
if (text) {
// Inserted text is not blank
// Filter the masterDataSource
// Update FilteredDataSource
const newData = masterDataSource.filter(function (item) {
const itemData = item.skill ? item.skill.toUpperCase() : "".toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilteredDataSource(newData);
setSearch(text);
} else {
// Inserted text is blank
// Update FilteredDataSource with masterDataSource
setFilteredDataSource(masterDataSource);
setSearch(text);
}
};
And my updated ItemView:
const ItemView = ({ item }) => {
return (
// Flat List Item
<View style={styles.itemStyle}>
<Image
source={{ uri: "https://source.unsplash.com/random" }}
style={{ height: 50, width: 50 }}
/>
<Text style={styles.textPortion}>
{item.name.toUpperCase()}
{"\n"}
{item.skill.toUpperCase()}
</Text>
</View>
);
};
I'm not sure if this is the optimal approach but hopefully it helps someone.

UseState not update when using alongside with redux dispatch in arrow function

I'm making an app that have notes, and when develop the delete function, i faced this error, the useState do not update when use alongside with redux dispatch function ( even the redux function run, the useState do not run ) , i tried to create the same issue on codesandbox, but weird is it WORKING TOTALLY FINE ON WEB?!
Here is the code:
NoteList.tsx
function NoteList(props: noteListI) {
const { title, note, id, date, selectStatus } = props; //they are props
const nav = useNavigation(); //for navigation
const [isDeleteChecked, setDeleteChecked] = useState(false);
const dispatch = useDispatch();
const data = useSelector((state: RootState) => state.persistedReducer.note); // note item from redux
const toggleSelectedButton = useSelector(
(state: RootState) => state.toggle.enableSelectedButton
); // to show selected button icon
const onNavDetail = () => {
nav.navigate(RouteName.EDIT_NOTE, {
date: date,
note: note,
header: title,
id: id,
});
};
const toggleSelectButton = () => {
dispatch(switchToggle());
}; // toggle delete button function
const setDeleteItem = () => {
setDeleteChecked(!isDeleteChecked);
dispatch(toggleSelect({ id: id }));
}; ////==>>> the issue here the 'setDeleteChecked' not even work
return (
<TouchableOpacity
onLongPress={() => {
toggleSelectButton();
}}
style={CONTAINER}
onPress={() => (!toggleSelectedButton ? onNavDetail() : setDeleteItem())}
>
<View style={NOTE_ITEM_CONTAINER}>
<Text>{isDeleteChecked?.toString()}</Text> ==>always false, why????!
<View>
<View row centerV style={HEADER_CONTAINER}>
<View>
<AppText bold style={HEADER_TEXT}>
{title}
</AppText>
</View>
{toggleSelectedButton && (
<View>
{selectStatus ? ( ===> this is from redux and work but slow
<CheckIcon name="checkcircle" size={size.iconSize} />
) : (
<CheckIcon name="checkcircleo" size={size.iconSize} />
)}
</View>
)}
</View>
<View style={NOTE_CONTAINER}>
<AppText numberOfLines={7}>{note}</AppText>
</View>
</View>
<View
style={{
alignSelf: "flex-end",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<AppText>{moment(date).format("h:mmA MMM Do YY")}</AppText>
</View>
</View>
</TouchableOpacity>
);
}
export default memo(NoteList);
I use these from flatlist, here is the main flatlist code:
export default function NoteListScreen() {
const [user, setUser] = useState<any>();
const nav = useNavigation();
// useEffect(() => {
// dispatch(loadDefault());
// }, []);
const dispatch: AppDispatch = useDispatch();
const data = useSelector((state: RootState) => state.persistedReducer.note);
const userInfo: user = useSelector(
(state: RootState) => state.persistedReducer.firebase.userInfomation
);
useEffect(() => {
dispatch(fetchNote(userInfo.email)); //fetch note from firebase
}, []);
return (
<SafeAreaView style={CONTAINER}>
{data.length === 0 ? (
<>
<ScrollView>
<HeaderNote />
<AppText style={EMPTY_NOTE}>
Hmm, so don't have any secret yet
</AppText>
</ScrollView>
<FooterNote />
</>
) : (
<View style={CONTAINER}>
<FlatList
removeClippedSubviews
data={data}
style={{
marginBottom:
Platform.OS === "ios"
? onePercentHeight * 15
: onePercentHeight * 12,
}}
keyExtractor={() => {
return (
new Date().getTime().toString() +
Math.floor(
Math.random() * Math.floor(new Date().getTime())
).toString()
);
}}
ListHeaderComponent={() => <HeaderNote />}
renderItem={({ item, index }) => {
return (
<NoteList ==> here , the note list that faced error
note={item.note}
title={item.header}
date={item.date}
id={item.id}
selectStatus={item.selectStatus}
/>
);
}}
/>
<FooterNote />
</View>
)}
</SafeAreaView>
);
}
Here is the reducer code:
const noteReducer = createSlice({
name: "note",
initialState: NoteList,
reducers: {
addNote: (state, action: PayloadAction<NoteI>) => {
const newNote: NoteI = {
id:
new Date().getTime().toString() +
Math.floor(
Math.random() * Math.floor(new Date().getTime())
).toString(),
header: action.payload.header,
note: action.payload.note,
date: new Date(),
selectStatus: false,
};
state.push(newNote);
},
toggleSelect: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return { ...item, selectStatus: !item.selectStatus };
}
return item;
});
}, ///========>This is the reducer using in the note function
loadDefault: (state) => {
return state.map((item) => {
return { ...item, selectStatus: false };
});
},
resetNote: (state) => {
return (state = []);
},
editNote: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return {
...item,
note: action.payload.note,
header: action.payload.header,
date: action.payload.date,
};
}
return item;
});
},
},
extraReducers: (builder) => {
builder.addCase(fetchNote.fulfilled, (state, action) => {
state = [];
return state.concat(action.payload);
});
},
});
Here is the image of what i'm talking about, the code in image from noteList.tsx, the first piece of code i post here
Here is the quick gif:
In above gif, the false must return true then false everytime i click ( as above code ) but i don't why it never change value, the black dot also change color because it use value using in the same function using with this value, but when setDeleteItem fire, it NOT fire the setDeleteChecked(!isDeleteChecked)
Here is the demo that i made, but it WORK TOTALLY FINE, but in my app, it make weird error https://codesandbox.io/s/nostalgic-neumann-0497v?file=/redux/some-redux.tsx
Please help, i'm trying to provide must as i can, i stuck for days for this, thank you so much, if you need any detail, just tell me

how to make an infinite image carousel with hooks in react native

I am making an infinite image carousel using hooks and javascript in react native.
I am almost done with the code and also the carousel works absolutely fine but the active dots below the carousel runs faster than the images. I have tried a couple of things but unfortunately it didn't worked out.
Thanks in advance.
import React, {
useEffect,
useState,
useRef,
useCallback,
createRef,
} from 'react';
import {
StyleSheet,
View,
Dimensions,
FlatList,
LayoutAnimation,
UIManager,
Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';
const HomeCarousel = ({data}) => {
const [dimension, setDimension] = useState(Dimensions.get('window'));
const [index, setIndex] = useState(0);
const [dataState, setDataState] = useState(data);
slider = createRef();
let intervalId = null;
const onChange = () => {
setDimension(Dimensions.get('window'));
};
useEffect(() => {
Dimensions.addEventListener('change', onChange);
return () => {
Dimensions.removeEventListener('change', onChange);
};
});
useEffect(() => {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}, []);
viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
const onViewableItemsChanged = ({viewableItems, changed}) => {
if (viewableItems.length > 0) {
let currentIndex = viewableItems[0].index;
if (currentIndex % data.length === data.length - 1) {
setIndex(currentIndex),
setDataState(dataState => [...dataState, ...data]);
} else {
console.log(currentIndex, 'else');
setIndex(currentIndex);
}
}
};
const onSlideChange = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);
const newIndex = index + 1;
setIndex(newIndex);
slider?.current?.scrollToIndex({
index: index,
animated: true,
});
};
const startInterval = useCallback(() => {
intervalId = setInterval(onSlideChange, 3000);
}, [onSlideChange]);
useEffect(() => {
startInterval();
return () => {
clearInterval(intervalId);
};
}, [onSlideChange]);
const viewabilityConfigCallbackPairs = useRef([
{viewabilityConfig, onViewableItemsChanged},
]);
const renderIndicator = ()=>{
const indicators = [];
data.map((val,key)=>(
indicators.push(
<Text
key={key}
style={
key === index % data.length ? {color: 'lightblue',fontSize:10,marginBottom: 8,marginHorizontal:1} :
{color: '#888',fontSize:10,marginBottom: 8,marginHorizontal:1}
}>
⬤
</Text>
)
));
return indicators;
}
return (
<View style={{width: dimension.width,height: 280, backgroundColor: '#fff'}}>
<FlatList
ref={slider}
horizontal
pagingEnabled
snapToInterval={dimension?.width}
decelerationRate="fast"
bounces={false}
showsHorizontalScrollIndicator={false}
data={dataState}
renderItem={({item, index}) => (
<>
<View>
<Image
source={{uri: `${item.url}`}}
style={{
width: dimension?.width,
height: 250,
resizeMode: 'cover',
}}
PlaceholderContent={<ActivityIndicator />}
/>
</View>
</>
)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
getItemLayout={(data, index) => ({
length: dimension?.width,
offset: dimension?.width * index,
index,
})}
windowSize={1}
initialNumToRender={1}
maxToRenderPerBatch={1}
removeClippedSubviews={true}
/>
<View
style={{
flexDirection: 'row',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
}}>
{renderIndicator()}
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default HomeCarousel;
The data that is passed as the props to this component is
export const carouselImages = [
{url: 'https://i.ibb.co/FDwNR9d/img1.jpg'},
{url: 'https://i.ibb.co/7G5qqGY/1.jpg'},
{url: 'https://i.ibb.co/Jx7xqf4/pexels-august-de-richelieu-4427816.jpg'},
{url: 'https://i.ibb.co/GV08J9f/pexels-pixabay-267202.jpg'},
{url: 'https://i.ibb.co/sK92ZhC/pexels-karolina-grabowska-4210860.jpg'},
];
Ooooooh, I fixed it myself
Here is the perfectly working code full code. 😄
import React, {
useEffect,
useState,
useRef,
useCallback,
createRef,
} from 'react';
import {
StyleSheet,
View,
Dimensions,
FlatList,
LayoutAnimation,
UIManager,
Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';
const HomeCarousel = ({data}) => {
const [dimension, setDimension] = useState(Dimensions.get('window'));
const [index, setIndex] = useState(0);
const [dataState, setDataState] = useState(data);
const [indicatorIndex, setindicatorIndex] = useState();
slider = createRef();
let intervalId = null;
const onChange = () => {
setDimension(Dimensions.get('window'));
};
useEffect(() => {
Dimensions.addEventListener('change', onChange);
return () => {
Dimensions.removeEventListener('change', onChange);
};
});
useEffect(() => {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}, []);
viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
const onViewableItemsChanged = ({viewableItems, changed}) => {
if (viewableItems.length > 0) {
let currentIndex = viewableItems[0].index;
if (currentIndex % data.length === data.length - 1) {
setIndex(currentIndex), setindicatorIndex(currentIndex);
setDataState(dataState => [...dataState, ...data]);
} else {
console.log(currentIndex, 'else');
setIndex(currentIndex);
setindicatorIndex(currentIndex);
}
}
};
const onSlideChange = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);
const newIndex = index + 1;
setIndex(newIndex);
slider?.current?.scrollToIndex({
index: index,
animated: true,
});
};
const startInterval = useCallback(() => {
intervalId = setInterval(onSlideChange, 3000);
}, [onSlideChange]);
useEffect(() => {
startInterval();
return () => {
clearInterval(intervalId);
};
}, [onSlideChange]);
const viewabilityConfigCallbackPairs = useRef([
{viewabilityConfig, onViewableItemsChanged},
]);
const renderIndicator = () => {
const indicators = [];
data.map((val, key) =>
indicators.push(
<Text
key={key}
style={
key === indicatorIndex % data.length
? {
color: 'lightblue',
fontSize: 10,
marginBottom: 8,
marginHorizontal: 1,
}
: {
color: '#888',
fontSize: 10,
marginBottom: 8,
marginHorizontal: 1,
}
}>
⬤
</Text>,
),
);
return indicators;
};
return (
<View
style={{width: dimension.width, height: 280, backgroundColor: '#fff'}}>
<FlatList
ref={slider}
horizontal
pagingEnabled
snapToInterval={dimension?.width}
decelerationRate="fast"
bounces={false}
showsHorizontalScrollIndicator={false}
data={dataState}
renderItem={({item, index}) => (
<>
<View>
<Image
source={{uri: `${item.url}`}}
style={{
width: dimension?.width,
height: 250,
resizeMode: 'cover',
}}
PlaceholderContent={<ActivityIndicator />}
/>
</View>
</>
)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
getItemLayout={(data, index) => ({
length: dimension?.width,
offset: dimension?.width * index,
index,
})}
windowSize={1}
initialNumToRender={1}
maxToRenderPerBatch={1}
removeClippedSubviews={true}
/>
<View
style={{
flexDirection: 'row',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
}}>
{renderIndicator()}
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default HomeCarousel;

ReactNative deep cloning state for Flatlist

My FlatList renderItem was re-rendering every item when one of them was changed.
After doing some debugging i deepcloned the state variable which holds the items (+ React.memo), it's working fine now but not sure if it's the optimal solution.
Snack: https://snack.expo.io/-419PhiUl
App.js
import * as React from 'react';
import { View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import _ from 'lodash';
import Item from './components/Item';
const keyExtractor = item => item.id.toString();
export default function App() {
const [data, setData] = React.useState([
{id: 1, title: 'Post 1', liked: false, user: {name: 'A'}},
{id: 2, title: 'Post 2', liked: false, user: {name: 'B'}},
{id: 3, title: 'Post 3', liked: false, user: {name: 'C'}},
]);
/**
* Like / Unlike the item.
*/
const like = React.useCallback((id) => {
setData(state => {
let clonedState = [...state];
let index = clonedState.findIndex(item => item.id === id);
clonedState[index].liked = ! clonedState[index].liked;
return clonedState;
});
}, []);
/**
* Render items.
*/
const renderItem = React.useCallback(({item}) => (
<Item item={item} onLike={like} />
), []);
const deepClonedData = React.useMemo(() => _.cloneDeep(data), [data]);
return (
<View style={styles.container}>
<FlatList
data={deepClonedData}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
Item.js
import React from 'react';
import {
Text, TouchableOpacity, StyleSheet
} from 'react-native';
function Item({item, onLike}) {
const _onLike = React.useCallback(() => {
onLike(item.id);
}, []);
console.log('rendering', item.title);
return (
<TouchableOpacity onPress={_onLike} style={styles.item}>
<Text>{item.title} : {item.liked ? 'liked' : 'not liked'}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
item: {
marginVertical: 10,
backgroundColor: 'white',
padding: 15,
borderWidth: 1
}
});
const areEqual = (prevProps, nextProps) => {
return prevProps.item.liked === nextProps.item.liked;
}
export default React.memo(Item, areEqual);

react-native loading a list of images from gallery using hooks,

Im using react native and hooks to make a view where i choose from taking a photo with my camera or open my cellphone gallery and chose any image i want, once i have a picture it should show the image in a list, so if i choose more than one picture it should show more than oneimage in the list.
const Seleccion = ({navigation}) => {
const [fileList, setFileList] = useState([]);
const state = useMemo(() => ({ fileList }), [fileList]);
const onSelectedImage = useCallback((image) => {
setFileList(fileList => {
const newDataImg = [...fileList];
const source = { uri: image.path };
const item = {
id: Date.now(),
url: source,
content: image.data
};
newDataImg.push(item);
return newDataImg;
});
}, [setFileList]);
const takePhotoFromCamera = useCallback(() => {
ImagePicker.openCamera({
width: 300,
height: 400,
}).then(image => {
onSelectedImage(image);
console.log(image);
});
}, [onSelectedImage]);
const choosePhotoFromLibrary = useCallback(() => {
ImagePicker.openPicker({
width: 300,
height: 400,
}).then(image => {
onSelectedImage(image);
console.log(image);
});
}, [onSelectedImage]);
const renderItem = useCallback(({ item, index }) => {
return (
<View>
<Image source={item.url} style={styles.itemImage} />
</View>
);
}, []);
return (
<View style={styles.container}>
<FlatList
data={fileList}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
extraData={state}
/>
<TouchableOpacity style={styles.viewData} onPress={takePhotoFromCamera}>
<Text>Foto</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.viewData} onPress={choosePhotoFromLibrary}>
<Text>galeria</Text>
</TouchableOpacity>
</View>
);
}
This was my dirty component in a sample, take a look at it:
import React, {useState, useEffect, Fragment} from 'react';
import Icon from 'react-native-vector-icons/FontAwesome5Pro';
import {
View,
Modal,
ScrollView,
Image,
TouchableOpacity,
Text,
} from 'react-native';
import ActionSheet from 'react-native-action-sheet';
import ImagePicker from 'react-native-image-crop-picker';
import ImageViewer from 'react-native-image-zoom-viewer';
import styles from '../../../../../../styles';
const BUTTONS = ['Gallery', 'Camera', 'Cancel'];
export default props => {
let {files, setFiles} = props;
const [ImageViwerIsVisible, showImageViwer] = useState(false);
let [viewingIndex, setViewingIndex] = useState(-1);
useEffect(() => {
viewingIndex !== -1 && showImageViwer(true);
}, [viewingIndex]);
useEffect(() => {
!ImageViwerIsVisible && setViewingIndex(-1);
}, [ImageViwerIsVisible]);
const showActionSheet = () =>
ActionSheet.showActionSheetWithOptions(
{
title: 'Choose photo location',
options: BUTTONS,
cancelButtonIndex: 2,
destructiveButtonIndex: 2,
tintColor: 'blue',
},
buttonIndex => {
switch (buttonIndex) {
case 0:
return selectFromGallery();
case 1:
return selectFromCamera();
default:
return 0;
}
},
);
const selectFromGallery = () =>
ImagePicker.openPicker({
multiple: true,
//cropping: true,
})
.then(image => {
setFiles([...files, image].flat());
})
.catch(e => {});
const selectFromCamera = () =>
ImagePicker.openCamera({
cropping: true,
includeExif: true,
})
.then(image => {
setFiles([...files, image]);
})
.catch(e => {});
return (
<Fragment>
{!!ImageViwerIsVisible && (
<Modal transparent={true} onRequestClose={() => showImageViwer(false)}>
<ImageViewer
imageUrls={files.map(f => ({url: f.path}))}
index={viewingIndex}
/>
</Modal>
)}
<View
style={{
flexDirection: 'row',
padding: 10,
backgroundColor: '#ececec',
marginBottom: 5,
}}>
<TouchableOpacity onPress={() => showActionSheet()}>
<Icon solid name="camera-retro" size={32} />
</TouchableOpacity>
{/* <Text>{JSON.stringify(files, null, 2)}</Text> */}
<ScrollView style={{flexDirection: 'row', flex: 1}} horizontal>
{files.map((file, index) => (
<TouchableOpacity
key={file.path}
onPress={() => setViewingIndex(index)}
onLongPress={() =>
ImagePicker.cleanSingle(file.path)
.then(() => setFiles(files.filter(f => f.path !== file.path)))
.catch(e => {})
}
style={{
marginRight: 5,
width: 50,
height: 50,
borderWidth: 1,
borderColor: '#000',
borderRadius: 5,
}}>
<Image
source={{uri: file.path}}
style={{width: '100%', height: '100%', borderRadius: 5}}
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
</Fragment>
);
};
You need to place useCallBack as you were use in your code, in that time i was dummy about react-hooks.

Categories

Resources