Getting router params into Vuex actions - javascript

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)
})

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.

Use Redux state to initialize Axios client

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

Cannot access redux store in actions/api service (without connect() func)

I'm trying to fetch the token from my auth reducer in my app for making subsequent requests for more resources.
The problem is I can't access the store.getState() or store anywhere outside my components. Like actions/api service.
I remember earlier making an app where I was able to without any problems.
Here's a contrived example : https://stackblitz.com/edit/react-redux-app-1wxxab?file=index.js I've made a basic todo app and you can see in actions/index.js that when I console.log(store) I get undefined.
Update: I've updated the example to emphasise the problem, where I can't access it in a separate file api.js
Another Example: https://stackoverflow.com/a/43944684/1356046 they say it works like this but I'm not able to reproduce it.
Anyway to fix this and access the store state? Have tried everything since yesterday. Thanks.
Export store when you created it, then use it eg store.dispatch(action); or create api specific middleware
See What is the best way to access redux store outside a react component? for many examples
Update:
https://stackblitz.com/edit/react-redux-app-1mswrv
store.js:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
export const store = configureStore({
reducer: rootReducer,
});
index.js:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import { store } from "./store";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
api.js:
import {store} from './store';
// Some promise which dispatches a fetch request after getting the token from the store
export const getSomething = () => {
console.log('store:', store);
return store;
}
Cloned your stackblitz and made the following changes:
In your api file do the following:
import store from './store';
Add a store.js with the content:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer,
});
export default store;
And changed your index.js to:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './store'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
I would still opt for saving a token in local storage, if the user is logged in and opens another tab the user needs to log in again because the other tab has no access to the token, same when the user goes to another site and then back to your site.
If you are using redux-thunk and passing your api with withExtraArgument() to the thunk middleware passed to createStore() then you can use a lazy callback function to inject selectors for your API token (or whatever else you'd like) to a wrapped API helper function.
Personally, I prefer this approach as it decouples the api helper from redux. The API helper does not have to know about redux at all for this to work. Insread of imports, you simply inject the selector into the API helper, which then calls them when needed (long after the store is initialized).
api.js
// function accepting callback that returns a pretty
// standard post function
export const post = (getHeaders) => (url, body) => {
return fetch(url, {
method: 'POST',
headers: getHeaders(),
...
}
}
store.js
import * as api from './utils/api';
import { selectAuthHeaders } from './features/auth'
const store = createStore(
rootReducer,
initialState,
applyMiddleware(
thunk.withExtraArgument({
post: api.post(() => {
return selectAuthHeaders(store.getState());
}
})
)
)

Unexpected keys, "length", "action", "location" etc in Redux when using connectedRouter

I am trying to write a React app and I am trying to use ConnectedRouter:
https://github.com/supasate/connected-react-router
It's a Redux binding for React Router.
I am getting the following error:
Now I think this is probably related to this question's accepted answer:
Redux: Unexpected key found in preloadedState argument passed to createStore
However unlike there when trying to pass default, I actually probably want these in my combine reducer.
Here's my current code in my reducers/index.js:
export default history =>
combineReducers({
router: connectRouter(history),
search,
profile,
color,
categories,
coordinates: LocationReducer,
idprovider,
firstFavorite,
analytics,
sidebar,
messages,
total_messages,
onesignal,
tokens
});
And in my store.js:
import createRootReducer from "./reducers/index";
I'm not quite sure what the correct solution is here, as ConnectedRouter doesn't seem to do anything with these values.
What is the correct solution?
EDIT: In my example bellow I used syntax used in connected-react-router v4, but my example was definitely wroking.
There was an update in usage for v5/v6, if you are using version>=5, try to migrate my example into it:
https://github.com/supasate/connected-react-router/blob/master/FAQ.md#how-to-migrate-from-v4-to-v5v6
You probably do not intialize the store correctly.
Try this:
reducers/index.js
export default combineReducers({
// router reducer will be added automatically by connectRouter in store.js
search,
profile,
color,
categories,
coordinates: LocationReducer,
idprovider,
firstFavorite,
analytics,
sidebar,
messages,
total_messages,
onesignal,
tokens
});
store.js
import {connectRouter, routerMiddleware} from 'connected-react-router';
import {createBrowserHistory} from 'history';
import reducers from './reducers';
const history = createBrowserHistory(history);
const store = createStore(
connectRouter(history)(reducers),
applyMiddleware(routerMiddleware(history))
);

Url change and display the keyword used in a serach component

I have a search bar that when you click on it, using redux bring the results and display a list of product. But the url stay the same how can I make the url show the keyword I use to search like this:
http://localhost/seach?q=keyword
I don't if this is the best way. But I'm using history.pushState() to change the url without reload.
You can use react-router to control the router of the application, and use react-router-redux to connect the route to the redux.
```
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// Dispatch from anywhere like normal.
store.dispatch(push({
pathname: 'search',
query: {
q: keyword
}))
```

Categories