How to refresh a screen in react native inside a function? - react-hooks

I want to be able to delete elements from my FlatList. I couldn't do it with the onPress of the TouchableOpacity in ItemView so I decided to create a Button with the onPress={botClick} so when I fill the TextInput above that Button it erases that element from the AsyncStorage and then the element is also removed from proddata. My problem is that to see that element removal from the FlatList I have to change my navigation screen to another one and return to see the changes reflected. Can I put something inside of botClick() that refreshes or recharges the screen when the function is called to see the changes automatically without changing screens?
export default function TestScreen () {
const [proddata, setProddata] = useState([]);
const [deletepar, setDeletepar] = useState('');
const whenClick = () => {
console.log("hello");
}
async function botClick(){
try {
await AsyncStorage.removeItem(deletepar);
console.log("Removed");
//Add something here that refreshes or recharges the screen
}
catch(exception) {
}
};
const ItemView = ({item}) => {
return (
<TouchableOpacity onPress={whenClick}>
<View>
<Text>
{item[0]+ ' ' + item[1]}
</Text>
</View>
</TouchableOpacity>
);
};
async function carInState() {
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys);
setProddata([...proddata, ...result]);
}
useFocusEffect(
React.useCallback(() => {
carInState();
}, [])
);
return (
<View>
<View>
<TextInput placeholder="..." onChangeText={(val) => setDeletepar(val)}/>
<View>
<Button title="Delete" onPress={botClick}/>
</View>
</View>
<FlatList
data={proddata}
renderItem={ItemView}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
};

It isn't a problem you can manipulate the state directly or get all data againg
async function botClick() {
try {
await AsyncStorage.removeItem(deletepar);
// 1) Variant load data again & invoke setProddata
const keys = await AsyncStorage.getAllKeys();
const result = await AsyncStorage.multiGet(keys);
setProddata([...result]);
// 2) Or you can remove it from you list
setProddata(prevProddata => prevProddata.filter(value => value.x.x.x !== deletepar))
} catch (exception) {}
}

Related

useEffect not triggered by prop dependency

So I have this component set up, which has an onClick handler to like or unlike a certain recipe. I am using the useEffect hook to make sure that the icon is changed accordingly based on the favoriteId prop. When the onClick and the associated queries are executed however, the useEffect hook is not triggered at all, how come?
const RecipeCard = ({ name, image, id, favoriteId }) => {
const { user } = useContext(AuthenticatedUserContext);
const [isFavorite, setIsFavorite] = useState(false);
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
favoriteId = res.id;
}
};
useEffect(() => {
console.log("hit");
favoriteId ? setIsFavorite(true) : setIsFavorite(false);
}, [favoriteId]);
return (
<TouchableWithoutFeedback
onPress={onPress}
style={{ flex: 1, padding: 10 }}
>
<View>
<AntDesign
onPress={() => {
if (!user) {
setShowNoAccountModal(true);
} else {
onLikePress();
}
}}
name={isFavorite ? "like1" : "like2"}
color="black"
size={30}
/>
</View>
</TouchableWithoutFeedback>
);
};
export default RecipeCard;
Parent component:
export const HomeScreen = () => {
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getRecipes();
}, []);
const getRecipes = async () => {
const querySnapshot = await getDocs(collection(db, "receipes"));
const fetchedRecipes = [];
for (const d of querySnapshot.docs) {
const citiesRef = collection(db, "favorites");
const q = query(
citiesRef,
where("userId", "==", user.uid),
where("recipeId", "==", d.id)
);
const querySnapshot = await getDocs(q);
const isFavorite = false;
if (querySnapshot.empty) {
favoriteId = null;
} else {
favoriteId = querySnapshot.docs[0].id;
}
const recipe = {
...d.data(),
id: d.id,
favoriteId,
};
fetchedRecipes.push(recipe);
}
setRecipes(fetchedRecipes);
setLoading(false);
};
return (
<View style={styles.container}>
{/* <Button title="Sign Out" onPress={handleLogout} /> */}
<Text style={{ fontSize: 24, fontWeight: "bold", paddingBottom: 10 }}>
Recepten
</Text>
{recipes && recipes.length > 0 && (
<FlatList
data={recipes}
renderItem={({ item }) => (
<RecipeCard
name={item.title}
id={item.id}
image={item.thumbnail}
favoriteId={favoriteId}
/>
)}
keyExtractor={(item) => item.id}
horizontal
/>
)}
</View>
);
};
You are mutating your favoriteId variable, but not using setState, so it is not done properly and react is unaware your variable might have changed.
To fix this, you will need to pass a function to change your favoriteId prop inside of your component's parent:
// in parent:
const [favoriteId, setFavoriteId] = useState() // this code should be here already
return (
// your code here
<RecipeCard changeFavoriteId={(newId) => setFavoriteId(newId)} />
// just add this changeFavoriteId prop, the old props should be here still though
// rest of your code
)
// in RecipeCard.js
const RecipeCard = ({ name, image, id, favoriteId, changeFavoriteId }) => {
// your code here
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
changeFavoriteId(res.id) // this bit here changed, now you are using setState
}
};
// the rest of your code
By using setState function that you obtained from parent useState, your component will trigger a rerender after the value is changed.

Setting the state from a callback in the parent component doesn't work

I'm trying to set the state from the parent component using a callback. This callback gets passed down to the child component that renders a material ui datatable. The callback responds onClick and passes some values to the callback. The problem is that setting the state with the values from the callback arguments doesn't work.
My assumption is that when the user clicks the button from the child component, it should invoke the callback function and pass the values I needed to set the state.
Parent Component:
export default function ViewJobs() {
const [type, setType] = useState('');
const [params, setParams] = useState({});
const callback = ({ cellValues, componentType, path }) => {
setType(componentType);
setParams(cellValues) // Sets the params with an object.
console.log(cellValues) // Displays the data I need in the console
history.push(path);
};
console.log(params) // Displays undefine in the console.
return(
<React.Fragment>
<TabPanel value={value} index={0} dir={theme.direction} >
<DataTable
jobs={job}
title='All'
parentCallback={callback}
/>
</TabPanel>
</React.Fragment>
);
}
Child Component
import React, { useEffect } from 'react';
export default function DataTable(props) {
const { jobs, parentCallback } = props;
const rows = jobs.payload;
const handleDiaryClick = (event, cellValues) => {
const params = {
cellValues,
componentType: 'diary',
path: "/view/jobs/diary"
};
parentCallback(params);
};
const renderDiaryElement = params => {
return (
<Button
variant="contained"
color="primary"
style={{ backgroundColor: "#000000" }}
onClick={(event) => {
handleDiaryClick(event, params);
}}
>
<MenuBookIcon />
</Button>
);
}
return (
<div
className={classes.root}
style={{ height: 400, width: '100%' }}
>
<DataGrid
rows={rows}
columns={columns}
pageSize={5}
//checkboxSelection
disableSelectionOnClick
/>
</div>
);
}
Since the state has been lifted up to the parent component, I'm under the impression that the code above should be working.
I tried to reproduce the issue but I couldn't replicate it.
Any advice or inputs are appreciated. Thanks.
After further checking on my codebase, I found that the history.push(path) located in my callback is causing the issue. I had to remove this line of code for it to work.

React Navigation header button that controls rendering of the screen

PLEASE NOTE that 'this' is not accessible from a static function: React Native : Access Component state inside a static function
I am trying to define a button in the screen's header that, when clicked, will affect rendering, and will be replaced with another icon.
It is an old app, still using react navigation 3.
I didn't know how to do the following things:
modify the component's state from a function that is activated when the button is pressed
modify the screen parameter from this function
What I managed to implement is the following lame and embarrassing way to do it:
When the button is clicked, a static function is executed, which modfies a static variable
periodic code is fired in componentDidMount that checks whether the static detailedDisplay variable has been modified. If it has been modified, this code sets a state variable that affects rendering. This periodic code also modifies the screen parameter which changes the icon in the header (because, as I wrote above, I also failed to set the parameter from the static function).
How can this be done in not-so-lame way?
Here is my code:
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
var detailedDisplay = false;
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
static toggleDisplay(navigation) {
detailedDisplay = !detailedDisplay;
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ detailedDisplay });
}
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
}
componentDidMount() {
setInterval(() => {
if (detailedDisplay !== this.state.detailedDisplay) {
this.props.navigation.setParams({ detailedDisplay });
this.setState({ detailedDisplay });
}
}, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}
I think you can try to use your state inside your navigationOption method
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
this.state.detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
And then change your state in the toggleDisplay method
static toggleDisplay(navigation) {
this.setState(state=> detailedDisplay:!state.detailedDisplay)
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ this.state.detailedDisplay });
}
You can try and keep the param and a state variable in sync.
An example POC is here
Here, I have tried to keep the state toggleVariable and the param value in sync but updating them through a common setter, and on Component Mount it will just be the same as the parameter.
You cannot read properties of this because this in JS is dynamic, and when you pass function as callback this is lost. There is 2 solutions that i know:
One of them bind this using bind method:
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
this.toggleDisplay = this.toggleDisplay.bind(this);
this.navigationOptions = this.navigationOptions.bind(this)
}
Now you can use non-static methods and you can access react state and methods in them:
navigationOptions({ navigation }) {
// somewhere in code <button onPress={this.toggleDisplay}><button>
// this.props this.state this.setState is available here
};
toggleDisplay(navigation) {
// this.props this.state this.setState is available here
// this.props.navigation.setParams() is available too
}
Finally, you can change the detailedDisplay state and screen params in toggleDisplay function. So when you'll press button state'll be changed and your commponent'll be re-rendered.
Additional
Second way to do it is to call method in a function:
onPress={() => this.toggleDiaplay()}
Here you must also use non-static methods, and this will work the same way as the first solution with bind.
Try doing something like this:
UPDATED: removed static from the toggleDisplay and added Class reference in the static function.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
state = {
detailedDisplay: false,
};
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: navigation.getParam('detailedDisplay', false) ?
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
toggleDisplay() {
this.setState({detailedDisplay: !this.state.detailedDisplay})
this.props.navigation.setParams({detailedDisplay: true});
}
constructor(props) {
super(props);
}
componentDidMount() {
// setInterval(() => {
// if (detailedDisplay !== this.state.detailedDisplay) {
// this.props.navigation.setParams({ detailedDisplay });
// this.setState({ detailedDisplay });
// }
// }, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}

Retrieve array of images from Firestore

I made a query to take all the fields of a specific document from Firestore. One of those fields is an array of images, taken from the Firebase Storage. I don't know why, but i can't retrieve those images.
EDIT: I tried to add a abc after the log, before the image, nothing shows.
Here my code:
const [photo, setPhoto] = useState([]);
useEffect(() => {
handlClick();
}, []);
const handlClick = () => {
var i = 0;
setRestaurant([]);
setPhoto([]);
db.doc(idBranch).get().then(data => {
const branchData = data.data();
setRestaurant(branchData);
const ph = branchData.Photo;
ph.forEach(pho =>{
setPhoto(p => [...p, pho]);
})
})
}
return (
{photo && photo.forEach((ph) => {
<View>
<Text> {console.log(ph)}</Text>
<Image source = {{uri : ph}}/>
</View>
})}
)
The console.log is correct (even if they are retrieved more than one times each and shouldn't do that), here the image:
Here the Firestore structure:
Please, help me to understand what I should do to retrieve the images.
Your functional component must return JSX but not the object. Also you must use .map method if you want to render JSX via loop. Try this:
return (
<View>
{photo && photo.map((ph) => (
<View>
<Text>{console.log(ph)}</Text>
<Image source = {{uri : ph}}/>
</View>
))
}
</View>
)

React Navigation: setParams in Nested Navigator

In my React Native application, I use React Navigation.
It's an app that enables the user to search an underlying database, i.e. for names. The GIF below illustrates the navigation.
From the landing screen, Go to search button is pressed (Main Stack Navigator) --> The Header appears, which is alright.
On the second screen, there is a bottomTabNavigator, where names is chosen (in names, there is a second StackNavigator nested).
This leads to the third screen. Here, three cards are shown. With the help of the second StackNavigator, clicking on Mehr opens a details screen.
What I want to achieve is that the Header of the first StackNavigator (that one at the top) disappears as soon as the user opens the details screen.
You see a button there because in the first step, I wanted to let the Header disappear on button click.
The below code works if it is implemented in a screen that is derived from the first StackNavigator directly. But because I am inside a nested navigator, it does not work anymore.
Here is the code:
App.tsx:
imports ...
class RootComponent extends React.Component {
render() {
const image = require('./assets/images/corrieBackground3.png');
console.log('calling the store', this.props.resultValue); // undefined
return (
<View style={styles.container}>
<LandingPage />
</View>
);
}
}
const RootStack = createStackNavigator(
{
LandingPage: {
screen: RootComponent,
navigationOptions: {
header: null,
},
},
SearchScreen: {
screen: SearchScreen,
navigationOptions: {
title: 'I SHOULD DISAPPEAR',
},
},
},
{
initialRouteName: 'LandingPage',
},
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
TwoTabs.tsx (for the 2nd screen):
imports ...
const SearchBarStack = createStackNavigator(
{
SearchBar: {
screen: SearchBar,
navigationOptions: {
header: null,
},
},
Details: {
screen: Details,
navigationOptions: {
title: 'I am here, above header disapear',
},
},
},
{
initialRouteName: 'SearchBar',
},
);
const TabNavigator = createBottomTabNavigator(
{
One: {
screen: SearchCriteria,
navigationOptions: {
tabBarLabel: 'criteria',
},
},
Two: {
screen: SearchBarStack,
navigationOptions: {
tabBarLabel: 'names',
},
},
},
);
const TabLayout = createAppContainer(TabNavigator);
type Props = {};
const TwoTabsHorizontal: React.FC<Props> = ({}) => {
return (
<View>
<TabLayout />
</View>
);
};
export default TwoTabs;
SearchBar.tsx (3rd screens skeleton):
import ...
type Props = {};
const SearchBar: React.FC<Props> = () => {
// logic to perform database query
return (
<View>
<ScrollView>
... logic
<SearchResult></SearchResult> // component that renders 3 cards
</ScrollView>
</View>
);
};
export default SearchBar;
Card.tsx (card rendered by SearchResult):
imports ...
type Props = {
title: string;
navigation: any;
};
const Card: React.FC<Props> = ({title, navigation}) => {
return (
<Content>
<Card>
<CardItem>
<Right>
<Button
transparent
onPress={() => navigation.navigate('Details')}>
<Text>Mehr</Text>
</Button>
</Right>
</CardItem>
</Card>
</Content>
);
};
export default withNavigation(Card);
And finally, the Details screen together with its Content. Here, the Header from the first StackNavigator should be hidden.
imports ...
type Props = {};
const Details: React.FC<Props> = ({}) => {
return (
<View>
<Content></Content>
</View>
);
};
export default Details;
imports ...
type Props = {
navigation: any;
};
class Content extends React.Component {
state = {
showHeader: false,
};
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
return params;
};
hideHeader = (hide: boolean) => {
this.props.navigation.setParams({
headerShown: !hide,
});
console.log('props ', this.props.navigation);
};
render() {
return (
<View>
<View>
</View>
<Button
title={'Press me and the header will disappear!'}
onPress={() => {
this.setState({showHeader: !this.state.showHeader}, () =>
this.hideHeader(this.state.showHeader),
);
}}
/>
</View>
);
}
}
export default withNavigation(CardExtended);
Maybe someone has an idea?

Resources