How to pass parameters to another screen without navigating to it - reactjs

Right now to pass parameters to another screen I'm using the following:
this.props.navigation.navigate('MyWatchList', {watchLists: response});
This automatically navigates to the other screen. I'm trying to pass parameters to another screen without navigating away from the screen I'm on but I'm having trouble figuring out how to do it. I have been trying to use setParams but I have been having no success. Any help or a point in the right direction would be appreciated.

You can consider to use the asyncStorage in order to share data between your screens.
e.g
AsyncStorage.setItem('MyWatchList',JSON.stringify({watchLists: response}));
and retrieve by using
AsyncStorage.getItem('MyWatchList').then(item => {
const list = JSON.parse(item);
...
});

You can also use EventRegister
import { EventRegister } from "react-native-event-listeners";
send value like this:
<TouchableOpacity
onPress={() => {this.setState({ visibleModal: false });
EventRegister.emit("changeModalEvent", this.state.visibleModal);
}}
>
{this.state.theme == true ? (
<Icon3 name="cancel" color={Colors.night_color_text} size={30} />
) : (
<Image
source={require("../assets/images/Auto_Layout_Horizontal.png")}
style={{ height: 24, width: 24 }}
/>
)}
</TouchableOpacity>
read the value in any screen with respect to the key like this:
useEffect(() => {
let eventListner = EventRegister.addEventListener(
"changeModalEvent",
(data) => {
// console.log({ data });
setVisibleModal(data);
}
);
return () => {
EventRegister.removeEventListener(eventListner)
};
});

what i think you trying to achieve is persistent state across screen you can do that by multiple of ways
you can use global variable you will be able to read and write to them from all of your classes/functions read more here
How to use global variables in React Native?
But the most effective way is to use redux to mange your state
Redux is an open-source JavaScript library for managing and
centralizing application state.

You can Store Your Data in Array and when you navigate to next screen , Pass that Array with navigation

Related

TypeError with .map and The Movie Database

I am learning programming since a few months and I am starting to practice with some projects that I could create by myself or that I could find on the internet to study and learn more about different topics. In this case, I am working with React.js, Material UI and The Movie Database trying to make some kind of website related to movies and series. I was doing fine, until I needed to display all the genres and filter them on the page.
My intention is to display all the genres from The Movie Database and filter them when I needed. In some cases this code works fine and without any issue but in general it says that "genres.map is not a function" and the component doesn't render.
I know that this type of error normally happens when I try to map something that it is not an array. However, in this case genres IS an array. The most annoying thing for me is that in some rare cases the code works fine.
As I said before, I am new relatively new to programming and it is my first question here so I apologize if this is not the place to ask this. I would be very grateful if someone could help me to solve this and most important to understand what is the problem. Many thanks in advance!
import { Chip } from "#mui/material";
import axios from "axios";
import React, { useEffect } from "react";
const Genres = ({
selectedGenres,
setSelectedGenres,
genres,
setGenres,
type,
setPage,
}) => {
const addGenre = (genre) => {
setSelectedGenres([...selectedGenres, genre]);
setGenres(genres.filter((g) => g.id !== genre.id));
setPage(1);
};
const removeGenre = (genre) => {
setSelectedGenres(
selectedGenres.filter((selected) => selected.id !== genre.id)
);
setGenres([...genres, genre]);
setPage(1);
};
const fetchGenres = async () => {
const { data } = await axios.get(
`https://api.themoviedb.org/3/genre/${type}/list?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`
);
setGenres(data.genres);
};
useEffect(() => {
fetchGenres();
return () => {
setGenres({});
};
// eslint-disable-next-line
}, []);
console.log(genres);
return (
<div style={{ padding: "6px 0" }}>
{selectedGenres.map((genre) => (
<Chip
label={genre.name}
style={{ margin: 2 }}
size="small"
key={genre.id}
clickable
onDelete={() => removeGenre(genre)}
/>
))}
{genres.map((genre) => (
<Chip
label={genre.name}
style={{ margin: 2 }}
size="small"
key={genre.id}
clickable
onClick={() => addGenre(genre)}
/>
))}
</div>
);
};
export default Genres;
genres must either consistently be an array, or your code must handle cases that are not an array. In your case the problem is here:
return () => {
setGenres({});
};
This will cause the state to be set to an object every time this component unmounts. Objects are not an array, and so do not have the functions of an array. When the component mounts again, it will be an object still for a brief period, before it fetches from the API and it gets set back to an array again.
Instead, use:
return () => {
setGenres([]);
};
Additionally, ensure that the default state where genres is initially declared (probably in the parent, or some ancestor) is set to an array otherwise it could be some other type before they are fetched from the API:
const [genres, setGenres] = useState([])
Off-topic but I'd also question if it's necessary to set it back to an empty array on unmount in the first place. Doing nothing would mean the old data is still there when this component remounts and the user would not need to wait for it to fetch again. The new data would replace once it arrives from the API. You could always implement a loading state to tell the user it is refreshing whilst not blocking them from seeing the "stale" data.

Button to load new screen but staying on one page

Okay so I am trying to accomplish what these photos show. I want to be able to go from list, which is a view with a white background that has some content to when clicked on map, it does not necessarily change pages, but it does load this screen of a map.
What is the best way to go about doing this for react native? I tried googling around but could not find much, it was hard to google for.
Thank you guys, I appreciate it more than you know.
I think you may be asking about how to conditionally render something. You can render components based on the value of another variable. Something like this.
const [viewMode, setViewMode] = useState('list');
const handleShowListPress = () => {
setViewMode('list');
};
const handleShowMapPress = () => {
setViewMode('map');
};
...
return (
<View>
<ListButton onPress={handleShowListPress} />
<MapButton onPress={handleShowMapPress} />
{viewMode === 'list' && <ListView />}
{viewMode === 'map' && <MapView />}
</View>
);

Why is my React Native component not re-rendering on state update?

I'm struggling with this React-Native component for a few days now. You should probably know that React-Native is kind of new to me so... sorry if the solution is obvious to you.
I'm using react-native-maps and I have several markers on my map. Each one of them has some data stored in my state and I want the callout to display a piece of this state on press.
Here are my states :
const [markersDetails, setMarkersDetails] = useState([]);
const [activeMarker, setActiveMarker] = useState({});
My activeMarker is updated by this function :
const markerSearch = (markerId) => {
let stockMarker = markersDetails.find((singleMarker) => {
return Number(singleMarker.idMarker) === markerId;
});
console.log("Stock ", stockMarker);
setActiveMarker(stockMarker);
console.log("State ", activeMarker);
};
And this function is called, inside my return, with the onPress of any marker :
<Marker
key={Number(marker.index)}
coordinate={{
latitude: Number(marker.latitude),
longitude: Number(marker.longitude),
}}
pinColor="blue"
onPress={() => {
markerSearch(Number(marker.index));
}}
>
{activeMarker !== {} && activeMarker.markerName && (
<Callout>
<View>
<Text>{activeMarker.markerName}</Text>
</View>
</Callout>
)}
</Marker>
But whenever I press on a marker, the callout opens immediatly while my state is not yet updated. So the text within the callout refers either to the previous marker or is empty (if it's the first marker I press on).
I've checked with console.log and my state is clearly updated but it takes a little bit more time. And I don't know why my callout is not re-rendering when this state is updating.
I've tried a ton of things to make this works but I can't figure this out...
Try doing something like that:
You can extract the section to a new component
Then inside this use the useEffect hook
export default function CalloutComponent({activeMarker}) {
const [markerName, setMarkerName] = useState('')
useEffect(() => {
setMarkerName(activeMarker?.markerName)
}, [activeMarker?.markerName])
if(!!markerName) return null
return (
<Callout>
<View>
<Text>{markerName}</Text>
</View>
</Callout>
)
}
And use this new component in your Main view
<Marker
...
>
<CalloutComponent activeMarker={activeMarker}/>
</Marker>

React Native - How can i display data from an API?

I'm a beginner in React Native and I am developing a movie app using The Movie Database API.
I managed to extract the info from each movie. However, I find it difficult to extract information on the casting of films. I only managed to extract data of type "String" using the function "map". I would like to know how we can extract photos from actors and improve the display of data.
Here is an example of the API data:
_displayFilm() {
const { acteur } = this.state
if (acteur != undefined) {
return (
<ScrollView style={styles.scrollview_container}>
<Text style={styles.default_text}>Acteurs(s) :{"\n"}{acteur.cast.map(function(casts){
return ( casts.name + ' ' + casts.character)
}).join("\n")}
</Text>
</ScrollView>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
{this._displayFilm()}
</View>
)
}
}
Thank you for your help
You would probably want to display this data using a FlatList rather than in a ScrollView.
FlatLists are very easy to set up and manage. You can see more about them in the docs here.
Below is some sample code to get you started. You can update your component to incorporate the following. You would need to add two functions to the body of your component; one is a key extractor and the other is a render item (I've named them appropriately). Then in your component's render method add the code for the FlatList.
Make sure you also import FlatList from react-native
...
_keyExtractor (item, index) {
return index.toString();
}
_renderItem ({ actor, index }) {
return (<Text>{actor.name} - {actor.character}</Text>);
}
render() {
return(
...
<FlatList style={{ flex: 1 }}
data={this.state.acteur.cast}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
/>
...
);
}
...
Adding Images
To add images to your list of characters you could update the _renderItem
method to return a better styled item, that could include images etc. For networked images look to the docs again, all the information is there.
Example:
_renderItem({actor, index}) {
return (
<View style={{flexDirection: 'row'}}>
<Image
source={`https://webaddress.com${actor.profile_path}`}
style={{width:40, height:40}}
/>
<Text>{actor.name} - {actor.character}</Text>
</View>
);
}
Remember to import Image from react-native and to use the correct base url for the web address so that you can get the image.
The above should get your started and you should only have to make some minor changes to the code so that it works for you.

How to properly update/re-render a component that is not a child React Native?

I'm using a react-navigation. More specifically, I have a materialTabNavigator nested inside of a drawerNavigator. Each tab is in itself a stackNavigator. I have a button in homeScreen, that navigates to makePost.js. There I take in information and store it to Async storage using a simple wrapper.
In Posts.js there's a FlatList displaying each post as a component. The data for the FlatList is initially set correctly after making a request from Async Storage. The problem is that this only happens when the app is first opened. I have tried many different approaches to solve this. The only way so far I've found is to continuously setState in ComponentDidUpdate() in Posts.js. Obviously this is problematic, because it re-renders constantly. I can set a flag to stop is from rendering, but then it will not re-render again.
Ultimately, what I'd like to happen is that when I hit the user is done entering their information and is ready to make a post, they hit the button in makePost.js, and the data in the FlatList of Posts.js is update.
I've tried to pass parameters using navigation, does not work, parameters get lost somewhere, probably because of the nested navigators.
I could really used some guidance on the proper way to accomplish this.
( Navigators; not sure why this is forcing to one line )
---drawer
--tabNav
-home
homeScreen.js
makePost.js
-posts
posts.js
-messages
--drawer1
--drawer2
//Posts.js
export default class Posts extends React.Component {
state = {
rows: [
{id: 0, text: "dog"},
],
}
componentDidMount() {
this.loadState();
}
loadState = () => {
var value = store.get('posts').then((res => {
if (res === null) {
res = [{id: 0, text: "default"}]
} else {
res = res
}
this.setState({rows: res})
}))
}
componentDidUpdate() {
this.loadState();
}
renderItem = ({item}) => {
return (
<BoardTab style={styles.row} />
)}
render() {
return (
<View style={styles.view}>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<BoardScreenFooter />
</View>
);
}
And Posts.js button looks like this:
<TouchableOpacity
onPress={ () => {
this._onPressButton
this.storeFunc(this.state.newPost)
const retval = this.state.rows
this.props.navigation.navigate('Board',
{rowsID: retval});
}
}>
<Icon
reverse
name='md-camera'
type='ionicon'
color='green'
size={12}
/>
</TouchableOpacity>
storeFunc(newObj) {
newObj.id = newObj.id + 1
store.push('posts', newObj)
store.get('posts').then((res) => {
this.setState({rows: res})
})
}
Rapidly, i would say: use Redux. It alloq you to have global state in your app, which mean you can access the state anywhere (And also set them anywhere)
When opening the app, you get the data from the AsyncStore into the Redux store. You listen to the redux state (Which will be a props in your component) and display your list. When modifying your list in the other tab, you need to do 2 things:
Store the new data in the AsyncStorage
Update the state in the redux store. Since Posts.js will be listening at the redux store (as a props), it will re-render each time your data will change
A simple way to re-render a React-Navigation screen view on navigating to it:
All credit goes to Andrei Pfeiffer, Jul 2018, in his article: "Handle Tab changes in React Navigation v2" https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
I will reiterate it here in case the above link goes dead.
Simply add a NavigationEvents component to your render function with the desired listener prop:
render() {
return (
<View style={styles.view}>
<NavigationEvents
onWillFocus={payload => {
console.log("will focus", payload);
this.loadState();
}}
/>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<PostScreenFooter />
</View>
);
}

Resources