How to pass a global object around React Native components? - reactjs

I am trying to set up a messaging module in my React Native app, which should get information from a service and render it in different components in different ways. Kind of like the inbox messages here: you receive a message, and in the header component you see the inbox with a red dot and the number of new messages. If you click it, you go to another component that renders the messages fully.
Now, I created two components to render the inbox in those two different ways. But when I try link them to the class that handles the notifications, I get errors inside the components classes saying that the object is undefined.
I have something like this:
Class that stores new messages
class Notifications {
constructor() {
this.notifications = [];
}
receiveNotification(notif) {
this.notifications.push(notif);
}
}
let notifications = new Notifications();
export { notifications };
Class that handles new messages from service
import framework from 'framework'; // this is the framework I use to communicate with the service
import Notifications from './Notifications.js';
export class PushNotificator extends Component {
constructor(props) {
super(props);
this.state = {
token: ""
}
}
componentDidMount() {
framework.requestPermissions()
.then(() => console.log('granted'))
.catch(() => console.log('notification permission rejected'));
framework.getToken().then(token => {
console.log("TOKEN (getToken)", token);
this.setState({token: token});
});
this.notificationListener = framework.on(frameworkEvent.Notification, notif => {
console.log("Notification", notif);
this.showLocalNotification(notif);
})
}
showLocalNotification(notif) {
Notifications.notifications.push(notif); // this fails because Notifications is undefined
framework.presentLocalNotification({
title: notif.title,
body: notif.body,
priority: "high",
click_action: notif.click_action,
show_in_foreground: true,
local: true
});
}
componentWillUnmount() {
this.notificationListener.remove();
}
render() {
return null;
}
}
Relevant part of the header inbox component
import Notifications from './Notifications.js' //assume the paths are correct
import {PushNotificator} from './PushNotificator.js'
export class Home extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
notifications: Notifications.notifications.find(notif => notif.seen).length
};
this.closeActivityIndicator = () => setTimeout(() => {
this.setState({ loading: false });
}, 2000);
}
...
render() {
<PushNotificator />
...
}
As soon as the constructor is called, the program fails because Notifications is undefined. But why is it undefined? Can I not use it this way?
Thanks.

There are two options, I see, how to fix your issue:
1. You have already instantiated your Notifications, so it is possible to export that instance by default without additional wrapping:
export default notifications;
and then just:
import notifications from './Notifications.js';
// ...
notifications.push(notif);
2. If you don't want to use the default, you may continue exporting your instance via
export { notifications };
and in that case you need to import it properly:
import { notifications } from './Notifications.js';
// ...
notifications.push(notif);
But in both cases you are working with instatiated notifications object, not with Notifications class.

Related

How to access naviagtion options from imported file in react-native

I'm passing data through different pages down to the last page in my app, its been working fine.
But the issue is the last page has 2 components so the typical </ChatActivity navigation="{this.props.navigation}" />, here's what I mean:
I have an App.js
content of App.js
import ChatScreen from './chat'
class ChatActivity extends Component {
static navigationOptions = {
...
}
render() {
return(
<ChatScreen navigation={this.props.navigation} />
)
}
}
I also have chat.js that contains the chat component. Chat.js itself, needs to import Fire from './fire.js'
so now, this.props.navigation was only passed to Chat.js...but I need to access it from fire.js as well.
I've read about import {useNavigation}, but from what i have tried it didn't work cause my fire.js doesn't even look like the example in the docs
this is my fire.js
class Fire extends React.Component{
constructor (props) {
super(props)
this.init()
this.checkAuth()
}
init = () => {
firebase.initializeApp({
})
};
checkAuth = () => {
firebase.auth().onAuthStateChanged(user => {
if (!user) {
firebase.auth().signInAnonymously();
}
})
}
send = messages => {
messages.forEach(item => {
const message = {
text: item.text,
timestamp: firebase.database.ServerValue.TIMESTAMP,
// image: item.image,
//video: item.video,
user: item.user
}
this.db.child(`NEED NAVIGATION PARAMS HERE`).push(message)
})
}
parse = message => {
const {user, text, timestamp} = message.val();
const {key, _id} = message
const createdAt = new Date(timestamp)
return {
_id,
createdAt,
text,
user
}
}
get = callback => {
this.db.child(`NEED NAVIGATION PARAMS HERE`).on('child_added', snapshot => callback(this.parse(snapshot)))
}
off() {
this.db.off()
}
get db() {
return firebase.database().ref(`NEED NAVIGATION PARAMS HERE`);
}
get uid(){
return(firebase.auth().currentUser || {}).uid
}
}
export default new Fire();
Since i couldn't access navigation params, I tried AsyncStorage, but thats probably not the best practice and it isn't working too well. Not sure if its the AsyncStorage or react-native-gifted-chat but when I load the chat page once, it shows the same messages for other chats till I restart the app which shouldn't be cause i'm fetching the data based on unique parameters.
You have just missed one step here...
Since you have passed the navigation as props by using the following approach:
<ChatScreen navigation={this.props.navigation} />
the chat screen gets to use navigation properties of ChatActivity.
For Fire.js to be able to use the navigation as well, that was provided to Chat.js by ChatActivity you will need to pass the navigation props received by Chat.js to Fire.js in the same way.
This is how your Chat.js should look like:
import Fire from './Fire'
class Chat extends Component {
static navigationOptions = {
...
}
render() {
return(
<Fire navigation={this.props.navigation} />
)
}
}
That should solve the issue. Cheers!

React - what are the steps to get data from api and render it?

I am building a site just like stackoverflow.com. I want my home page to display top questions. For that, I have sample questions on the backed. Now, I want to display only the question and tags from the questions array.
The code is in the image
I have made axios connection for that:
const instance = axios.create({
baseURL: "https://2w2knta9ag.execute-api.ap-south-1.amazonaws.com/dev", });
instance.defaults.headers.post["Content-Type"] = "application/json";
To connect it, I wrote the command: instance.get("/questions)
Now, how do I display only the question and tags??
EDIT:
On using the code given bellow, my js file now becomes:
import React from 'react';
import instance from '../../api';
class QuestionList extends React {
componentDidMount() {
instance
.get("/questions")
.then((res) => {
this.setState({ data: res.data });
});
}
render () {
const { data } = this.state;
return <div>
{
data && data.map(d => {
return <div>question: {d.question}, tags: {d.tags}</div>;
})
}
</div>
}
}
export default QuestionList;
But, this is just making my site in a loading state, and it gets hanged!!
If I understood correctly, you want to get an array only with the tags and the question. if so, you can use Array.prototype.map for this
const questions = result.map(({ question, tags }) => ({ question, tags }))
First you export the axios instance so that it can be used from other components.
Now you can send the api request in componentDidMount and update your component's state with the data.
And in render function, you just get the value from state and display.
If you are new to react, learn React Hooks and know that componentDidMount method is the best place to send api requests.
For Example:
import React from 'react';
import instance from '../../api';
class QuestionList extends React.Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
instance.get('/questions').then((res) => {
this.setState({ data: res.data });
});
}
render() {
const { data } = this.state;
return (
<div>
{data &&
data.map((d) => {
return (
<div>
question: {d.question}, tags: {d.tags}
</div>
);
})}
</div>
);
}
}
export default QuestionList;

React - Provide Global Singleton From Composition Root

To start off, I have been working with React now for three months and the application I am building is testable, performant, etc... Nothing wrong. My experience pre-React is from the Angular world and what is considered a best practice there is not normally in react and vice-a-versa... I don't think what I am doing is wrong for the application I am building also don't want to miss anything big.
To make a long story short, inside of my App.tsx (using TypeScript) file I am creating a new instance of a singleton service and exporting it as a named export. For example, my app component looks something like:
import * as React from 'react'
... axios import here
import { HttpService } from './core/http.service';
import { Spinner } from './shared/spinner';
const axiosInstance = Axios.create({config here});
const httpService = new HttpService(axiosInstance);
class App extends React.Component {
props: any;
state: any;
constructor(props: any) {
super(props);
}
render() {
return(<App Root Component Here...>)
}
}
export { httpService };
export default App;
Imagine a component somewhere in the app that needs to use my singleton service. For the purposes of my question, I will call the component Home (home/Home.tsx).
import * as React from 'react'
import { httpService } from '../App';
class Home extends React.Component {
props: HomeProps;
state: HomeState;
constructor(props: HomeProps) {
super(props);
this.state = {
isLoading: false,
myData: []
}
this.loadData = this.loadData.bind(this);
}
componentDidMount() {
this.loadData();
}
// Using httpService here.
loadData() {
this.setState({isLoading: true});
httpService.get('/api/somedataurl').then((response) => {
const { data } = response;
this.setState({myData: data});
}).then(() => {
this.setState({isLoading: false});
});
}
myDataList() {
return (<ul>...{map of this.state.myData}</ul>)
}
render() {
return this.state.isLoading ? (<Spinner>) : this.myDataList();
}
}
export default Home;
I decided to use this implementation because I know that I can always rely on the App component to be available and there are no plans for server-side rendering.
As a simple solution, is there anything seriously flawed with providing my singleton service as a named export and importing into other components as needed?

How to use mobx observer without the class in react

I am using axios and mobx in my react native project. One of the component (Home) on mount calls for a method (getBirds()) from a different file where all the API methods are organized.
store.js:
class BirdStore {
#observable birdList = [];
#action setBirdList = (birds) => {
this.birdList = birds;
};
}
Home.js:
#observer #inject("BirdStore")
export default class Home extends React.Component {
componentDidMount() {
api.getBirds()
...
}
}
api.js:
#inject('BirdStore') #observer
const api = {
getBirds() {
const url = website + '/api/birds/';
return axios.get(url)
.then((response) => {
this.props.BirdStore.setBirdList(response.data)
})
.catch((error) => {
console.log(error);
})
},
};
But this gives me an error:
Leading decorator must be attached to a class declaration
I can use the data from the server returned by the getBirds() into Home component, and then call the setBirdList() action, but I wanted to keep api related stuff separately. Is there anyway to use mobx without the class so that I can handle all the api related stuff other than the class component itself?
That error is pretty cryptic. They say "must be attached to..." In light of this I would say, yes. You need to attach the decorator to a class. More importantly though, you won't have access to this.props otherwise.

Meteor React Komposer: Execute Component Constructor AFTER ready subscription

I have a React Component Post:
export class Post extends React.Component {
constructor(props) {
this.state = this.props;
}
...
}
, which I compose with
import { composeWithTracker } from 'react-komposer';
import { composeWithTracker } from 'react-komposer';
import Post from '../components/post.jsx';
function composer(props, onData) {
const subscription = Meteor.subscribe('post', props.postId);
if (subscription.ready()) {
const data = {
ready: true,
posts: Posts.findOne(props.postId).fetch()
}
onData(null, data);
} else {
onData(null, {ready: false});
}
}
export default composeWithTracker(composer)(Post);
. As given in the Post Component I want to put some properties in the state of the component, but the constructor will be executed before I get the data from the composer!
How do I wait until the data is send and then put my props into the state?
Isn't this what the React Kompose should do? BTW I am using Version 1.~ to get composeWithTracker.
You could use componentWillReceiveProps to get new properties and set as component state. This function will run whenever there are new properties passed to component:
export class Post extends React.Component {
// ...
componentWillReceiveProps(nextProps) {
this.setState({
...nextProps,
});
}
// ...
}

Resources