I have a situation right now with my RN app, and I don't know how to tackle it.
I'm logging the user in through Facebook API, in our backend, we handle all the FB user data, and also, its profile picture that we crop to a certain size for performance purposes.
To do this, we run an async worker that will do the cropping, in the meantime, in the app we show a default user avatar with the app logo. But once the worker finished the task, the image isn't updated, not until I re-render the view, this re-render causes to run again "renderUserAvatar()" function which validates if the user has a profile picture or not. Which makes sense.
Now here's the help, how can I listen to this URL availability? So that whenever the Image is available, it re-renders?
At first, I thought about adding something like handling the Image's onError, by setting a setInterval, and trying to force a re-render, but that doesn't look very performant it rather sounds ugly.
Is there a clean way to handle this specific case-scenario?
This is my current renderUserAvatar function:
renderUserAvatar() {
const { userInfo } = this.props;
if (!_.isEmpty(userInfo) && userInfo.userPictures && userInfo.userPictures.length) {
const avatar = userInfo.userPictures.filter(pic => pic.isAvatar && pic.isEnabled);
if (avatar && avatar.length) {
const url = `${avatar[0].url}?height=${USER_AVATAR_HEIGHT}&width=${USER_AVATAR_WIDTH}`;
return <Thumbnail large style={ styles.userProfilePic } source={{uri: url}}/>;
}
}
return <Thumbnail large style={ styles.userProfilePic } source={ImageAssets['user-avatar']}/>
}
(Thumbnail is a NativeBase's component based on React-Native Image. So it would have all the Image methods and props too)
You could put your image into a state-variable.
At the first load (or if User-Image are not yet fetched) it is your Placeholder-Image.
If the async function runs on entering the screen and the images was fetched, replace the placeholder-image in the state with your new one.
The change of a state-variable will cause a re-render and this way, you'r image should appear.
Related
I am currently developing an e-commerce management mobile application with React Native, and Redux. We persist data using redux-persist. This brings a great experience with cached data which is a principle in offline-first development.
But there might be a bug that can happen in the mobile world environment.
Let's assume that I have a reducer called "products". That reducer is just an array with product objects. The user logs in and now the data in that reducer is persisted. Later, my development team decides to update the mobile app with a new structure on that "products" reducer. The user's app gets updated, and now the persisted/cached data doesn't align with the new "products" reducer structure which leads to the app crashing.
I may be wrong, but is this an actual bug that can exist? If so, what is a work around or solution?
Thanks!
This is a potential bug that can exist i agree because it has happened to me twice and i discovered something which might be helpfull to you. The cause of the crash is dependent on what is stored on disk and what your code uses let me explain.
//let say we have a reducer
function ProductReducer(
state={
products:[],
fetching:false
},action){
switch(){......}
}
// lets say we have a combine reducer
combineReducers({
products: ProductReducer
})
//let say we have a component that consums this products
function RenderProducts(){
const {products, fetching} = useSelector((store)=>({
products: store.products.products,
fetching: store.products.fetching,
}))
return (
<View>
{
fetching && (
<Text>
loading...
</Text>
)
}
{products.map((item)=>
<View>
<Text>
{item.name}
</Text>
</View>)
}
</View>
)
}
NOW OUR UPDATE LOOKS LIKE THIS
// Now lets new reducer be
function ProductReducer(
state={
productsList:[],
fetchingProduct:false
},action){
switch(){......}
}
// Now our Render component becomes
function RenderProducts(){
const {products, fetching} = useSelector((store)=>({
products: store.products.productsList,
fetching: store.products.fetchingProduct,
}))
return (
<View>
{
fetching && (
<Text>
loading...
</Text>
)
}
{products.map((item)=>
<View>
<Text>
{item.name}
</Text>
</View>)
}
</View>
)
}
Now the second code is going to cause a crash crash for one reason you will be trying to call .map on an undefined object, why is this happening the simple reason is illustrated below
// Intial store
const store = {
products:{
products:[], // array of products
fetching: false,
}
}
**This was our store stored on disk as JSON string
**
AFTER OUR UPDATE TO REDUCER our store remains the same and before we fetch data from our server so that our reducer can write our update to disk the render product component is trying to apply our update and thus the crash
SOLUTION
You can purge the store // you will not want to this because all stored data including tokens and vital inofrmaton will be lost and user will start app afresh
Understanding this problem you can now have several work arounds like dispatching an action to enforce your migration on disk before your components get rendered . this is what i mean
to apply update i will do this ...
function ProductReducer(
state={
productsList:[],
fetchingProduct:false
},action){
switch(action.type){
case 'MIGRATING':{
if(store.products){ // checking if products exist on the products
reducer this will handle updates and new installs
state = {...state,productsList:
state.products,fetchingProduct:state.fetching}
}
return state;
}
}
}
This will write changes to disk but make sure this runs before your components are rendered i will do this for all reducers
I will just write my component to always check if a data they are reading is available or else render empty data like '',[],{} for string, array, and object respectively
My RN 0.62.2 app needs to automatically save page data just before the function component unmounts. The idea is that when the user close the page (detecting losing focus may not work here since user may zoom in image in modal screen), then the save (to backend server) is automatically triggered. Since it is function component, how to know when the component will unmount?
Here is the sample code of a function component shall do:
const MyCom = () => {
//do something here. ex, open gallery to upload image, zoon in image in `modal screen, enter input`
if (component will unmount) {
//save the data by sending them to backend server
}
}
The useEffect triggers with every rendering and will have performance issue if keep saving to backend server with each and every rendering. The auto save only happens once just before the component unmount. User may click Back or Home button to leave the page.
Yoı must use useEffect for componentWillUnmount in functional components.
const MyCom = () => {
//do something here. ex, open gallery to upload image, zoon in image in
useEffect(() => {
// Component Did Mount
return => {
// ComponentWillUnmount
}
},[])
return(/*Component*/)
}
I am making a react native map, using MapView from 'react-native-maps' and this marker clustering engine. This is the component for a cluster marker that has been working just fine with blazing performance:
export default class ClusterMarker extends PureComponent {
constructor(props) {
super(props)
this.state = {
tracksViewChanges: true
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props !== nextProps) {
this.setState(() => ({
tracksViewChanges: true,
}))
}
}
componentDidUpdate() {
if (this.state.tracksViewChanges) {
this.setState(() => ({
tracksViewChanges: false,
}))
}
}
render() {
/*...
bunch of unrelated code where 'message', 'image', 'coordinate' and 'pointCount' are set
...*/
return (
<Marker
anchor={{x:0.5,y:0.5}}
centerOffset={{x:0.5,y:0.5}}
coordinate={coordinate}
image={image}
title={pointCount}
description={message}
tracksViewChanges={this.state.tracksViewChanges}>
{/* <Text>{pointCount}</Text> */} <-------- I want this to work just as fast
</Marker>
)
}
}
See that title={pointCount} in Marker props? When a user clicks a cluster, an overlay pops up to show them how many pins are there in the cluster. I want to bring that text out of there and display it over the cluster. Note that in render inside Marker, there is a commented line. When uncommented, visually it does pretty much what is needed, but with a terrible perfomance on phone (1 fps would be an overstatement).
It's clear that the problem is with Text. My hypothesis is that the Text component keeps on checking parent if there are updates on text, or that it re-renders all the time for no reason. Here is a list of things I've tried to fix this:
Create a component extending from Text, performing the same task without weird updates or re-renders. No fps increase.
Create a component that renders a Text but never updates or re-renders for no reason. No fps increase.
Add the tracksViewChanges logic you can see above. That was somewhat of a success, increasing average fps from 1 to 5.
Use other marker clustering libs (all of which failed to work with decent performance even without the Text inside Marker).
Many hacky solutions from the internet, none of which actually improved performace.
This map is currently dealing with an average of 20,000 pins and the solution has to be performant as this pin count increases, because it will.
I would be very glad if anyone could help!
Perhaps this is not the answer you are looking for, but 20k markers is... a lot. Have you considered clustering the markers based on the zoom level? You could also filter out markers that are not in the map viewport.
Currently, I'm checking if the user is authorized during app start and based on the result, I display the app.
However, the user may sign in or sign out while using the app and I want to re-render the app in those cases so that my logic stays at one place (ie at app start).
Is there a way to achieve that?
By default, when an state changes, render function will call again. you can change render function based on an state and then when that state changed, the view has changes... for example:
render() {
return (
<View>
{
this.state.authorized == true ?
<Text>authorized</Text>
:
<Text>unauthorized!</Text>
}
</View>
)
}
so when this.state.authorized changes, render function shows different view based on state.
But if you want to restart the app on state change, you can use React Native Restart package and restart app when you want. There is no way to restart your app on every state change because there is many state changes in RN lifecycle and your app will restart frequently. I hope it will help you.
render() {
const { authorised } = this.state;
const userMessage = authorised ? "authorized" : "unauthorized";
return (
<View>
<Text>{ userMessage }</Text>
</View>
)
}
You can also use props to maintain the user's status and render the components/messages based on the props values.
I have 3 pages that will be interacting with each other. a Login page, a Settings page, and a HomeScreen page. The login page contains a toolbar which has a clickable back arrow image(uses goBack()) and a clickable settings image which redirects to a settings page where a language can be picked.
There is no problem detecting a language change on the settings page because state is updated upon a change in language. However, if the user taps the backarrow image, the login page does NOT detect a change in state. How do I make sure that the login page can detect if the language has been changed(on the Settings page)?
I found on question that is similar to my problem here however, the answer provided uses navigate, whereas I'm using goBack. I know they're similar, but I'm unsure as to how/where I could pass a callback function on my settings page, and where to use refresh() on my Login page.
I use this method on my Login page
componentWillMount() {
AsyncStorage.getItem('language', (err, result) => {
langCode = result;
this.setState({
currLang: result,
})
});
}
On my Settings page:
onLangChange(value, index){
Config.langCode = value;
this.setState({ currLang: Config.langCode });
AsyncStorage.setItem('language', value)
console.log( 'here is your new lang ' + Config.langCode);
}
and then
render(){
const {navigate} = this.props.navigation;
const {goBack} = this.props.navigation
const langText = Config.langText[this.state.currLang]; // Object that has text in current language.
return(
<ScrollView style={styles.mainContainer}>
<View style={styles.headerContainer}>
<View style={styles.iconContainer}>
<TouchableOpacity onPress={ () => goBack('') } >
<Image source={Config.images.leftArrowIcon} style={styles.leftArrowIcon} />
</TouchableOpacity>
Use redux for global states like language in your example. It's just much simpler. If you have like 30 screens in your app, and every screen must display texts in correct language, this means that you have to send language back and forth between 30 screens, that's just horrible. redux is a state container, it's strongly recommended that you put those global states (states that many screens share) in redux and send them to each screen. If you don't know how to use redux yet, don't worry, it's not hard to understand, there're plenty good tutorials online. Here is the LanguagePickerExample I wrote for this question using redux, simply
npm install
or
yarn
then
react-native link
The result looks like this: