Re rendering a component with an async function inside - javascript

I am new to react native and my JS is a bit rusty. I need to be able to change the value of my collection for the firestore. I have two buttons that will change the value of typeOfPost by setting the state. Component1 can successfully get "this.state.typeOfPost". However, when I click one of the buttons and update the state my log inside of the async function is not being called. It is only called when the app initially renders. What I find weird is that my log on the top of Component1 will display as expected. Is there any better way of doing this?
class Forum extends Component {
state = {
typeOfPost: ' '
}
onPressSitter = () => {
this.setState({
typeOfPost: 'sitterPosts'
})
}
onPressNeedSitter = () => {
this.setState({
typeOfPost: 'needPosts'
})
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<TouchableOpacity
style={styles.button}
onPress={this.onPressSitter}
>
<Text>I am a sitter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={this.onPressNeedSitter}
>
<Text>Need a sitter</Text>
</TouchableOpacity>
</View>
<View>
<Component1 typeOfPost = {this.state.typeOfPost}> </Component1>
</View>
</View>
)
}
}
const Component1 = (props) => {
console.log("type of post " + props.typeOfPost);
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [data, setData] = useState([]); // Initial empty array of data
const getData = async () => {
console.log("type of post inside async " + props.typeOfPost);
const subscriber = firestore()
.collection(props.typeOfPost) // need to be able to update this
.onSnapshot(querySnapshot => {
const data = [];
querySnapshot.forEach(documentSnapshot => {
data.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setData(data);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}
useEffect(() => {
getData();
}, [])
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={data}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>No Data</Text>
</View>
}
renderItem={({ item }) => (
<View>
<Text>User ID: {item.fullName}</Text>
</View>
)}
/>
)
}

There is a difference between mount and render. I see no problem with your code except the few remarks I have made. The thing is that when you change typeOfPost, the component is rerendered, but the useEffect is not called again, since you said, it's just called when it was first mounted:
useEffect(() => {
}, []) // ---> [] says to run only when first mounted
However here, you want it to run whenever typeOfPost changes. So here is how you can do this:
useEffect(() => {
getData();
}, [typeofPost])
class Forum extends Component {
state = {
typeOfPost: ' '
}
onPressSitter = () => {
this.setState({
typeOfPost: 'sitterPosts'
})
}
onPressNeedSitter = () => {
this.setState({
typeOfPost: 'needPosts'
})
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<TouchableOpacity
style={styles.button}
onPress={this.onPressSitter}
>
<Text>I am a sitter</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={this.onPressNeedSitter}
>
<Text>Need a sitter</Text>
</TouchableOpacity>
</View>
<View>
<Component1 typeOfPost = {this.state.typeOfPost}> </Component1>
</View>
</View>
)
}
}
const Component1 = (props) => {
const { typeOfPost } = props
console.log("type of post " + props.typeOfPost);
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [data, setData] = useState([]); // Initial empty array of data
const getData = () => {
setLoading(true)
console.log("type of post inside async " + props.typeOfPost);
const subscriber = firestore()
.collection(props.typeOfPost) // need to be able to update this
.onSnapshot(querySnapshot => {
const data = [];
querySnapshot.forEach(documentSnapshot => {
data.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setData(data);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}
useEffect(() => {
getData();
}, [typeofPost])
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={data}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>No Data</Text>
</View>
}
renderItem={({ item }) => (
<View>
<Text>User ID: {item.fullName}</Text>
</View>
)}
/>
)
}

you are using a class based component to access react hook which is a bad practice, i will advice you use a functional component and you have access to react useCallback hook which will handle your request easily
const ButtonPressed = useCallback(() => {
setLoading(true);
getData()
}).then(() => setLoading(false));
}, [loading]);

Related

How to add navigation to different items in a rendered array

I am attempting to press on this pressable button, and navigate to a new page. The tricky bit is that this Pressable item is part of a returned array, as there are multiple of them being rendered each with different data. I want each button to take me to a 'product page', each page being different depending on the button
Here is what i have so far:
The main function
const JobRequestList = () => {
const [data, setData] = useState([]);
useEffect(() => {
returnArray().then(data2 => {
setData(data2);
});
}, []);
if (data.length === 0) {
j = [];
return (
<ScrollView>
<View key={'ERROR'} style={styles.wrapperERROR}>
<Text style={styles.textError}> {'No Current Job Requests'} </Text>
</View>
</ScrollView>
);
} else {
return <ScrollView>{data}</ScrollView>;
}
};
This requests the data, and returns it in a form that can be rendered. It either returns a no object, or an array of items from the below function - This is where my onPress is located, and have no idea how to implement a navigation fnction into it. Please note, i already have my navigation functions setup
const returnArray = async () => {
return queryData().then(() => {
return j.map(x => {
return (
<Pressable
key={x.id}
style={styles['wrapper' + x.data().PD]}
onPress={() => {}}>
<Text style={styles.text}> {x.data().PD} </Text>
<Text style={styles.text}> {x.data().address} </Text>
<Text style={styles.text}> {x.data().time} </Text>
</Pressable>
);
});
});
};
The above function then calls the below
const queryData = async () => {
await firestore()
.collection('Jobs')
.where('driver', '==', 'TBA') //TODO ADD CUSTOMER DISTANCE
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
j.push(doc);
});
});
};
Here is what my navigation functions should be inside this class - Again, which is already setup correctly
const navigation = useNavigation();
navigation.navigate('JobInfo');
Thankyou in advanced!
It is anti-pattern in React to store JSX in component state. React components's rendered UI is a function of state & props. Store the data in state and then render the data mapped to JSX.
Example:
queryData fetches firebase docs & data
const queryData = async () => {
await firestore()
.collection('Jobs')
.where('driver', '==', 'TBA') //TODO ADD CUSTOMER DISTANCE
.get()
.then(querySnapshot => {
const docs = [];
querySnapshot.forEach(doc => {
docs.push({
...doc,
data: doc.data(),
});
});
return docs;
});
};
Apply the navigation logic in the Pressable component's onPress handler when mapping the data state.
const JobRequestList = () => {
const navigation = useNavigation();
const [data, setData] = useState([]);
useEffect(() => {
queryData()
.then(data => {
setData(data);
});
}, []);
return (
<ScrollView>
{data.length
? data.map(el => (
<Pressable
key={el.id}
style={styles['wrapper' + el.data.PD]}
onPress={() => {
navigation.navigate('JobInfo');
}}
>
<Text style={styles.text}> {el.data.PD} </Text>
<Text style={styles.text}> {el.data.address} </Text>
<Text style={styles.text}> {el.data.time} </Text>
</Pressable>
))
: (
<View key={'ERROR'} style={styles.wrapperERROR}>
<Text style={styles.textError}> {'No Current Job Requests'} </Text>
</View>
)
}
</ScrollView>
);
};

todos not loading while using AsyncStorage

I am trying to use AsyncStorage to fetch my todos from inside the useEffect hook. If there are no todos(Meaning todos === []) Then a Text Component shows saying "Add a todo".
App image in expo
Initially the todos are set to "[]" inside the useState hook. When the addItem() method is called onPress the todos are not loading.
I do not know why this is happening...
export default function App() {
const [todo, setTodo] = useState('');
const [todos, setTodos] = useState([]);
useEffect(() => {
_retrieveData();
}, [todos]);
const addItem = (newTodo) => {
if (newTodo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(newTodo);
let newTodos = [newTodo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
}
};
const deleteTodo = (idx) => {
setTodos(todos.filter((todo, id) => id !== idx));
};
const _storeData = async (value) => {
try {
await AsyncStorage.setItem('TASKS', value);
} catch (error) {
// Error saving data
console.log(e);
}
};
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// We have data!!
setTodos(JSON.parse(value));
console.log(value);
}
} catch (error) {
// Error retrieving data
console.log(error);
}
};
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss();
}}
>
<View style={styles.outerContainer}>
<Text style={styles.header}>TODO</Text>
<View style={styles.container}>
<TextInput
placeholder='new todo'
style={styles.input}
value={todo}
onChangeText={(text) => {
setTodo(text);
}}
></TextInput>
<Button title='Add' onPress={() => addItem(todo)}></Button>
</View>
<ScrollView style={styles.scrollView}>
{todos === [] ? (
<View>
<Text>Add a todo!</Text>
</View>
) : (
todos.map((todo, idx) => (
<View style={styles.todo} key={idx}>
<Text style={styles.todoText}>{todo}</Text>
<View style={styles.delete}>
<Button
color='red'
title='Delete'
onPress={() => deleteTodo(idx)}
></Button>
</View>
</View>
))
)}
</ScrollView>
</View>
</TouchableWithoutFeedback>
);
}
Dont use passed todo value newTodo, as setState is async dont get executed immediately, so you can use current setted todo value instead passed old value,
const addItem = (newTodo) => {
if (todo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(todo);
let newTodos = [todo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
setTodos(newTodos);
}
};

Not geting data from return inside firebase database

I am getting data from the firebase but able to it on emulator I tried using consloe which workfine
const Getdata = async () => {
await firebase.database().ref(`/orders/${user1.uid}`)
.on("child_added", (snapshot, key) => {
if(snapshot.key) {
console.log('key',snapshot.key);
let grabbedData = snapshot.val().orders;
grabbedData.map((order, i) => {
console.log('order',order.id);
console.log('order',order.avatar);
console.log('order',order.name);
console.log('order',order.price);
console.log('----------------');
});
}
});
}
Getdata();
After modifing the above code as below code nothing is showing to the screen
const Getdata = () => {
let data = firebase.database().ref(`/orders/${user1.uid}`)
.on("child_added", (snapshot, key) => {
// something is wrong with this below statememnt I think
return (
<Card>
<Text>{snapshot.key}</Text>
{
snapshot.val().orders.map((order, i) => {
return (
<TouchableOpacity key={i} onPress={() => {
}}>
<Card>
<View style={styles.user}>
<Image
style={styles.image}
resizeMode="cover"
source={{ uri: order.avatar }}
/>
<View style={{flexDirection:'column', flex: 1}}>
<Text style={styles.name} h4>{order.name}</Text>
<Card.Divider style={{ marginTop: 25}}/>
<View style={{flexDirection:'row', flex: 1,justifyContent: 'space-between'}}>
<Text style={styles.price}>{order.price}</Text>
</View>
</View>
</View>
</Card>
</TouchableOpacity>
);
})
}
</Card>
)
})
return data;
}
and then <Getdata/>
Something I am doing wrong with first return statememnt but dont know what.
Edit I am adding a pic how data is organised
Try this way
const [orders, setOrders] = useState([]); // initially empty
const [key, setKey] = useState(undefined); // undefined empty
const Getdata = async () => {
await firebase.database().ref(`/orders/${user1.uid}`)
.on("child_added", (snapshot, key) => {
if(snapshot.key) {
console.log('key',snapshot.key);
let grabbedData = snapshot.val().orders;
setKey(snapshot.key); // set key here
setOrders(grabbedData); // set orders here to state, it will rerender
}
});
}
useEffect(() => {
Getdata();
});
return (
<Card>
{key && <Text>{snapshot.key}</Text>}
{
orders.map((order, i) => {
return (
<TouchableOpacity key={i} onPress={() => {
}}>
.........
</TouchableOpacity>
);
})
}
</Card>
)

Class component to functional component is not working as expected

I am implementing infinite scrolling with react-native, when I do a search the result is returned and if the result has many pages on the API, when I scroll the API returns more data .
my implementation works fine on the class component but when I try to convert it to a working component, when I do a search, the data is returned and if I did another search, the previous data from the previous search is still displayed
class component
class Exemple extends React.Component {
constructor(props) {
super(props);
this.searchedText = "";
this.page = 0;
this.totalPages = 0;
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms() {
if (this.searchedText.length > 0) {
this.setState({ isLoading: true });
getFilmsWithSearch(this.searchedText, this.page + 1).then((data) => {
this.page = data.page;
this.totalPages = data.total_pages;
this.setState({
films: [...this.state.films, ...data.results],
isLoading: false,
});
});
}
}
_searchTextInputChanged(text) {
this.searchedText = text;
}
_searchFilms() {
this.page = 0;
this.totalPages = 0;
this.setState(
{
films: [],
},
() => {
this._loadFilms();
}
);
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._searchFilms()}
/>
<Button title="Rechercher" onPress={() => this._searchFilms()} />
<FlatList
data={this.state.films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (this.page < this.totalPages) {
this._loadFilms();
}
}}
/>
{this._displayLoading()}
</View>
);
}
}
the functional component
const Search = () => {
const [films, setFilms] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [searchedText, setSearchedText] = useState("");
const _loadFilms = () => {
if (searchedText.length > 0) {
setIsLoading(true);
getFilmsWithSearch(searchedText, page + 1).then((data) => {
setPage(data.page);
setTotalPages(data.total_pages);
setFilms([...films, ...data.results]);
setIsLoading(false);
});
}
};
useEffect(() => {
_loadFilms();
}, []);
const _searchTextInputChanged = (text) => {
setSearchedText(text);
};
const _searchFilms = () => {
setPage(0);
setTotalPages(0);
setFilms([]);
_loadFilms();
};
const _displayLoading = () => {
if (isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
};
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder="Titre du film"
onChangeText={(text) => _searchTextInputChanged(text)}
onSubmitEditing={() => _searchFilms()}
/>
<Button title="Rechercher" onPress={() => _searchFilms()} />
<FlatList
data={films}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <FilmItem film={item} />}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (page < totalPages) {
_loadFilms();
}
}}
/>
{_displayLoading()}
</View>
);
};
With functional components, you cannot run effects (like getFilmsWithSearch) outside of useEffect.
From https://reactjs.org/docs/hooks-reference.html#useeffect
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
When you are calling _loadFilms from within then onSubmitEditing={() => _searchFilms()} event handler, you are not running inside useEffect, unlike the call to _loadFilms from useEffect that runs with the component mounts (because the second parameter to useEffect is [], it runs once on mount).
To solve this issue, you would typically have _searchFilms set a state variable (something like reloadRequested, but it does not have to be a boolean, see the article below for a different flavor) and have a second useEffect something like this:
useEffect(() => {
if (reloadRequested) {
_loadFilms();
setReloadRequested(false);
}
}
, [reloadRequested])
For a more complete example with lots of explanation, try this article https://www.robinwieruch.de/react-hooks-fetch-data.

How do I get data from firebase realtime databese with React Native?

I'm trying to get a data firebase realtime database. I can show data with console.log() but I cannot show in Flat list. I tried different ways to solve this problem. But, I could not find any certain solution.
Here, my JSON tree in firebase:
Here, my code:
const NotesList = (props) => {
const { colors } = useTheme();
const styles = customStyles(colors);
const user = auth().currentUser
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const getData = () => {
firebase.database().ref(`notes/`).on('value', function (snapshot) {
console.log(snapshot.val())
});
}
useEffect(() => {
setTimeout(() => {
setData(getData);
setLoading(true);
}, 1000);
}, []);
const renderItem = ({ item }) => {
return (
<View style={{ marginRight: 10, marginLeft: 10 }}>
<TouchableOpacity>
<NoteCard title={item.noteTitle} icerik={item.noteDetails} date={item.timestamp} />
</TouchableOpacity>
</View>
);
};
const split = () => {
let icerik = data.map((item, key) => item.icerik);
return icerik.slice(0, 1) + '...';
};
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<NoteSearchBar />
{!loading ? (
<View style={{ alignItems: 'center' }}>
<ActivityIndicator color="#ff5227" />
</View>
) : (
<FlatList
data={data}
numColumns={2}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
/>
)}
<TouchableOpacity
style={styles.addButton}
onPress={() => props.navigation.navigate('AddNote')}>
<Plus width={40} height={40} fill="white" margin={10} />
</TouchableOpacity>
</View>
);
};
I tried many different ways, but no suggestions solved the problem. I would be glad if you could help.
Your getData doesn't return the data, as the data is loaded asynchronously. You should instead move he call to setData into your getData:
const getData = () => {
firebase.database().ref(`notes/`).on('value', function (snapshot) {
setData(snapshot.val());
});
}
useEffect(() => {
setTimeout(() => {
getData()
setLoading(true);
}, 1000);
}, []);
Now the call to setData will put the JSON into the state, which in turn forces React to repaint the UI with the new data.
Ciao, basically you made 2 mistake:
getData does not return nothing so data will be never updated with values from firebase;
useEffect has a strange implementation (it's useless call setTimeout you are already waiting data from firebase);
My suggestion is to call getData from useEffect like this:
useEffect(() => {
getData()
}, []);
And modify getData like this:
const getData = () => {
firebase.database().ref(`notes/`).on('value', function (snapshot) {
setData(snapshot.val());
setLoading(true);
});
}
I found solution and I want to share it with you guys.
I kept the data from firebase as a child at a new value, then I put it in setData. In this way, I can display the data in the flat list.
const getData = () => {
firebase.database().ref(`notes/`).on('value', snapshot => {
let responselist = Object.values(snapshot.val())
setData(responselist)
console.log(snapshot.val())
setLoading(true);
});
}
useEffect(() => {
getData();
}, []);
Also, while firebase realtime DB was working, a new method called Object method helped when taking child values in the JSON tree.

Categories

Resources