React Native Firebase Ref() and Navigation complication - reactjs

I am using firebase along with React navigation for this app.
For a component of mine, I am able to retrieve data from my firebase realtime database to populate an array of components. This is done in my componentDidMount() function.
If, however, I navigate to another page, and then navigate back (the goBack() function is not manually called), no items are rendered. By using console logs I was able to figure out the following:
-componentDidMount() IS being called again when I revisit the page.
-I am able to enter into the function specified under:
Ref.on('value', (snap) => { .... });
as I am able to call console logs from within there.
-states are reset back to what the constructor assigns, as loading is set to true again.
It seems like I am simply unable to retrieve data from firebase after I call the ref() the first time. The following is the important aspects of my code.
export default class RestaurantPage extends Component<Props> {
constructor(props){
super(props);
this.state = {
showSearch:false,
loading:true,
index:0,
data: []
}
this.rootRef = firebaseApp.database().ref();
this.cards = [];
}
componentDidMount(){
var Ref = firebaseApp.database().ref("Restaurants");
Ref.on('value', (snap) => {
var restaurants = snap.val();
this.setState({data: Object.keys(restaurants)});
console.log(this.state.data);
var day = moment().format('dddd');
for(var i = 0; i < this.state.data.length; i++){
var newSnap = snap.child(this.state.data[i]);
var id = newSnap.val()
var name = newSnap.child('Name').val();
var cuisine = newSnap.child('Cuisine').val();
var price = newSnap.child('Price').val();
var address = newSnap.child('Address').val();
var closing = newSnap.child('Closing/' + day).val();
var description = newSnap.child('Description').val();
this.cards.push(
<RestaurantCard key = {i}
id = {id}
title = {name}
cost = {price}
cuisine = {cuisine}
tags = {756}
closing ={closing}
distance = {address}
description = {description}/>);
}
this.setState({loading:false});
});
}
renderCards = () => {
console.log("rendercards called");
return(
{this.cards}
);
}
Does anyone have insight on this?
Side Note: No problems occur if I simply am going to another page and then use the goBack() function, as I believe all states are retained anyway in that case.
Additionally: I am using this.props.navigation.navigate(), rather than push().
ALSO: Something that might be quite significant: The issue occurs when I navigate from one page (Feed) to the page I am having issues with (RestaurantPage). Then, I navigate back to (Feed) using my navBar, and then navigate back to (RestaurantPage), at which point I see that the firebase ref() is not working.
Stack Navigator:
export default createStackNavigator(
{
RestaurantProfile: RestaurantProfile,
RestaurantPage: RestaurantPage,
SearchPage: SearchPage,
SearchResults: SearchResults,
Feed: Feed,
OtherUserProfile: OtherUserProfile,
OtherUserTags: OtherUserTags,
PersonalTags: PersonalTags
},
{
headerMode:'none',
initialRouteName: 'Feed',
transitionConfig: () => ({ screenInterpolator: () => null })
},
);

The problem was that I did not call off() on the firebase ref after calling on()!
The solution is to call Ref.off() in componentDidUnmount or simply use a Ref.once() function rather than Ref.on()

Related

How to best work with collection updates in ReactJS

I am having an issue with keeping track of collection state updates in ReactJS
I have model called Ticket
export default class Ticket {
numbers?: number[];
constructor(numbers?: number[]) {
if (numbers == undefined) {
numbers = []
}
this.numbers = numbers
}
}
Now in my Home Component I have the following
const [tickets, setTickets] = useState<Ticket[]>([]);
const [activeTicket, setActiveTicket] = useState<Ticket>(new Ticket());
Now, I have a function as below
const addMoreTickets = () => {
let newTicket = new Ticket();
setTickets([...tickets, newTicket]);
setActiveTicket(tickets[tickets.length-1]); //This doesn't work, it still holds the old ticket instead of the newly added ticket
}
Is there a work around to this?

Extend background video and navigation bar across all pages in Gatsby

I want to have my background video and navigation bar spread across all pages I make but I also want them to be behind all of the other components rendered. I understand that I need to wrap the root element inside of gatsby-browser.js but the problem I have been faced with is that the background covers all of the elements. Is there any way to fix this?
Current gatsby-browser.js - Currently covers all components with background.
import React from 'react';
import BackGround from "../../src/components/bg"
export const wrapPageElement = ({ element }) => {
return <BackGround>{element}</BackGround>;
};
"use strict";
var _interopRequireDefault = require("#babel/runtime/helpers/interopRequireDefault");
var _interopRequireWildcard2 = _interopRequireDefault(require("#babel/runtime/helpers/interopRequireWildcard"));
/* global __PATH_PREFIX__ */
// Taken from https://github.com/netlify/netlify-identity-widget
var routes = /(confirmation|invite|recovery|email_change)_token=([^&]+)/;
var errorRoute = /error=access_denied&error_description=403/;
var accessTokenRoute = /access_token=/;
export const onInitialClientRender = function (_, _ref) {
var _ref$enableIdentityWi = _ref.enableIdentityWidget,
enableIdentityWidget = _ref$enableIdentityWi === void 0 ? true : _ref$enableIdentityWi,
_ref$publicPath = _ref.publicPath,
publicPath = _ref$publicPath === void 0 ? "admin" : _ref$publicPath;
var hash = (document.location.hash || "").replace(/^#\/?/, "");
if (enableIdentityWidget && (routes.test(hash) || errorRoute.test(hash) || accessTokenRoute.test(hash))) {
Promise.resolve().then(function () {
return (0, _interopRequireWildcard2.default)(require("netlify-identity-widget"));
}).then(function (_ref2) {
var netlifyIdentityWidget = _ref2.default;
netlifyIdentityWidget.on("init", function (user) {
if (!user) {
netlifyIdentityWidget.on("login", function () {
document.location.href = __PATH_PREFIX__ + "/" + publicPath + "/";
});
}
});
netlifyIdentityWidget.init();
});
}
};
If any additional information is needed please let me know.
I don't think that wrapPageElement nor wrapRootElement APIs fits your requirements since they will wrap your entire page in a provided component, and that's what you are trying to avoid. Of course, they will prevent your component to be unmounted, however, this will be automatically handled by #reach/routing with the following approach.
What you are trying to achieve it's a simple shared component across all pages that extends from your <Layout>:
const Layout = ({ children }) => {
return <section>
<BackGround>
<YourNavigationComponent />
</BackGround>
<main>{children}</main>
</section>;
};
Something like this will wrap your <YourNavigationComponent /> with your background component across all site, avoiding the wrapping of the whole elements.

Is there any way to keep React from re-rendering for each item added to a MobX observed array (using Hooks)

I'm still relatively new to React so this may not be an issue or one that's solvable. I put together a very contrived (don't judge :P) experiment using Mobx and React (hooks) here.
What I am wondering is if there is any way to avoid the need to re-render the parent (App) for each item that is added to an array marked observable despite the items being added via concat.
For example:
export class MyModel {
constructor() {
this.messages = [];
}
addMemo(msg: string, done = false) {
const memos: Memo[] = [];
for (let i = 0; i < 10; i++) {
const memo = new Memo(`${msg}-${i}`);
memo.letsDance();
memos.push(memo);
}
this.messages = this.messages.concat(memos);
return memos.shift();
}
}
decorate(MyModel, {
messages: observable,
addMemo: action
});
Causes React to render the consumer 10 times:
const App = () => {
// ...
const myModel = useObservable(new MyModel());
// ...
return useObserver(() => (
<div>
{/* ... */}
</div>
));
};
https://codesandbox.io/s/white-tdd-jh42r
Your async letsDance() makes react rerender 10 times (1 for each item which 'dances') check an example here which comments out that part: https://codesandbox.io/s/interesting-mountain-wdtdg

react how to do async in loop and then return react element

getLinkName(segments) {
let arr = segments.split('/') // cars/honda
let el = []
let baseUrl = arr[0]
arr.map(async (item, index) => {
let name = await getCategoryNameFromSlug(baseUrl)
baseUrl = baseUrl + '/' + arr[index]
let i = (
<Link to={'/' + baseUrl} key={index}>
{name}
</Link>
)
el.push(i)
})
console.log('el', el)
return el
}
I got this function, it loop through an array. at each index. it fetches data, and then return the data and a react element.
Problem, the result I expect is an array of react element, but I got a bunch of promises
This is expected because this is how asynchronous js works, functions run to complete, so when your getLinkName function returns, your el arr will have a bunch of promises.
Network requests in React should be done in eventListeners or on lifecycle components like componentDidMount or (useEffect if you are using hooks)
Assuming you are using a Class Component your code should look something like this
// make sure babel supports this or set the initial state in the constructor
state = { categoryNames: null}
componentDidMount() {
let baseUrl = segments.split('/')[0]
Promise.all(segments.split('/').map((item,index) => {
let name = await getCategoryNameFromSlug(baseUrl)
baseUrl = baseUrl + '/' + arr[index]
return name;
})).then(categoryNames => this.setState({categoryNames}))
}
render(){
// here use categoryNames like you wanted to do in your question
}
A common pattern in React for cases like yours is to fetch your async data first and then save it as state, which in turn triggers a rerender of your component.
React elements are the basic building blocks used when React looks for changes and created each time the render phase is triggered (e.g. render method in case of class components). React can invoke render potentially many times as part of its internal diffing algorithm implementation, so you better not rely on async operations in render phase - it expects a synchronous return value.
You can implement it like this for a class component:
class ExampleComp extends React.Component {
state = { nameBaseUrlTuples: [] };
componentDidMount() {
this.getLinkName("cars/honda");
}
render() {
return this.state.nameBaseUrlTuples.map((nameBaseUrlTuple, index) => (
<Link to={"/" + nameBaseUrlTuple[1]} key={index}>
{nameBaseUrlTuple[0]}
</Link>
));
}
async getLinkName(segments) {
const arr = segments.split("/"); // cars/honda
let baseUrl = arr[0];
const nameBaseUrlTuples = await Promise.all(
// not sure, if you also want to iterate over first index
// seems as first is your root path segment containing sub categories
arr.map(async (item, index) => {
let name = await getCategoryNameFromSlug(baseUrl);
// You could remove this side effect by
// using map callback's third "array" argument and
// compose your URL with array.slice(0, index+1).join("/") or similar
baseUrl = baseUrl + "/" + item;
return [name, baseUrl];
})
);
this.setState({ nameBaseUrlTuples });
}
}

Central State Management without Redux or Mobx?

Recently I contemplated the idea of having central state management in my React apps without using Redux or Mobx, instead opting to create something similar to the application class in Android. In any event, I implemented something similar to this:
Create a store folder and a file called store.js in it whose contents are:
// State
let state = {
users: {},
value: 0
};
// Stores references to component functions
let triggers = [];
// Subscription Methods
export const subscribe = trigger => {
triggers.push(trigger);
trigger();
}
export const unsubscribe = trigger => {
let pos = -1;
for (let i in triggers) {
if (triggers[i]===trigger) {
pos = i;
break;
}
}
if (pos!==-1) {
triggers.splice(pos, 1);
}
}
// Trigger Methods
let triggerAll = () => {
for (let trigger of triggers) {
trigger();
}
}
// State Interaction Methods
export const setUser = (name, description) => {
state.users[name] = description;
triggerAll();
}
export const removeUser = name => {
if (name in state.users) {
delete state.users[name];
}
triggerAll();
}
export const getAllUsers = () => {
return state.users;
}
export const getUser = name => {
if (!(name in state.users)) {
return null;
}
return state.users[name];
}
export const getValue = () => {
return state.value;
}
export const setValue = value => {
state.value = value;
triggerAll();
}
And connecting to this store in the following manner:
// External Modules
import React, { Component } from 'react';
import {Box, Text, Heading} from 'grommet';
// Store
import {subscribe, unsubscribe, getAllUsers} from '../../store/store';
class Users extends Component {
state = {
users: []
}
componentDidMount() {
subscribe(this.trigger); // push the trigger when the component mounts
}
componentWillUnmount() {
unsubscribe(this.trigger); // remove the trigger when the component is about to unmount
}
// function that gets triggered whenever state in store.js changes
trigger = () => {
let Users = getAllUsers();
let users = [];
for (let user in Users) {
users.push({
name: user,
description: Users[user]
});
}
this.setState({users});
}
render() {
return <Box align="center">
{this.state.users.map(user => {
return <Box
style={{cursor: "pointer"}}
width="500px"
background={{color: "#EBE7F3"}}
key={user.name}
round
pad="medium"
margin="medium"
onClick={() => this.props.history.push("/users/" + user.name)}>
<Heading margin={{top: "xsmall", left: "xsmall", right: "xsmall", bottom: "xsmall"}}>{user.name}</Heading>
<Text>{user.description}</Text>
</Box>
})}
</Box>;
}
}
export default Users;
Note. I've tested this pattern on a website and it works. Check it out here. And I apologize I am trying to keep the question concise for stackoverflow, I've provided a more detailed explanation of the pattern's implementation here
But anyway, my main question, what could be the possible reasons not to use this, since I assume if it was this simple, people wouldn't be using Redux or Mobx. Thanks in advance.
That's what Redux and MobX basically do, you are wrong in thinking that at their core concept they are much different. Their size and complexity came as a result of their effort to neutralize bugs and adapt to a vast variety of application cases. That's it. Although they might be approaching the task from different angles, but the central concept is just that. Maybe you should familiarize your self with what they actually do underneath.
Btw, you do not need to store redundant state in your component, if all you need is to trigger the update. You can just call forceUpdate() directly:
// function that gets triggered whenever state in store.js changes
trigger = () => {
this.forceUpdate();
}
That's similar to what Redux and MobX bindings for react do under the hood.

Resources