How to force update single component react native - react-native

I'm using 2 usestate in my component
const [choosedH, setChoosedH] = useState([]);
const [h, setH] = useState([]);
I have async method which fetch data from api and convert it to final array.
useEffect(() => {
getH();
}, [])
async function getH(){
const username = await SecureStore.getItemAsync('username')
const token = await SecureStore.getItemAsync('token')
axiosInstance.get('/api/xxx/' + username,
{
headers: {
Cookie: token,
},
},
{ withCredentials: true }
)
.then((response) => {
if(response.data.length > 0){
let singleH = {};
response.data.forEach(element => {
singleH = {
label: element.name,
value: element.name
}
h.push(singleH);
});
console.log(h)
}
})
.catch(function (error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
throw error;
})
}
and finally i have my component:
<RNPickerSelect
onValueChange={(value) => setChoosedH(value)}
items={h}
useNativeAndroidPickerStyle={false}
style={{
...pickerSelectStyles,
iconContainer: {
top: 10,
right: 10,
},
}}
placeholder={{
label: 'Select',
value: null,
}}
Icon={() => {
return <Icon name="arrow-down" size={24} />;
}}
value={choosedH}
/>
I have a problem. After render my picker contain empty array. When render end, hook useEffect call getH() which give me data from api and convert it as I want to value of useState "h". How to force update picker items when function getH will end? It it possible to get data from api before render? Any ideas ?

I guess the problem is that you try to access h directly instead of using setH.
This should work:
if(response.data.length > 0){
const myArray = []
response.data.forEach(element => {
const singleH = {
label: element.name,
value: element.name
}
myArray.push(singleH);
});
setH(myArray)
console.log(h)
}

Related

React Native dynamic search with flatlist from API

function ManageData({props, navigation}) {
const [details, setDetails] = useState({
dataList: [],
loading: true,
offset: 1,
totalRecords: 0,
search: '',
});
useEffect(() => {
getData();
}, []);
const getData = async () => {
try {
// console.log('search',details.search);
var params = {};
params = {
'pagination[page]': details.offset,
'pagination[perpage]': 10,
};
if(details?.search?.length > 0){
params['query[search]'] = details?.search;
params['pagination[pages]'] = 30;
params['pagination[total]'] = 293;
}else{
params['query'] = ""
}
const result = await getPayeeDetails(session, params);
// console.log('result',result?.data?.data?.length);
if (result?.data?.data?.length > 0) {
setDetails(prev => ({
...prev,
offset: prev.offset + 1,
dataList: [...prev.dataList, ...result.data.data],
loading: false,
totalRecords: result.data.recordsFiltered,
}));
}
} catch (error) {
console.log('getPayeesError', error);
}
};
const loadMore = () => {
try {
if (details.dataList.length != details.totalRecords) {
setDetails(prev => ({
...prev,
loading: true,
}));
getData();
}
} catch (error) {
console.log('LoadMoreError', error);
}
};
const searchHandler=(data)=>{
try{
console.log('clearData',data);
setDetails(prev => ({
...prev,
dataList:[],
offset:1,
search: data == 'RESET'?"":data,
}));
getData();
}catch(error){
console.log("SearchError",error)
}
}
return (
<BackDropContainer
searchHandler={searchHandler}>
<View style={{backgroundColor: 'white', flex: 1}}>
<FlatList
style={{marginTop: '4%'}}
data={details?.dataList}
renderItem={({item}) => (
<TouchableOpacity onPress={() => showDialog(item)}>
<Item data={item} />
</TouchableOpacity>
)}
onEndReached={loadMore}
keyExtractor={(item, index) => index}
/>
</View>
</BackDropContainer>
);
}
I have a flatlist with searchview in my React Native application. Each time user scrolls to the end of flatlist the loadmore function will be called and also the offset value is increased as 1 to fetch next page from API.
Every time the API results array of 10 data from API so the flatlist will be loaded 10 by 10 for each scroll. When I type some data in searchview the searchHandler function will be called, and there I want to reset the offset as 1 and also need to send typed data to the API.
The issue is searched data and offset is not sending with API whenever I try to search the data. State is not updating properly when searching data.
Note: The data which is types has to be sent along with API whenever user search something.

Apollo-Client refetch - TypeError: undefined is not an object

I have a flatlist in react-native and I am trying to refetch the data when pulling it down (the native refresh functionality). When I do, I am getting this error:
Typeerror: undefined is not an object
I can't figure out what is going wrong. I am using
Expo SDK 38
"#apollo/client": "^3.1.3",
"graphql": "^15.3.0",
This is my code:
export default function DiscoverFeed({ navigation }) {
const theme = useTheme();
const { data, error, loading, refetch, fetchMore, networkStatus } = useQuery(
GET_RECIPE_FEED,
{
variables: { offset: 0 },
notifyOnNetworkStatusChange: true,
}
);
if (error) return <Text>There was an error, try and reload.</Text>;
if (loading) return <Loader />;
if (networkStatus === NetworkStatus.refetch) return <Loader />;
const renderItem = ({ item }) => {
return (
<View style={styles.cardItems}>
<RecipeCard item={item} navigation={navigation} />
</View>
);
};
return (
<SafeAreaView style={styles.safeContainer} edges={["right", "left"]}>
<FlatList
style={styles.flatContainer}
data={data.recipe}
removeClippedSubviews={true}
renderItem={renderItem}
refreshing={loading}
onRefresh={() => {
refetch();
}}
keyExtractor={(item) => item.id.toString()}
onEndReachedThreshold={0.5}
onEndReached={() => {
// The fetchMore method is used to load new data and add it
// to the original query we used to populate the list
fetchMore({
variables: {
offset: data.recipe.length,
},
});
}}
/>
</SafeAreaView>
);
}
I have a typepolicy like so:
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
recipe: {
merge: (existing = [], incoming, { args }) => {
// On initial load or when adding a recipe, offset is 0 and only take the incoming data to avoid duplication
if (args.offset == 0) {
console.log("offset 0 incoming", incoming);
return [...incoming];
}
console.log("existing", existing);
console.log("incoming", incoming);
// This is only for pagination
return [...existing, ...incoming];
},
},
},
},
},
});
And this is the query fetching the data:
export const GET_RECIPE_FEED = gql`
query GetRecipeFeed($offset: Int) {
recipe(order_by: { updated_at: desc }, limit: 5, offset: $offset)
#connection(key: "recipe") {
id
title
description
images_json
updated_at
dishtype
difficulty
duration
recipe_tags {
tag {
tag
}
}
}
}
`;

How to open a pdf from my flatlist in react-native?

Im trying to pick a file (pdf-file) from a module called react-native-file-picker. This works ok, and gaves me name, type, path and uri.
After this, i display the name of the document that i picked in a flatlist.
Now, what i want is to "onPress" of the item in the flatlist, open the document with some pdf viewer or something like that.
I've already tried to use other modules like react-native-view-pdf and react-native-pdf and react-native-pdf-view but i cant access the state of my uri with either of them.
The last one that i used it was react-native-file-viewer and doesn't work very well because it doesn't open the item on press.
This is my actual code.
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Button, TextInput,
Dimensions, FlatList } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage'
import FilePickerManager from 'react-native-file-picker';
import FileViewer from 'react-native-file-viewer';
global.myfunction = function myfunction() {
FilePickerManager.showFilePicker(null, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled file picker');
}
else if (response.error) {
console.log('FilePickerManager Error: ', response.error);
}
else {
this.storeItem(response)
}
});
};
export default class Docs extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
title: 'Docs',
header: null
}
};
state = {
arr: [],
local: '',
password: '',
obj: null,
count: 1,
image: {},
b64: '',
isModalVisible: false,
pdfuri: null,
};
pdf = () => {
FilePickerManager.showFilePicker(null, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled file picker');
}
else if (response.error) {
console.log('FilePickerManager Error: ', response.error);
}
else {
this.storeItem(response)
this.setState({
pdfuri: response.path
});
}
});
}
toggleModal = (item) => {
this.setState({ isModalVisible: !this.state.isModalVisible, obj: item });
};
storeItem(item) {
try {
//we want to wait for the Promise returned by AsyncStorage.setItem()
//to be resolved to the actual value before returning the value~
console.log(item)
var joined = this.state.arr.concat(item);
console.log('files ', joined)
this.setState({ arr: joined })
AsyncStorage.setItem('files', JSON.stringify(joined));
console.log(this.state.arr)
} catch (error) {
console.log(error.message);
}
}
componentDidMount() {
//Here is the Trick
const { navigation } = this.props;
}
componentWillMount() {
AsyncStorage.getItem('files').then(array => {
item = JSON.parse(array)
item ? this.setState({ arr: item }) : null;
console.log(this.state.arr)
})
}
verpdf() {
const path =
"content://com.android.providers.downloads.documents/document/4183"
FileViewer.open(path, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
// error
});
}
render() {
return (
<View style={[styles.container, { marginTop: 20 }]}>
<FlatList
data={this.state.arr}
renderItem={({ item }) => <TouchableOpacity onPress=
{this.verpdf(item)} style={{ marginBottom: 10, marginTop: 10, alignItems: 'center' }}>
<Text>{item.fileName}</Text></TouchableOpacity>}
/>
<Button title='ok' onPress={this.pdf}></Button>
</View>
);
}
}
How should i do this?
Try to change the event handler from
onPress=
{this.verpdf(item)}
to
onPress=
{()=>this.verpdf(item)}
Like #Oleg said, to open a certain item i needed to change the event handler to a arrow function.
onPress = {this.verpdf(item)}
to
onPress= {()=>this.verpdf(item)}
After that i wanted to open a certain item from my flatlist which i did:
verpdf(item) {
const path = item.path
FileViewer.open(path, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
// error
});
}

React Native Flat List doesn't call onEndReached handler after two successful calls

I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!

Update position of All places in react-native-sortable-listview

I am using react-native-sortable-listview in react-native for sorting same places.
constructor() {
this.state = {
makers: [
{ kolkata: 'Hawrah Birdge' },
{ Delhi: 'Lal Kila' },
{ Agra: 'Taj Mahal' },
{ Mumbai: 'India Gate' },
],
allObj: {},
order: []
};
}
componentDidMount() {
const newAllObj = this.getAllObjFromMaker(this.state.makers);
const newOrder = this.getOrderFromMaker(newAllObj);
this.setState({ allObj: newAllObj, order: newOrder });
}
getAllObjFromMaker(makers) {
const allObj = makers.reduce((result, d) => {
result[`${d.coordinate.latitude}_${d.coordinate.longitude}`] = d;
return result;
}, {});
return allObj;
}
getOrderFromMaker(allObj) {
const order = Object.keys(allObj);
return order;
}
renderOneDraggableMilestone(milestone) {
const i = this.state.makers.indexOf(milestone);
return (
<TouchableOpacity {...this.props.sortHandlers}>
<Text>{i + 1}</Text>
<Text>{milestone.address}</Text>
</TouchableOpacity>
);
}
arrangedMilestoneList(e) {
const arr = this.state.makers;
arr.splice(e.to, 0, arr.splice(e.from, 1)[0]);
const newAllObj = this.getAllObjFromMaker(arr);
const newOrder = this.getOrderFromMaker(newAllObj);
this.setState({ makers: arr, allObj: newAllObj, order: newOrder
});
}
render() {
return (
<SortableListView
data={this.state.allObj}
order={this.state.order}
activeOpacity={0.5}
onRowMoved={e => {
this.arrangedMilestoneList(e);
this.forceUpdate();
}}
renderRow={(row) => this.renderOneDraggableMilestone(row)}
/>
);
}
I want to arrange places and also their position in this.state.makers as I am doing using i in renderOneDraggableMilestone. On renderRow only draggable place are render so only their position is updated. And renderRow is last to excute so forceUpdate is also not working.
How to rerender after executing renderRow. So all position could be updated.
Ok I have find a way to re-render as follow.
<SortableListView
key={this.state.count}
data={this.state.allObj}
order={this.state.order}
activeOpacity={0.5}
onRowMoved={e => {
this.setState({ count: this.state.count + 1 });
this.props.arrangedMilestoneList(e);
console.log('onRowMoved is called');
}}
onMoveEnd={() => console.log('onMoveEnd is fired')}
renderRow={(row, s1, i) => this.renderOneDraggableMilestone(row, s1, i)}
/>
What I am doing is I added a key attribute to SortableListView and update this key on each onRowMoved action. And because of this it causes re-render.