Updating firebase user picture not rendering new photo until reloading app - firebase

I am updating a user profile picture and am able to see the image changing in the database. I am storing the location of the image in profile picture. I am able to see the change happen when reloading the app and am able to see the image in firebase storage change instantly.
Here is the structure:
Here is the function I am using to change the image:
export const changeUserAvatar = (userId, imageUrl) => {
firebaseService
.database()
.ref('users/' + userId)
.update({
profilePicture: imageUrl
});
};
And here is how I am rendering the image:
<Avatar size="xlarge" rounded source={{ uri: this.props.profilePicture }} onPress={this.openPicker}/> :
I am using redux to manage the state and would think it would automatically re-render with an updating value of profile picture. I have tried everything in here:
https://github.com/facebook/react-native/issues/9195
https://github.com/facebook/react-native/issues/12606
And when I try to add something like this I don't get an image at all and it shows up as a blank screen:
{{uri: profilePicture + '?' + new Date()}}
I am facing the same issue for when I navigate back to my messages list/ the picture only changes when I do a hard reload on the app.

I finally got solution of this issue:
componentDidUpdate(prevProps) {
if (this.props.photoUrl && prevProps.photoUrl !== this.props.photoUrl) {
this.setState({ profilePicture: this.props.photoUrl })
}
}
Basically, I just decided to store the new picture as a state instead.

Related

Prevent React Native remount of component on context value change

In a chat app I am building I want to deduct credits from a user's account, whenever the users sends a message and when a chat is initiated.
The user account is accessible in the app as a context and uses a snapshot listener on a firestore document to update whenever something changes in the user account document. (See code samples 1. and 2. at the bottom)
Now whenever anything in the userAccount object changes, all of the context providers children (NavigationStructure and all its subcomponents) are re-rendered as per React's documentation.
This, however causes huge problems on the chat screen that also uses this context:
The states that are defined there get re-initalized whenever something in the context changes. For example, I have a flag that indicates whether a modal is visible, default value is visible. When I go onto the chat screen, hide the modal, change a value manually in the firestore database (e.g. deduct credits) the chat screen is rerendered and the modal is visible again. (See code sample 3.)
I am very lost what the best way to solve this issue is, any ideas?
Solutions that I have thought about:
Move the credits counter to a different firestore document and deduct the credits once per day, but that feels like a weird workaround.
From Googling it seems to be possible to do something with useCallback or React.memo, but I am very unsure how.
Give up and become a wood worker...seems like running away from the problem though.
Maybe it has something to the nested react-navigation stack and tab navigators I'm using within NavigationStructure?
Desperate things I have tried:
Wrap all sub-components of NavigationStructrue in "React.memo(..)"
Make sure I don't define a component within another component's body.
Look at loads of stack overflow posts and try to fix things, none have worked.
Code Samples
App setup with context
function App() {
const userData = useUserData();
...
return (
<>
<UserContext.Provider value={{ ...userData }}>
<NavigationStructure />
</UserContext.Provider>
</>
}
useUserData Hook with firestore snapshot listener
export const useUserData = () => {
const [user, loading] = useAuthState(authFB);
const [userAccount, setUserAccount] = useState<userAccount | null>();
const [userLoading, setUserLoading] = useState(true);
useEffect(() => {
...
unsubscribe = onSnapshot(
doc(getFirestore(), firebaseCollection.userAccount, user.uid),
(doc) => {
if (doc.exists()) {
const data = doc.data() as userAccount & firebaseRequirement;
//STACK OVERFLOW COMMENT: data CONTAINES 'credits' FIELD
...
setUserAccount(data);
}
...
}
);
}, [user, loading]);
...
return {
user,
userAccount,
userLoading: userLoading || loading,
};
};
Code Sample: Chat screen with modal
export const Chat = ({ route, navigation }: ChatScreenProps): JSX.Element => {
const ctx = useContext(UserContext);
const userAccount = ctx.userAccount as userAccount;
...
//modal visibility
const [modalVisible, setModalVisible] = useState(true);
// STACK OVERFLOW COMMENT: ISSUE IS HERE.
// FOR SOME REASON THIS STATE GET'S RE-INITALIZED (AS true) WHENEVER
// SOMETHING IN THE userAccount CHANGES.
...
return (
<>
...
<Modal
title={t(tPrefix, 'tasklistModal.title')}
visible={ModalVisible}
onClose={() => setModalVisible(false)}
footer={
...
}
>
....
</Modal>
...
</>)
}
Any change to the context does indeed rerender all consumer components whether they use the changed property or not.
But it will not unmount and mount the component which is the reason why your local state gets initialized to the default value.
So the problem is not the in the rerenders (rarely the case) but rather <Chat ... /> or one of it's parent component unmounting due to changes in the context.
It is hard to tell from the partial code examples given but I would suggest looking at how you use loading. Something like loading ? <div>loading..</div> : <Chat ... /> would cause this behaviour.
As an example here is a codesandbox which illustrates the points made.
This is a characteristic of React Context - any change in value to a context results in a re-render in all of the context's consumers. This is briefly touched on in the Caveats section in their docs, but is expanded on in third-party blogs like this one: How to destroy your app's performance with React Context.
You've already tried the author's suggestion of memoization. Memoizing your components won't prevent re-initialization, since the values in the component do change when you change your user object.
The solution is to use a third-party state management solution that relies not on Context but on its own diffing. Redux, Zustand, and other popular libraries do their own comparison so that only affected components re-render.
Context is really only recommended for values that change infrequently and would require full-app re-renders anyway, like theme changes or language selection. Try replacing it with a "real" state management solution instead.

Firebase Persisting Auth with Stack Navigator

Attempting to let Firebase persist authentication within the app.js of React Native by doing the following:
There is a sign in page that envokes auth() sign in via Firebase, which works fine, and redirects to the home page via navigation.replace("Home"); however, once the app is closed and relaunched on the emulator, it redirects back to sign in.
This is seemingly what the App.js looks like, I assume that the AuthStateChanged would be prevalent as depicted below, however, user is not accessed in App.js as it is established in SignIn.js, when the Firebase credentials are sent, but I assume it would be similar to this layout?
const App = () => {
var initialRoute = null
React.useEffect(() => {
const unsubscribe = auth().onAuthStateChanged(user => {
if(user) {
initialRoute = "Home"
}
else {
initialRoute = "SignIn"
}
})
return unsubscribe
}, []);
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName={initialRoute}
>
The reason it needs to affect the initial route, and not just redirect anyone who reopens to the home page, is because after registration, there are extra steps included that adjust the database, such as location mapping and etc., therefore, the redirection has to occur within the initial route.
Thanks for your assistance.
This is not how you build a navigation flow with react-navigation. But that's no problem since theres a guide here on the official react-navigation side on how to do that: https://reactnavigation.org/docs/auth-flow/
Fixed by setting two different returns within App.js, one for if the user is authenticated with Firebase that sets the initialRoute to "Home", and one for else that sets it to "SignUp", seems to work fine.

Contentful and Next JS - Issues with content not showing

So at the moment, I have a site I'm creating with Next Js on the front-end with contentful used as my CMS.
I have quite a few pages that use the contentful function to fetch the data:
export async function getStaticProps() {
const client = createClient({
space: process.env.NEXT_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_CONTENTFUL_ACCESS_TOKEN,
});
const res = await client.getEntries({ content_type: "MYCONTENT" });
return {
props: {
MYCONTENT: res.items,
},
};
}
So imagine this code on about 30 pages. I then filter and map through the items that I need using this code:
{MYCONTNET
.filter((e) => e.fields.tag === "design tools")
.map((content) => {
return (
<ToolsCard
key={content.fields.title}
title={content.fields.title}
para={content.fields.tagline}
url={content.fields.url}
img={content.fields.img.fields.file.url}
/>
);
})}
It was going well until I recently came across an issue whereby not all the items get passed to the front end.
My only solution is to republish the content on contentful but when I do it seems to have an effect on the other pages. Even when I tried to console log I cant see the data being passed.
It shows me this image and here is a more detailed image
I'm not sure what the issue is, it was working fine, but when I started adding more content this issue came up.
If anyone can help, that would be great. Thanks in advance.
Answer:
So I didn't realise, I needed to increase the limit parameter. I had over 113 but the default was 100.

Deep Link doesn't open the app instead does a google search

I have been using Expo to develop a react-native app, The functionality I am currently trying to implement is to share a link with friends on platforms such as fb messenger/whatapp or even normal texts and when they click this link it will launch my app to a specific page using the parameters.
After extensive research online - I’ve come to a blocker, following expo’s documentation I defined a scheme for my app - when I press share everything works correctly a message is created and I’m able to share content but only as string.
I am using react-natives Share library to share to an app and I’m using Expo to provide me with the link.
Ideally my first goal is to get the app opening using the Expo Link before I explore further into adding more functionality to the link.
Share.share({
message: "Click Here to View More! " + Linking.makeUrl( ' ' , { postkey : "7a5d6w2x9d6s3a28d8d});
url: Linking.makeUrl( ' ' , { pkey : gkey });
title: 'This post is amazing',
})
.then((result) =>{
console.log(result)
if(result === 'dismissedAction'){
return
}
})
.catch((error) => console.log(error))
In the root of my app I have also defined the event handlers: App.js
_handleRedirect=(event)=> {
let {path,queryParams} = Linking.parse(event);
Alert.alert(`queryparams : ${event} path : ${path} `)
this.props.navigation.navigate("Post_Detail",{key:queryParams.postkey})
}
}
componentDidMount() {
let scheme = 'nxet'
Linking.getInitialURL()
.then(url => {
console.log("App.js getInitialURL Triggered")
// this.handleOpenURL({ url });
})
.catch(error => console.error(error));
Linking.addEventListener('url', ({url}) => this._handleRedirect(url));;
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
}
When I share the link to Whatsapp, Facebook Messenger or even just messages or notes it appears as myapplink://, I try to enter this into the browser and instead of asking me to open my app - it does a google search.
Please note I am attempting to have this working on Android Device and facing this issue
Is there something I am doing incorrectly?
Any help is much appreciated. Thanks.
You can not open external links, means other than http, https on Android. But you can on iOS. In order to be able to open your expo links, you need proper anchor tags on android. You can create html mails and give it a try, you will see it is gonna work on Android as well.

How to refresh data retrieved from SQLite when table is updated in React Native?

In my home screen I have buttons that lead to different screens where a test is given. When a user completes a test, the score is inserted into SQLite db file. When user clicks on "Home" to go back to home screen, I want to display the new score in the results section. Something like this:
Home Screen (App.js):
import Test1 from './Tests/Test1";
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
test1Score: 0,
}
//Retrieve last test score from SQLite table
//setState for test1Score
getTestScoreFromSQLiteDBAndSetState();
}
render() {
return(
<Button onPress={this.gotoTest1()} />
<Text>Last test result: {this.state.test1Score}</Text>
)}
}
Test1.js:
onTestComlete() {
//insert the test score to the SQLite table
insertTestScoreToSQLiteDB();
}
<Button onPress={navigation.navigate('HomeScreen')} title='Home' />
This is the basic setup, I'm not going to post the full codes as it gets too messy.
Right now I am able to insert the score to the db table. Problem is in the HomeScreen, the getTestScoreFromSQLiteDBAndSetState part only execute when the first time the app is loaded. So if I complete Test1, then press "Home" to navigate to HomeScreen, the score does not refresh.
Is there any technique in React Native to accomplish this?
EDIT:
For those who runs into similar issue, here's what I did based on the #2 solution of the accepted answer:
HomeScreen:
navigateToTest1 = () => {
this.props.navigation.navigate('test1', {
updateResult: this.updateTest1Results.bind(this)
});
}
updateTest1Results = () => {
//codes to retrieve result and setState goes here
}
Test1.js:
const { params } = this.props.navigation.state;
params.updateResult();
Whats happening is when you go back react-navigation doesn't load your screen again just show what was there before for better performance. You have a lot of possibilities some of them would look like this:
1-
Instead of using navigation.navigate() use navigation.push() that will create a new screen so it's going to update whatever there is to update.
2- you can call a function on test1.js from homeScreen before you navigate, just pass a function from homescreen to test as a param or as a prop (i don't know how it's constructed). On that function just have what you want to update, so the call to the sqlite table and the setState
3- use react-navigation events.
<NavigationEvents
onWillFocus={payload => console.log('will focus',payload)}
onDidFocus={payload => console.log('did focus',payload)}
onWillBlur={payload => console.log('will blur',payload)}
onDidBlur={payload => console.log('did blur',payload)}
/>
for more information about react-navigation events see this
for more information about navigation.push() see this

Resources