React - Mobx - Fetch - DRY Best Practices - javascript

I am trying to clean up some code to remove redundancy and have some questions around best practices.
To keep it simple lets say I have 3 mobx stores PeopleStore, PlacesStore, and AuthStore.
All of these stores connect to a 3rd party api using fetch.
The AuthStore provides login functionality and has a property: #observable apiToken
There is duplicate logic in the other stores, setting the api url, the token, etc...
I tried creating the following file:
// ./api.js
const url = 'https://example.com'
const options = {
headers: {
Authorization: ??? (how do I access AuthStore.apiToken)
}
}
export const api = (endpoint) => {
fetch(url + endpoint, options)
}
Then in PeopleStore
import api from './api'
class PeopleStore {
getPeople() {
api('/people');
}
}
(This is just an example of the concept and not the actual code)
My quesions are:
Is this a good approach or are there better ways to go about this?
How do I access the apiToken inside api.js?
Am I correct in assuming that api is just a function and not a react component?
If so, can I still inject the AuthStore into the api()? (I believe I ran into issues with this)
My other thought would be to wrap all of the Stores in an HOC which provides this functionality, but again (I'm assuming) the Stores are not actual react components, does this cause any issues?

Another approach would be to just set your authorization header globally at the top level of your app. This would require you to install axios or something similar
For example, in your root index.js:
Get your api key, via process.env.apiToken or however you are getting it
Set the header:
axios.defaults.headers.common['Authorization'] = apiToken;
You can also set the base URL to help with the paths:
axios.defaults.baseURL = 'https://api.example.com';
This way you do not need to worry about the token, it will just be taken care of.

Related

Gatsby: Why Do I (Or Do I Even?) Need to Use exports.onCreateNode to Create Pages?

All of the examples in the Gatsby documentation seem to assume you want to define an exports.onCreateNode first to parse your data, and then define a separate exports.createPages to do your routing.
However, that seems needlessly complex. A much simpler option would seem to be to just use the graphql option provided to createPages:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const { data } = await graphql(query);
// use data to build page
data.someArray.forEach(datum =>
createPage({ path: `/some/path/${datum.foo}`, component: SomeComponent }));
However, when I do that, I get an error:
TypeError: filepath.includes is not a function
I assume this is because my path prop for createPage is a string and it should be "slug". However, all the approaches for generating slugs seem to involve doing that whole exports.onCreateNode thing.
Am I missing a simple solution for generating valid slugs from a path string? Or am I misunderstanding Gatsby, and for some reason I need to use onCreateNode every time I use createPage?
It turns out the error I mentioned:
TypeError: filepath.includes is not a function
Wasn't coming from the path prop at all: it was coming from the (terribly named) component prop ... which does not take a component function/class! Instead it takes a path to a component (why they don't call the prop componentPath is just beyond me!)
But all that aside, once I fixed "component" to (sigh) no longer be a component, I was able to get past that error and create pages ... and it turns out the whole onCreateNode thing is unnecessary.
Why Do I Need to Use exports.onCreateNode to Create Pages?
You do not.
Gatsby heavily uses GraphQL behind the scenes. The Gatsby documentation is about teaching users that many of the features in Gatsby are often only available via GraphQL.
You can create pages without GraphQL as you do in answer with data.someArray.forEach ... but that is not the intended way. By skipping createNodeField you will not be able to query for these fields within your page queries. If you don't need these fields via GraphQL then your solution is perfect.

Access Vuex store from javascript module

I have an out javascript module, and I want to get the auth key from the vuex store
This is my code, that's seems that it work
import store from '../../../store';
class ExternalApi {
constructor() {
const that = this;
that.configuration = { apiKey: '', basePath: '/api/v2' };
that.authorizationHeader = function authorizationHeader() {
const cstore = store;
if (cstore && cstore.state && cstore.state.accessToken) {
that.configuration.apiKey = cstore.state.accessToken;
}
const localVarHeaderParams = { };
localVarHeaderParams.authorization = `Bearer ${that.configuration.apiKey}`;
return localVarHeaderParams;
};
this.list = function list(params) {
const localVarPath = `${that.configuration.basePath}/list`;
const localVarHeaderParams = that.authorizationHeader();
return axios....
};
}
I want to know 2 things:
Does it the way to connect to store from javascript or there is a better way for doing it
Does this code can make me some securities problems.
Answering your first question, when you want to use the store from outside Vue just use its API as you are doing though you don't really need to check for the store or the state to be initialized.
Regarding security, storing the authorization token in JS will be vulnerable to XSS attacks, the best way to avoid (or mitigate) them is implementing a robust Content Security Policy (CSP) and using a low (5-10min) time to live for your tokens.
On a side note, you don't need to rename store to cstore and if you use class methods instead of adding function properties in the constructor you wont need to juggle with this. Also note that storing the token in memory will not survive a page refresh so you may want to store it in sessionStorage.

How to fetch the API details from a component folder using Next.js?

I have a reusable component that I have created on the components folder where I have all the details from the user that logs in to the system which is a header section.
I am trying to use getInitialProps using fetch with isomorphic-unfetch.
static async getInitialProps(ctx) {
const UserdetailsRespone = await fetch("my API url");
const UserdetailsJson = await UserdetailsRespone.json();
return { UserDetails: UserdetailsJson.root[0] };
}
In the render method when I log this.props.UserDetails I get undefined.
It is the same API fetch as the one I am doing in the pages folder where I am able to fetch an API response. But I am not able to fetch the response in the components folder.
Can someone help me to solve it?
Thanks in Advance.
getInitialProps runs server side and client side. So you will have to be careful how you use it. I usually use typeof Window === 'undefined' to check whether getInitialProps is being called server side or not.
If you have a child component that you need to make a call everytime it is mounted, why not stick with componentDidMount?
async componentDidMount() {
const userDetailsResponse = await fetch("my API url");
const userDetailsJson = await userDetailsResponse.json();
this.setState({userDetails: userDetailsJson})
}
You can access the properties from state instead of props then.
If you're using getInitialProps in a child component, it won't work. It only works in the default export of every page.
From official docs
Edit:
As mentioned by Uzair Ashraf, using fetch is the way to go for fetching data in components. You can take a look at swr too.

Vue.JS - Both 'history' and 'abstract' router?

I am creating a VueJS app in which the user fills out a 5-step form.
These steps are routed to /step-1 through /step-5 in the Vue Router. However, I would like the site to return to the index page (/) when refreshing the page.
I could use abstract mode for this – but the result page is generated from the following url: /result/:userid in which I need the state to be history in order to be able to get the userid from the URL (and then do a post request to the server).
I also want this URL to be accessible even after finishing the form, so abstract here is not an option unfortunately.
So – is it possible to use both modes? Refresh the page to index.html when refreshing the form-pages, but then use history mode to render the result?
You cannot do this. It is either history or abstract but not both. Having said this, there are a couple of things you can do.
Approach 1: Use history mode with steps as query params
So instead of having routes like /step-1 or /step-2, use then as part of query params. So you will have routes like:
Index route: example.com/?step=1, example.com/?step=2
Result route: example.com/result/:userId
Approach 2: Use abstract mode with higher order component
Here, you will have a router with abstract but it will only serve as a state router and won't help with any browser URL manipulation.
Build a higher order component like AppComponent where you will have your own regular expressions to determine the route. It would look like:
// Should match route /result/:userId
const IS_RESULT = new RegExp('/result/(\\d+)$');
// User RegExp groups
const IS_STEP = new RegExp('/step-(\\d+)$');
export default class AppComponent extends Vue {
// Use Vue.js created hook
created() {
const path = window.location.pathname;
// Top level routing of the application
if (IS_STEP.test(path)) {
// Do something. You are in step-1/step-2/etc.
} if (IS_RESULT.test(path)) {
// Do something. You are in /result/:userId
// Get userId
const groups = IS_RESULT.exec(path);
const userId = groups[1];
} else {
// Goto Error page
throw new Error('Unknown route');
}
}
}
Approach 3: Use Multi-page SPA
Here, you will create two single page application. The first app will have routes /step-1, /step-2, etc. You will use abstract mode for this. The second application will have /result/:userId route with history mode.
In this architecture, when a user is on step-5, instead of pushing a new state to the router, you will use HTML5 history API to change the router and then cause a forced page refresh. Also, there are other ways you achieve this.
You can simply have a redirect in native javascript where you call it window.location.href('yourHomePage.com') and it will do a full refresh.

React and Mobx - Load API data on load?

I have to validate that the user is logged in, using some token, that currently in the next example will already be set, for testing.
I have two options that I can think of.
Option 1
Do it on store's constructor:
export class MyStore {
#observable token = "sometoken";
#observable authenticated = false;
constructor() {
this.checkAuth();
}
#action
checkAuth() {
fetch("http://localhost:3001/validate/" + this.token)
.then(res => res.json())
.then(data => {
this.authenticated = data.validated;
});
// catch etc
}
}
Option 2:
Do it in my component's that uses the data, componentDidMount method.
Both of the ways work, but what is really the best practice to handle such state?
I would definitely go for the first option. If you don't always need the authentication - for example some parts are public - then just don't call this.checkAuth() in the store constructor. If all parts need authentication, then it looks good like this.
Option 2 should be avoided because that would make unnecessary roundtrips to the server to re-validate a token which was already validated. And in general MobX gives great tools to minimize the use of lifecycle methods and write a cleaner code.

Categories

Resources