Use Redux state to initialize Axios client - javascript

I have a React/Electron application I'm working on in which I want to use data from my Redux store to initialize my Axios client. The use case is, for example, on first load of the app the user enters some information, like their username. This is pushed to the Redux store (and persisted in localStorage for future use), then used in the baseURL of the axios client for subsequent network requests.
The problem is, I can't get axios to work with react-redux and the connect() function. Axios' function exports seem to be hidden by the exported HOC, and any time I call one of its functions I get the following error:
TypeError: _Client2.default.get is not a function
My client looks something like this:
import axios from "axios";
import { connect } from "react-redux";
const Client = ({ init }) => {
return axios.create({
baseURL: `http://${init.ip}/api/${init.username}`
});
};
const mapStateToProps = state => {
return { init: state.init };
};
export default connect(
mapStateToProps,
{}
)(Client);
What am I doing wrong here?

Here in react-redux documentation https://react-redux.js.org/api/connect#connect-returns it says that The return of connect() is a wrapper function that takes your component and returns a wrapper component with the additional props it injects. So it returns react component that wraps react component. Your function returns axios client, it doesn't render anything.
I prefer to use action creators and make api calls there(Therefore I don't pass axios client or whatever). But if I decided to that I would initialize axios client inside reducer and keep in the store. And then pass it to clients as props.
const mapStateToProps = state => {
return { axios: state.axios };
};

On top of #Ozan's answer, In this case what you can do is create a main component, connect it with redux and dispatch an action on mount to initialize axios client.

You should initiate AXIOS client before you load App.js. I recommend you can use redux-axios as redux middleware and use action to call api.
https://github.com/svrcekmichal/redux-axios-middleware

Related

Send data between components in ReactJs using Apollo Client

According to the documentation, GraphQl provides state management like Redux. I have 2 components. In the Component1 i get data from the server using AppoloClient, it works ok, and in the Component2 i want to read data from the cache(store).
//Component1
import React from "react";
import { gql, useQuery } from "#apollo/client";
const EXCHANGE_RATES = gql`
query GetExchangeRates {
rates(currency: "EUR") {
currency
}
}
`;
const Component1 = () => {
const { loading, error, data } = useQuery(EXCHANGE_RATES);
console.log("component1", data);
return <div>Component1</div>;
};
export default Component1;
//Component2
import React from 'react';
import {gql} from "#apollo/client";
import {client} from "./App";
const Component2 = () => {
const info = client.readQuery({
query: gql`
query EXCHANGE_RATES {
rates(currency: "EUR") {
currency
}
}
`,
});
console.log('component2',info);
return (
<div>
component2
</div>
);
};
export default Component2;
Issue: I can get data in component 1, but when I try to read data from component 2, I get undefined.
Question: How to solve this to be able to read data that is fetched in component 1, in component 2? Also how in GraphQl and Apollo client to pass an object for example in the cache, and to read this in component 1(like redux functionality)?
demo: https://codesandbox.io/s/empty-sun-symv6?file=/src/App.js
When the App mounts, both of your component's data are empty.
Then apollo fetches the data with useQuery. And your component1's state got changed. Because of that, component1 re-render and log the new data.
But there is no state on your component2 that changed. So, component2 does not re-render.
To solve this, you can run useQuery hook with the same query on the component2 again, by default apollo will provide you the data from the Cache.
Apollo provides client-side state handling which can be set up to handle your client site state in the same we you do it with your server-side state.
In your example this is not what you want. Recently there is a noticeable shift in the react community that server side data should not be stored in your global state handling tool of choice. Your fetching tool (in your case apollo) should handle this by caching the responses.
To solve your problem, where both components are using the exact same query, you should just do that. Run the query twice and let apollo handle the caching. So you could pull out the query to a query file or just create a useRates hook and import that in your component to even better share the logic between your components.
To answer why your approach is not working you have to understand that your lookup in the cache is happening at a time before your request has even finished and that this cache look up is not "reactive".
Edit: I just got this out fast to provide a starting point and can clean this up later if things got cleared up.

Vuex modules and returning a promise in an action

I want to split my vuex file into modules, but as soon as I do that the promise I return from my action becomes undefined when I console log it in the actual template.
So in my action I have something like
return axios.get(....)
And in my component I have a method that does the following
this.$store.dispatch('setRules').then(response => {console.log(response)})
As soon as I switch from using store.js to importing a module in my store.js file, the response becomes undefined but the rest of vuex still works correctly. When checking the state I also see that the action actually gets executed and the state gets updated, so it seems as if it is a problem with axios.
The new store.js file looks the following:
import Vue from 'vue'
import Vuex from 'vuex'
import stock from './modules/stock';
Vue.use(Vuex);
export default new Vuex.Store( {
modules: {
stock,
}
});
And in my module stock.js I have something like this.
const getters = {..}
const actions = {..}
const mutations = {..}
const state = {..}
export default {
namespaced: false,
state,
mutations,
actions,
getters
}
Does anyone have an idea what I am doing wrong?
The above scenerio is happening because javascript is asyncronous and hence before the response from your api call is returned the console statement is executed and hence you see that as undefined.
You can try using async await on axios call as
async actionName ()
{
await axios.get(..).then(response => { console.log(response)} )
}
So I found out that the problem was caused by the fact that I tried to acces this.$store.state.x while it should be this.$store.state.stock.x. Is there a way to not need the module name to access the state?

Mock componentDidMount lifecycle method for testing

I have a component which uses axios within componentDidMount to retrieve data from the server. When using Jest / Enzyme for unit testing the component, the tests fail with a network error.
How do I mock componentDidMount so that the axios call to the server does not happen?
The component in question uses React DnD and is a DragDropContext.
class Board extends Component {
componentDidMount() {
this.load_data();
}
load_data = () => {
// axios server calls here
}
}
export default DragDropContext(HTML5Backend)(Board);
Example test:
it('should do something', () => {
const board = shallow(<Board />);
// get the boardInstance because board is wrapped in Reactdnd DragDropContext
const boardInstance = board.dive().instance();
boardInstance.callSomeMethodToTestIt();
expect(testSomething);
}
So I just need to mock componentDidMount or load_data so that it doesn't try to call the server. If the load_data method was being passed in as a prop, I could simply set that prop to jest.fn(). However this is my top level component which does not receive any props.
With the new update to enzyme, lifecycle methods are enabled by default.
(https://airbnb.io/enzyme/docs/guides/migration-from-2-to-3.html#lifecycle-methods)
However, you can disable them in with shallow rendering as such:
const board = shallow(<Board />, { disableLifecycleMethods: true });
docs: https://airbnb.io/enzyme/docs/api/shallow.html#shallownode-options--shallowwrapper
Lifecyle methods do not defaultly work with shallow, you need to add a flag with shallow
const board = shallow(<Board />, { lifecycleExperimental: true });
Before that you can create a spy on componentDidMount to check if it was called like
const spyCDM = jest.spyOn(Board.prototype, 'componentDidMount');
and to prevent the axios request from hitting the server , you can mock the axios call using moxios

Getting router params into Vuex actions

I would like to pass router params into Vuex actions, without having to fetch them for every single action in a large form like so:
edit_sport_type({ rootState, state, commit }, event) {
const sportName = rootState.route.params.sportName <-------
const payload = {sportName, event} <-------
commit(types.EDIT_SPORT_TYPE, payload)
},
Or like so,
edit_sport_type({ state, commit, getters }, event) {
const payload = {sportName, getters.getSportName} <-------
commit(types.EDIT_SPORT_TYPE, payload)
},
Or even worse: grabbing params from component props and passing them to dispatch, for every dispatch.
Is there a way to abstract this for a large set of actions?
Or perhaps an alternative approach within mutations themselves?
To get params from vuex store action, import your vue-router's instance, then access params of the router instance from your vuex store via the router.currentRoute object.
Sample implementation below:
router at src/router/index.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes
})
export default router
import the router at vuex store:
import router from '#/router'
then access params at vuex action function, in this case "id", like below:
router.currentRoute.params.id
Not sure to understand well your question, but :
This plugin keeps your router' state and your store in sync :
https://github.com/vuejs/vuex-router-sync
and it sounds like what you are looking for.
To my knowledge ( and I've looked into this for a project I'm working on ) no, there is not.
The simplest way to do this is to abstract route fetching or anything you want to do to a service and use it in your vuex file or if you use modular approach import it in you actions.js file.
so paramFetching.js file would look like this:
export default {
fetchRouteParams: function() {
// do fetching
// you should return a promise
}
}
Then import that into your vuex
import service from 'paramFetching.js'
And then make an action like so
...
fetchParamsAction: function({commit}) {
service.fetchRouteParams()
.then( (response) => { // stuff gottten from service. you should o your commit here } )
.catch( (error) => { // error handling } )
}
And then just dispatch this action and everything will be handled in an action. So it kinda isolates that from the rest of the code.
This is just a general idea. I'm sorry if it's not clear enough. If I can help further, please ask.
You can use this function to get params into Vuex
import router from './router';
router.onReady(()=>{
console.log(router.currentRoute.params.sportName)
})

Load JSON data in my initital state

I try to learn React.js but I have some difficulties to load data from my server with redux. For the moment my global state is loaded with static json like this:
My store.js
import ...
// Load my static json data
import tickets from '../data/tickets.js'
import interventions from '../data/interventions.js'
const defaultState = {
tickets,
interventions
}
const store = createStore(rootReducer, defaultState);
export const history = syncHistoryWithStore(browserHistory, store);
export default store;
My application works perfectly. Now I want to fill my components with the data returned from my node.js server. However I have no idea how to corretly do it.
I have to fill my defaultState only once ? After the reducers will change the values ?
Is isomorphic-fetch is a good way to call a url from my server ? (I use react-router on my client)
Thanks for your answers.
if you try to do this on client side you would be asynchronous.
On your entry point, where you are importing the store, you can fetch your data then dispatch an action that will init your tickets or interventions.
You can also use redux-async-connect to give your app all required data before it's will be mounted, this can be use also on server rendering side

Categories