Access data property of component in Vuex store - javascript

I have a component with data as:
export default {
data() {
return {
allItems: [],
orderItems: {},
itemAdded: [],
};
},
I want to access the itemAdded array data which is present in the local component in the Vuex store.js file.
Is there anyway to do so?

You can't access component data from vuex, what you can do is pass the data as the payload when dispatching a vuex action.
for example, your vuex action:
actions: {
doSomething(context, itemAdded) {
// Do something with itemAdded
}
}
and dispatch the action from your component:
this.$store.dispatch('doSomething', this.itemAdded);
If you want to change itemAdded value on the component from the vuex action, you can return the value like this:
actions: {
doSomething(context, itemAdded) {
// Do something with itemAdded
return itemAdded;
}
}
and on your component:
// Need to use await since dispatch return a promise, you can also use `.then`
this.itemAdded = await this.$store.dispatch('doSomething', this.itemAdded);
Keep in mind that Vuex is a state management, and vuex action should do an action that's related to a state, not a place to store a 'reusable function'. I can't think of any case why would you want to return the edited value from the action, but if you think it makes sense / it's related to state, then you can do it this way.

Related

Can't access my store object from my component

In my store module /store/template.js I have:
const templateConfig = {
branding: {
button: {
secondary: {
background_color: '#603314',
background_image: ''
}
}
}
}
export const state = () => ({
branding: {},
...
})
export const actions = {
initializeStore (state) {
state.branding = templateConfig.branding
}
}
(initializeStore() is called when app initially loads)
I want to retrieve the branding the branding object in my component:
computed: {
...mapState({
branding: state => state.template.branding
})
}
But when trying to console.log() branding I see this:
Why don't I simply see the branding object? (and what on earth is this?)
You need to always use a mutation to change state. You can call one from your action:
export const mutations = {
SET_BRANDING(state, payload) {
state.branding = payload;
}
}
export const actions = {
initializeStore ({ commit }) {
commit('SET_BRANDING', templateConfig.branding);
}
}
What you're seeing with the observer is normal, and indicates that the branding object has been successfully mapped and accessed.
What you see is Vue's observable object, which is how Vue implements reactivity. Without this, there would be no reactivity, and you will see such a wrapper on all top-level reactive objects. You can pretend it's not there.
Vue in fact applies this same "wrapper" to the data object internally to make it observable:
Internally, Vue uses this on the object returned by the data function.
You won't see it on other reactive properties, but if they're reactive, they belong to some parent observable object.
You need to import { mapState, mapActions } from 'vuex' (already done I guess).
And then, you can write this
...mapState(['branding']) // or ...mapState('#namespacedModule', ['branding'])
Still, why do you not simply put the state directly (with your background_color) rather than going through a Vuex action ?
If you want to keep it this way, do not forget to await this.initializeStore() in your component before trying to access the state.

In React / Redux, how to call the same fetch twice in componentDidMount, setting 2 state variables with results

The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components

In Redux, where does the state actually get stored?

I searched a bit about this question but found very vague answers. In redux, we know that the state is stored as an object. But where is this state stored actually? Is it somehow saved as a file which can be accessed by us later on? What I know is that it does not store it in a cookie format or in the browser's local storage.
The state in Redux is stored in memory, in the Redux store.
This means that, if you refresh the page, that state gets wiped out.
You can imagine that store looking something like this:
function createStore(reducer, initialState) {
let state = initialState // <-- state is just stored in a variable that lives in memory
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action) // <-- state gets updated using the returned value from the reducer
return action
}
return {
getState,
dispatch
}
}
The state in redux is just a variable that persists in memory because it is referenced (via closure) by all redux functions.
Here's a simplified example of what is going on:
function example() {
let variableAvailableViaClosure = 0
function incrementTheClosureVariable() {
variableAvailableViaClosure += 1
}
function getTheClosureVariable() {
return variableAvailableViaClosure
}
return {
incrementTheClosureVariable,
getTheClosureVariable
}
}
let data = example()
// at this point example is finished
// but the functions it returned
// still have access to the (internal) variable via closure
console.log(
data.getTheClosureVariable() // 0
)
data.incrementTheClosureVariable()
console.log(
data.getTheClosureVariable() // 1
)
Furthermore, the statement
In redux, we know that the state is stored as an object.
isn't correct. State in redux can be any valid javascript value, not just an object. It just usually makes the most sense for it to be an object (or a special object like an array) because that allows for a more flexible data structure (but you could make the state just be a number for example, if you wanted to).
Check out the actual Redux implementation for more details.
If you want the state to persist in a cookie or localStorage, you would enhance the store such that, on top of updating the state in memory, it will save to your desired storage as well (and load from that storage when the store is initialized)
States are stored in redux-store. Redux Store is a global store which can be accessed anywhere/any components.
Let consider an example of getting Index of data using third party API. The following snippet uses componentWillMount which will trigger a fetch call using redux action.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDataFromUrl } from '../actions/index.js';
class Indexdata extends Component {
constructor(props){
super(props);
this.state = {
text: ''
}
}
componentWillMount(){
let thisVal = this;
thisVal.props.fetchIndexofData()
}
componentWillReceiveProps(nextProps){
this.setstate({
text: nextProps.indexData.text
})
}
render(){
return(
<div>
<Navbar />
<h2 className="prescription-index-title">Index of Data</h2>
</div>
)
}
}
function mapStateToProps(state){
return{
indexData: state.fetchedData
}
}
function mapDisptachToProps(dispatch){
return {
fetchIndexofData: () => dispatch(fetchDataFromUrl(access_token))
};
};
export default connect(mapStateToProps, mapDisptachToProps)(IndexData);
The above snippet will fetch index of data using a redux action. The below code is a redux action,
export function fetchDataFromUrl(){
return(dispatch) => {
const base_url = "https://api_serving_url.com"
fetch(base_url, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
dispatch({
type: "INDEX_DATA",
data: data
})
})
}
}
Redux action will dispatch data to reducer, where state will be initialized in redux store. The following code snippet is redux-reducer
export function fetchedData(state = [], action) {
switch(action.type) {
case "INDEX_DATA":
return action.data;
default:
return state;
}
}
State stored in redux store will be mapped using function mapStateToProps, implemented in the above component. Now you can access the state using props in the respective component. Lifecyclehook componentWillReceiveProps will be able to fetch the state stored redux store.
You can access the State by means of using store.getState() in any component.The only drawback of using reducer state, is that it will reset the state when you refresh the component/application. Go through Reducer Store , for more information.

Run reducer after state is updated by another reducer

Let's say I've got an app with two reducers - tables and footer combined using combineReducers().
When I click on some button two actions are being dispatched - one after another: "REFRESH_TABLES" and "REFRESH_FOOTER".
tables reducer is listening for the first action and it modifies the state of tables. The second action triggers footer reducer. The thing is it needs current state of tables in order to do it's thing.
My implementation looks something like below.
Button component:
import React from 'react';
const refreshButton = React.createClass({
refresh () {
this.props.refreshTables();
this.props.refreshFooter(this.props.tables);
},
render() {
return (
<button onClick={this.refresh}>Refresh</button>
)
}
});
export default refreshButton;
ActionCreators:
export function refreshTables() {
return {
type: REFRESH_TABLES
}
}
export function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
The problem is that the props didn't update at this point so the state of tables that footer reducer gets is also not updated yet and it contains the data form before the tables reducer run.
So how do I get a fresh state to the reducer when multiple actions are dispatched one after another from the view?
Seems you need to handle the actions async so you can use a custom middleware like redux-thuk to do something like this:
actions.js
function refreshTables() {
return {
type: REFRESH_TABLES
}
}
function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
export function refresh() {
return function (dispatch, getState) {
dispatch(refreshTables())
.then(() => dispatch(refreshFooter(getState().tables)))
}
}
component
const refreshButton = React.createClass({
refresh () {
this.props.refresh();
},
{/* ... */}
});
Although splitting it asynchronous may help, the issue may be in the fact that you are using combineReducers. You should not have to rely on the tables from props, you want to use the source of truth which is state.
You need to look at rewriting the root reducer so you have access to all of state. I have done so by writing it like this.
const rootReducer = (state, action) => ({
tables: tableReducer(state.tables, action, state),
footer: footerReducer(state.footer, action, state)
});
With that you now have access to full state in both reducers so you shouldn't have to pass it around from props.
Your reducer could then looks like this.
const footerReducer = (state, action, { tables }) => {
...
};
That way you are not actually pulling in all parts of state as it starts to grow and only access what you need.

What is the correct way to use component props in a Vuex getter?

Assume you have a simple app component with a button to add multiple counters from a counter component using a vuex store.
Here is the whole thing on webpackbin.
A bit like the basic counter example from the vuex git repo.But you want to use the vuex getter with an ID that is handed over via a property on the component, how would you do that?
The getter has to be a pure function, so you can't use this.counterId. The official docs say you have to use a computed property, but that doesn't seem to work either. This code returns undefined for the getter:
import * as actions from './actions'
export default {
props: ['counterId'],
vuex: {
getters: {
count: state => state.counters[getId]
},
actions: actions
},
computed: {
getId() { return this.counterId }
},
}
getters should be pure functions and not dependent on component state.
you can still crate a computed prop from the getter, or directly use the store:
//option A
export default {
props: ['counterId'],
vuex: {
getters: {
counters: state => state.counters
},
actions: actions
},
computed: {
currentCounter() { return this.counters[this.counterId] }
},
}
//option B (better suited for this simple scenario)
import store from '../store'
computed: {
currentCounter() {
return store.state.counters[this.counterId]
}
}

Categories