I am working on a ReactJS application, which uses many API calls to get data from the server. These API calls are organized into multiple classes and all of them use another class which issues a HTTP request using Axios.
Example markup and flow: (this is hand typed to keep it simple, please ignore any minor mistake or omissions)
class MyComponent extends Component{
componentDidMount(){
this.props.getData(//parameters//); //getData is an action bound via react-redux connect.
}
}
export const getData = (//parameters//) => (
dispatch: any
) => {
return new Promise((resolve, reject) => {
//Some business logic
let axiosService = new axiosService();
axiosService .get(//parameters//).then((data: any) => {
dispatch({
type: "RECEIVE_DATA",
payload: data
});
resolve();
});
});
};
export default class AxiosService {
config: AxiosRequestConfig;
url : string;
constructor() {
this.url = "http://localhost:8080/api/";
this.config = {
headers: {
//I want to inject Authorization token here
},
responseType: "json"
};
}
get = () => {
return new Promise((resolve, reject) => {
resolve(axios.get(this.url, this.config));
});
};
}
Now I want to inject auth token as Authorization header when AxiosService makes an API call. This token is available in redux store. How can I access redux store in my service?
I can pass the token as a parameter to AxiosService get call, but that is messy. Based on my reading custom thunk middleware might help me here, but not sure how to update my service call. I have many actions, that need to use AxiosService.
There are many ways. For e.g. you can call Axios from the reducer or from the action builder. The reducer have the state, and redux-thunk is a popular library for that, that gives you the store to the Action Builder.
You can also use redux-saga, to listen to actions and perform http requests in response. redux-saga also gives you the store.
If you don't want all the libraries you can do a workaround. Where you created the store, save it to the global window:
window.store=redux.createStore(...)
Then, your http library can access the store:
window.store.getState()
It's not recommended way, but it's working.
Related
When testing the frontend of my Vue application, I sometimes want to skip the communication with the API backend.
I came up with the following idea:
add state in Vuex store: skipServerCommunication.
when skipServerCommunication = false: send the axios request as expected
when skipServerCommunication = true: return mocked data
The easy way would be to add an check before every call of axios.post(...) or axios.get(...), but I don't want to have to add code in every api function I wrote.
So I thought about using the interceptors of axios, but I think it's not possible to stop a request, and just return mocked data, right?
I then thought about wrapping axios, and dynamically returning the real axios instance or a mocked one, which also implements the .post(), .get() and all the other axios functions based on the Store state. I thought about something like this (I'm working with TypeScript):
import store from '#/vuex-store'
const axios = Axios.create( // real store config );
const axiosMock = {
get(path: string) {
if (path == '/test') {
return Promise.resolve( // mocked data );
}
return Promise.resolve(true)
}
}
class AxiosWrapper {
get instance() {
if (store.state.skipServerCommunication) {
return axiosMock;
}
return axios
}
}
export default new AxiosWrapper();
But this solution has some problems:
I need to replace all axios calls with axiosWrapper.instance.get(...). Can I somehow avoid this and mock axios in a way that I can still work with axios.get(...)?
VSCode isn't able to provide autocompletion anymore because the returned instance is either of type AxiosStatic or "MyMockType". So I thought about implementing the AxiosStatic interface, but I struggle to do that correctly because of the two anonymous functions in the AxiosInstance interface. Is there an alternative way to overcome this problem?
Use axios-mock-adapter instead. You can mock axios calls as needed per test, using advanced matching patterns.
Example usage:
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
describe('UserList', () => {
it('gets users on mount', async () => {
const mock = new MockAdapter(axios)
mock.onGet('/users').reply(200, {
users: [{ id: 1, name: 'John Smith' }],
})
const wrapper = shallowMount(UserList)
await wrapper.find('.getUsersBtn').trigger('click')
expect(wrapper.vm.users[0].id).toBe(1)
})
})
I would appreciate feedback on my approach:
BACKGROUND
The application needs to have ReactJS components that will fetch data from server.
The components will receive frequent updates from the server.
There will be more than one component using the same data model.
There will be different types of data model.
It has to be easy to add new components just changing the data-model name.
The application will probably be configurable.
APPROACH
Core:
I created a class called DataFetch that will fetch data from the server, which optionally can receive the Redux state options, SignalR options and callbacks using the methods onReceive() and onError().
When not configurated with Redux state options and SignalR options: the dataFetch will have the same axios functionality.
When configurated with Redux state options: the DataFetch class will dynamically create a redux state according the state options and keep it updated using the result from request. The Redux state options are: stateName and reducer. The actionTypes will be dynamically created using the stateName and for on success/error response of the request.
When configurated with Signal options: the dataFetch receive real-time updates from server. if configurated: It will update the redux state and will call the callbacks (onSuccess, onError).
Adicional information:
I'm using axios to fetch data from server.
This state has been dynamically created using the official redux approach: ReducerManager.
I'm using Core SignalR to add the real-time functionality to the application.
I created a hook useSignalR to easily use SignalR with React components.
All this is separated into their respective classes, functions to easily be tested.
How to use?
I created a hook called useDataFetch to easily use this functionality with `stateless React components.
The result from the hook will be a object like this: { data, error, isLoading }.
The syntax to use the hook is like this:
import React from 'react';
import { useSignalRContext } from './core/signalR';
import {
useDataFetch,
SignalRHubOptions,
ReduxStoreOptions
} from './core/dataFetch';
const ExampleComponent = props => {
const { dataModelId } = props;
const { someHubConnection } = useSignalRContext();
const signalRHubOptions = useMemo(
() =>
new SignalRHubOptions({
hubConnection: someHubConnection,
// Multiple components can be created for the same hubConnection.
// Therefore, we need to add a dataChangeMethodName by the component instance,
// otherwise, all instances of that component will receive the same message.
dataChangeMethodName: `DataChanged-${dataModelId}`
}),
[someHubConnection, dataModelId]
);
const reduxStoreOptions = useMemo(
() =>
new ReduxStoreOptions({
// Use diferrent stateName for different conponents.
stateName: `${dataModelId}_state`,
initialState: []
}),
[dataModelId]
);
// Receive data when:
// - perform the request with success;
// - signalR send a message;
// - redux state receive a update with the actionType SUCCESS;
// Receive error when:
// - perform the request with error;
// - redux state receive a update with the actionType FAILURE.
const { data, error, isLoading } = useDataFetch({
url: '/endpoint/${dataModelId}',
data: {
param1: 'value1'
},
signalRHubOptions: signalRHubOptions,
reduxStoreOptions: reduxStoreOptions
});
return {
/* render the component with the data or error or isLoading state */
};
};
export default ExampleComponent;
Currently i am working on storing data for a job opening application.
For the backend i use Laravel and for the frontend i use Nuxt.js
I am new to Nuxt, so i'm kinda stuck on the following issue.
I have a page for creating a new job opening called new-job.vue. I also created a store called jobs.js for handling the states.
On new-job.vue i have a form with data that has to be rendered in a list before the form starts.Like a list of all countries etc.. in order for me to select them in the form.
At this point i'm using asyncData within the export default on new-job.vue:
<script>
export default {
asyncData(context) {
return context.app.$axios
.$get('jobs/create')
.then(data => {
//context.store.dispatch('jobs/getTypes', data.types)
context.store.dispatch('jobs/getPlatforms', data.platforms)
context.store.dispatch('jobs/getCountries', data.countries)data.branches)
// return {
// loadedPost: { ...data, id: context.params.postId }
// }composer required barr
})
.catch(e => context.error(e))
},
computed: {
types () { return this.$store.state.jobs.types },
platforms () { return this.$store.state.jobs.platforms },
countries () { return this.$store.state.jobs.countries },
},
}
The asyncData method works and the lists of types, platforms and countries are getting filled with data from the database and the state from the Vuex store gets updated. .Only the data is being rendered on the client side.
I prefer this data to be loaded server side, so i was looking at nuxtServerInit. Only can someone explain to me how i can make this happen.
I placed an async call inside the export default of new-job.vue:
async nuxtServerInit ({ commit, state }, { app }) {
let res = await axios.get(`jobs/create`)
console.log(res)
commit('setPlatforms', res.data.platforms)
commit('setTypes', res.data.types)
commit('setCountries', res.data.countries)
},
I created the commits in the mutations of the jobs.store, but the states are not being updated.
What am i doing wrong and/or what am i missing?
Or maybe another question, is nuxtServerInit the way to go? Or is loading these lists of data on the clientside not a big deal?
UPDATE:
I use modules mode for the store, so i created a store called jobs.js. Inside this file i tried to call nuxtServerInit as well, but i didn't get any response.
nuxtServerInit(vuexContext, context) {
return context.app.$axios
.$get('jobs/create')
.then(data => {
console.log(data)
})
.catch(e => context.error(e))
},
From nuxtServerInit API reference in Nuxt.js documentation:
If the action nuxtServerInit is defined in the store, Nuxt.js will call it with the context (only from the server-side).
In other words, it is a reserved store action available only in store/index.js file and if defined will be called on server-side before rendering requested routes.
Only asyncData and fetch methods are available within pages.
Navigation guards are perfect for redirecting unauthorized users to a login page, but what does one do to redirect unauthorized vuex actions to a login page?
I can do this easily enough in the vue method where I'm calling the action like so:
if (!this.isLoggedIn) {
this.$router.push({ name: 'login', query: { comeBack: true } })
$(`.modal`).modal('hide')
return
}
But then I'm inserting these 5 lines of code for every component method that requires authorization.
All the solutions I can think of sound hacky, so I'm wondering what the vuex way is:
In order to reject it at the vuex action level, I have to pass up the $router instance, and I'm still reusing the 5 lines for each action that requires auth.
I can handle this in a utility file, but then I'm handling $router instance in that file.
I can use a global vue mixin and call it (a) before making a call and then again (b) when getting a 401 back from the server.
All those seem odd. What vuex way am I missing here?
This sounds like a job for middleware. Unfortunately, Vuex doesn't have an official way to do middleware.
There is a subscribeAction() but that runs after the commit, so does not allow mods to the action. There is also a proposal Middleware processing between actions and mutation.
As I see it, we want middleware to be able to do two generic things
cancel the action
allow alternative actions to be called
The second is difficult to do without patching store.dispatch() or messing with the private property _actions after store has been created.
However, to guard an action as you describe, we only need to be able to cancel it.
Here is a poor-man's middleware for the modules pattern for Vuex store which I prefer.
store construction from modules
export const store = new Vuex.Store({
modules: {
config,
pages: applyMiddleware(pages),
measures,
user,
loadStatus,
search
}
})
applyMiddleware
const applyMiddleware = function(module) {
if(module.middlewares) {
Object.values(module.middlewares).forEach(middlewareFn => {
Object.keys(module.actions).forEach(actionName => {
const actionFn = module.actions[actionName]
module.actions[actionName] = addMiddleware(actionName, actionFn, middlewareFn)
});
})
}
return module;
}
addMiddleware
const addMiddleware = function(actionName, actionFn, middlewareFn) {
return function(context, payload) {
const resultFn = middlewareFn(actionFn)
if(resultFn) {
resultFn(context, payload)
}
}
}
defining middleware in the module
const actions = {
myAction: (context, payload) => {
...
context.commit('THE_ACTION', payload)
...
},
}
const middlewares = {
checkAuthMiddleware: (action) => {
return this.isLoggedIn
? action // if logged-in run this action
: null; // otherwise cancel it
}
}
export default {
state,
getters,
mutations,
actions,
middlewares
}
This implementation has module-specific middleware functions, but you could also define them globally and apply to as many modules as applicable.
I am currently designing the architecture of a webapp using React.JS. In React, data flow is unidirectional. Sometimes (often) we want to communicate between views. React solves this problem with flux. So far so good, I now have stores that holds the (shared) data.
Now I wonder if flux is also the right solution for this old problem:
A view, say a table needs data from a server that it should render. I am not that experienced with React therefor pardon me if it's a stupid question, but is flux also the right solution for getting data from a server and parse it to the registered views? If so, is there a best practice to work with async data? If not, what would be an appropriate way to fetch data from the server for a react view?
I actually don't want a view to call for the data. In my opinion a view should be as stupid as possible...
..is flux also the right solution for getting data from a server and
parse it to the registered views?
You can do this with flux. Flux is al about Action Creators. In the flux architecture the idea is that you have a Controller View that registers itself with a Store. The Controller View is your smart component, it gets it state from the Store(s) and usually it renders child components, dumb components, where it passes (some of) the state as properties to it's child(ren).
When for instance you're fetching data from a server, you need to trigger an Action Creator, this Action Creator will then call a Web API utility that performs the actual request. When successful, the Web API utility will call a Server Action Creator that dispatches an action containing the payload received from the server. Any registered Store will then process this action and emit a change event. This way any Controller View interested in a Store, will be notified by it when the Store's data has changed. The Controller View will update it's state and re render itself and any child(ren) to display correct data.
This means that you can call an Action Creator to for instance fetch data from the server in the componentDidMount (note that this hook is only executed once though!) method of the Controller View.
Initially the Controller View will ask the store for data and get, for example, an empty array, which will be set as the Controller View's state and the Controller View will render something empty. Then after the data has been fetched, the Controller View (who is notified by the Store) will again retrieve the data from the Store, only now it will not be empty. The Controller View updates it's state accordingly, triggering a re render and displaying the appropriate data.
The essence of this is captured in this minimal (pseudo) code:
// Action Creator: actions/data.js
import { fetchData } from '../utils/server';
export function fetch() {
fetchData();
}
// Server Action Creator: actions/data-server.js
import dispatcher from '../dispatcher';
export function receiveData(data) {
dispatcher.dispatch({
type: 'RECEIVE_DATA',
payload: data
});
}
// Web API Utility: utils/server.js
import request from 'request';
import { receiveData } from '../actions/data-server';
export function fetchData() {
request.get('https://api.com/v1/data', receiveData);
}
// Store: stores/store.js
import dispatcher from '../dipatcher';
import { EventEmitter } from 'events';
const CHANGE_EVENT = 'change';
let __data__ = [];
const Store = Object.assign({}, EventEmitter.prototype, {
emitChange () {
this.emit(CHANGE_EVENT);
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getData () {
return __data__;
}
});
Store.dispatchToken = dispatcher.register(function (action) {
switch (action.type) {
case 'RECEIVE_DATA':
__data__ = action.payload;
Store.emitChange();
break;
}
});
export default Store;
// Controller View: components/List.jsx
import React from 'react';
import Store from '../stores/Store');
import { fetch } from '../actions/data';
var __getState = function () {
return {
data: Store.getData()
};
};
export default React.createClass({
getInitialState () {
return __getState();
},
componentWillMount () {
fetch();
},
componentDidMount () {
Store.addChangeListener(this._onChange);
},
componentWillUnMount () {
Store.removeChangeListener(this._onChange);
},
_onChange () {
this.setState( __getState() );
},
render () {
const { data } = this.state;
return (
<div className="list">
<ul>
{ data.map(dataItem => <li>{ dataItem.name }</li> )}
</ul>
</div>
);
}
});
Check out more detailed flux examples here.
I actually don't want a view to call for the data. In my opinion a
view should be as stupid as possible...
It's good practice to distinguish between smart and dumb components. The idea is that smart components hold state and call actions, while the dumb components have no dependencies on the rest of the application. This creates a clear separation of concerns and allows for better reusability and testability of your components. Read more about it here.
Also there are some interesting alternatives besides flux. For instance there is redux. It uses a single immutable state Object (i.e. Store) where a reducer (pure function) is only allowed to modify application state by means of actions.
And definitely check out react-refetch if you're mostly fecthing and rendering read-only data from a server.
A common way to do this is to initialize the async call in the view code's componentDidMount() method, and render it properly.
The code might be different for different flux implementations but you get the idea
getInitialState() {
return {
rows: MyStore.getState().rows
}
}
componentDidMount() {
this.listenTo(MyStore, this.onChange)
MyActions.fetchData()
}
onChange() {
this.setState(this.getInitialState())
}
....
render() {
if (!this.state.rows) {
return <span />
}
return <Table rows={this.state.rows}>
}
As for the container components, it's totally up to you to use it or not.