How to use mobx observer without the class in react - reactjs

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.

Related

How to use MSAL-React with class component?

Am using https://www.npmjs.com/package/#azure/msal-react in my react project. The library provides hooks to perform auth very easliy.
So in functional component, for getting access token,
if (account && inProgress === "none") {
instance.acquireTokenSilent({
...loginRequest,
account: account
}).then((response) => {
callMsGraph(response.accessToken).then(response => setGraphData(response));
});
}
needs to be called where const { instance, accounts, inProgress } = useMsal(); is used.
But I need to call api to fetch data from class components.
So, how to achieve the same functionality in class component
You cannot access the msal-react hooks inside your class components, so to do this you would either need to access the raw context, or wrap with a higher order component.
The following takes the examples from documentation, modified to your question about accessing instance, accounts, inProgress for subsequent API calls.
Consuming Raw Context
import React from "react";
import { MsalContext } from "#azure/msal-react";
class YourClassComponent extends React.Component {
static contextType = MsalContext;
render() {
const msalInstance = this.context.instance;
const msalAccounts = this.context.accounts;
const msalInProgress = this.context.inProgress;
// rest of your render code
}
}
}
Using Higher-Order-Component
import React from "react";
import { withMsal } from "#azure/msal-react";
class YourClassComponent extends React.Component {
render() {
const msalInstance = this.props.msalContext.instance;
const msalAccounts = this.props.msalContext.accounts;
const msalInProgress = this.props.msalContext.inProgress;
// rest of your render code
}
}
export default YourWrappedComponent = withMsal(YourClassComponent);
Either way works so personal preference really. I prefer accessing the raw context over wrapping for readability.

axios.get ERROR on using axios.create method by using baseUrl

I tried to create an application from an Random user API in react and I used axios library for HTTP Requests.I created a separate file for base API using axios.create and the file code goes as,
import axios from 'axios'
export default axios.create({
baseURL: `http://jsonplaceholder.typicode.com`,
});
Then I used this in another file to make an GET request and store the data in state on componentdidMount so ill can access the data in UI.
import React from "react";
import API from "../api";
export default class PersonList extends React.Component {
state = {
persons: []
};
componentDidMount() {
API
.get('/').then((data) => {
const persons = data.data;
this.setState({ persons });
console.log(this.state.persons);
});
}
render() {
const { persons } = this.state;
console.log('Stato',persons)
return (
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
);
}
}
But it doesn't work ,because the state is not filled up with Users data,so the .map() function is throwing an error.
You're encountering this error because of the URL you are using. In your example, you use https://jsonplaceholder.typicode.com as the endpoint in componentDidMount, but that isn't going to return any placeholder user data. I believe you meant to use https://jsonplaceholder.typicode.com/users instead.
I have a working example here: https://codesandbox.io/s/axios-instance-ki9g6. Notice how I only had to change / in componentDidMount to /users.

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 pass a global object around React Native components?

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.

Hide some React component children depending on user role

I am writing a single page application in React and Redux (with a Node.js backend).
I want to implement role-based access control and want to control the display of certain parts (or sub parts) of the app.
I'm going to get permissions list from Node.js, which is just an object with such structure:
{
users: 'read',
models: 'write',
...
dictionaries: 'none',
}
key is protected resource,
value is user permission for this resource (one of: none, read, write).
I'm storing it into redux state. Seems easy enough.
none permission will be checked by react-router routes onEnter/onChange hooks or redux-auth-wrapper. It seems easy too.
But what is the best way to apply read/write permissions to any component view (e.g. hide edit button in Models component if the user has { models: 'read' } permission).
I've found this solution and change it a bit for my task:
class Check extends React.Component {
static propTypes = {
resource: React.PropTypes.string.isRequired,
permission: React.PropTypes.oneOf(['read', 'write']),
userPermissions: React.PropTypes.object,
};
// Checks that user permission for resource is the same or greater than required
allowed() {
const permissions = ['read', 'write'];
const { permission, userPermissions } = this.props;
const userPermission = userPermissions[resource] || 'none';
return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
}
render() {
if (this.allowed()) return { this.props.children };
}
}
export default connect(userPermissionsSelector)(Check)
where userPermissionsSelector would be something like this: (store) => store.userPermisisons and returns user permission object.
Then wrap protected element with Check:
<Check resource="models" permission="write">
<Button>Edit model</Button>
</Check>
so if user doesn't have write permission for models the button will not be displayed.
Has anyone done anything like this? Is there more "elegant" solution than this?
thanks!
P.S. Of course user permission will also be checked on the server side too.
Well I think I understood what you want. I have done something that works for me and I like the way I have it but I understand that other viable solutions are out there.
What I wrote was an HOC react-router style.
Basically I have my PermissionsProvider where I init the users permissions. I have another withPermissions HOC that injects the permissions I provided earlier into my component.
So if I ever need to check permissions in that specific component I can access them easily.
// PermissionsProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
class PermissionsProvider extends React.Component {
static propTypes = {
permissions: PropTypes.array.isRequired,
};
static contextTypes = {
permissions: PropTypes.array,
};
static childContextTypes = {
permissions: PropTypes.array.isRequired,
};
getChildContext() {
// maybe you want to transform the permissions somehow
// maybe run them through some helpers. situational stuff
// otherwise just return the object with the props.permissions
// const permissions = doSomething(this.props.permissions);
// maybe add some validation methods
return { permissions: this.props.permissions };
}
render() {
return React.Children.only(this.props.children);
}
}
const withPermissions = Component => {
const C = (props, context) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} />
);
};
C.displayName = `withPermissions(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
C.contextTypes = {
permissions: PropTypes.array.isRequired
};
return hoistStatics(C, Component);
};
export { PermissionsProvider as default, withPermissions };
Ok I know this is a lot of code. But these are HOC (you can learn more here).
A higher-order component (HOC) is an advanced technique in React for
reusing component logic. HOCs are not part of the React API, per se.
They are a pattern that emerges from React’s compositional nature.
Concretely, a higher-order component is a function that takes a
component and returns a new component.
Basically I did this because I was inspired by what react-router did.
Whenever you want to know some routing stuff you can just add the decorator #withRouter and they inject props into your component. So why not do the same thing?
First you must setup the permissions with the provider
Then you use the withPermissions decorator on the components that check permissions
//App render
return (
<PermissionsProvider permissions={permissions}>
<SomeStuff />
</PermissionsProvider>
);
Somewhere inside SomeStuff you have a widely spread Toolbar that checks permissions?
#withPermissions
export default class Toolbar extends React.Component {
render() {
const { permissions } = this.props;
return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />;
}
}
If you can't use decorators you export the Toolbar like this
export default withPermissions(Toolbar);
Here is a codesandbox where I showed it in practice:
https://codesandbox.io/s/lxor8v3pkz
NOTES:
I really really simplified the permissions because that logic comes from your end and for demo purposes I simplified them.
I assumed the permissions were an array so that is why I check the PropTypes.array in the HOCs
It's a really long and complicated answer and I tried to articulate at my best ability. Please don't grill me for some mistakes here and there :)
The approach that suggested by #lokuzt is great.
And you can go even further in order to simplify your code.
First of all, every protected component has some requirement to be satisfied for render. You need to define a function that takes requirement to render and credentials of the current user as parameters. It must return true or false.
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
Further, we have to define a HOC (Higher-Order Component) using the new context API from ReactJS.
const { Provider, Consumer } = React.createContext();
function protect(requirement, WrappedComponent) {
return class extends Component {
render() {
return (
<Consumer>
{ credentials => isSatisfied(requirement, credentials)
? <WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>
: null
}
</Consumer>
);
}
};
}
Now you can decorate your components:
const requireAdmin = {...}; // <- this is your requirement
class AdminPanel extends Component {
...
}
export default protect(requireAdmin, AdminPanel);
or even third-party components:
import {Button} from 'react-bootstrap';
const AdminButton = protect(requireAdmin, Button);
Credentials have to be passed by ReactJS context API:
class MyApp extends Component {
render() {
const {credentials} = this.props;
<Provider value={credentials}>
...
<AdminPanel/>
<AdminButton>
Drop Database
</AdminButton>
...
</Provider>
}
}
Here is my extended implementation on github.
The demo is also available too.

Resources