titouancreach / vuejs-redux

Flexible binding between Vue and Redux
57 stars 11 forks source link

problem store init #53

Closed johai closed 5 years ago

johai commented 5 years ago

Hi,

i want to use vuejs + redux + redux-observable.

i have some trouble to add store on the beginning and to propagate the store to other subs pages/components.

I have another problem with combining 2 reducers.

composition of my project :

main.js

import Vue          from 'vue'
import Vuetify      from 'vuetify'
import VueRouter    from 'vue-router'

import App             from './App.vue'

import reduxobservablesample from './provider_sample.vue'

Vue.use(Vuetify);
Vue.use(VueRouter);

const routes = [
    { path: '/reduxobservablesample', component: reduxobservablesample }
]

const router = new VueRouter({
    routes
})

import { createEpicMiddleware }         from 'redux-observable';
import { createStore, applyMiddleware } from 'redux';
import { combineEpics } from 'redux-observable';
import { combineReducers } from 'redux';
import { rootEpicSample }    from './components/module_sample/root'; 
import { rootReducerSample } from './components/module_sample/root'; 

const rootEpic = combineEpics(
    rootEpicSample
    );

const rootReducer = combineReducers({
        rootReducerSample
});

const epicMiddleware = createEpicMiddleware();

const middlewareChoice = applyMiddleware(epicMiddleware)

const store = createStore(
    rootReducer,
    middlewareChoice
);
epicMiddleware.run(rootEpic);

let app = new Vue({
    el: '#app',
    store,
    router,
    components: {App},
    render: h => h(App)
})

App.vue

<template>

<Provider 
    :store="store">
    <v-app dark>
        <v-content>
            <v-container fluid>
                <router-view></router-view>
            </v-container>
        </v-content>
        <v-footer app>&copy; 2018-2019</v-footer>
    </v-app>
</Provider>
</template>

<script>
import Provider         from 'vuejs-redux';

export default {
    components: {
        Provider
    }
}
</script>

<style>
html {
    overflow-y: auto
}
</style>

provider_sample.vue

<template>
    <Provider 
        :mapDispatchToProps="mapDispatchToProps" 
        :mapStateToProps="mapStateToProps"
        :store="store">
        <template slot-scope="{ping_pong, user, actions}"> 
            <Sample :actions="actions" :ping_pong="ping_pong" :user="user" />
        </template>
    </Provider>
</template>

<script>

import { createStore, bindActionCreators } from 'redux';
import Provider from 'vuejs-redux';
import * as Actions  from './ping_actions.js';
import * as Actions2 from './users_actions.js';

import Sample from './sample.vue'
export default{  
    methods: {
        // extract state
        mapStateToProps(state) {
            return { ping_pong: state.ping_pong,
                     user:      state.user  }
        },
        // create binding actions - dispatch 
        mapDispatchToProps(dispatch) {
            return { actions: bindActionCreators( Object.assign({}, Actions, Actions2), dispatch) }
        }
    },
    components: {
        Sample,
        Provider
    }
}
</script>

<style>
</style>

sample.vue

<template>
    <div> 
    <v-btn flat v-on:click="actions.ping()" > PING </v-btn>
    <v-btn flat v-on:click="actions.pong()" > PONG </v-btn>
    <v-textarea auto-grow v-model="ping_pong" 
                readonly=true >
    </v-textarea> 
<br><br>
    <v-btn flat v-on:click="actions.fetchUser()">fetch user</v-btn>
    <v-textarea auto-grow v-model="user" 
                readonly=true>
    </v-textarea>

    </div>
</template>

<script>

export default{
      props: [
          "actions", 
          "ping_pong",
          "user"
          ]
};
</script>

<style>
</style>

the error on chrome

vue.runtime.esm.js:1737 TypeError: Cannot read property 'getState' of undefined
    at s.data (bundle.es.js:29)
    at vue.runtime.esm.js:3413
    at vue.runtime.esm.js:3370
    at Nt (vue.runtime.esm.js:3307)
    at s.e._init (vue.runtime.esm.js:4624)
    at new s (vue.runtime.esm.js:4794)
    at vue.runtime.esm.js:4306
    at init (vue.runtime.esm.js:4127)
    at vue.runtime.esm.js:5604
    at f (vue.runtime.esm.js:5551)
Ke @ vue.runtime.esm.js:1737
Ue @ vue.runtime.esm.js:1728
qe @ vue.runtime.esm.js:1717
(anonymous) @ vue.runtime.esm.js:3415
(anonymous) @ vue.runtime.esm.js:3370
Nt @ vue.runtime.esm.js:3307
e._init @ vue.runtime.esm.js:4624
s @ vue.runtime.esm.js:4794
(anonymous) @ vue.runtime.esm.js:4306
init @ vue.runtime.esm.js:4127
(anonymous) @ vue.runtime.esm.js:5604
f @ vue.runtime.esm.js:5551
(anonymous) @ vue.runtime.esm.js:6087
e._update @ vue.runtime.esm.js:2656
r @ vue.runtime.esm.js:2784
Et.get @ vue.runtime.esm.js:3138
Et @ vue.runtime.esm.js:3127
e @ vue.runtime.esm.js:2791
hn.$mount @ vue.runtime.esm.js:7995
init @ vue.runtime.esm.js:4133
(anonymous) @ vue.runtime.esm.js:5604
f @ vue.runtime.esm.js:5551
(anonymous) @ vue.runtime.esm.js:6126
e._update @ vue.runtime.esm.js:2656
r @ vue.runtime.esm.js:2784
Et.get @ vue.runtime.esm.js:3138
Et @ vue.runtime.esm.js:3127
e @ vue.runtime.esm.js:2791
hn.$mount @ vue.runtime.esm.js:7995
e._init @ vue.runtime.esm.js:4636
hn @ vue.runtime.esm.js:4725
(anonymous) @ index.js:66
n @ bootstrap:19
(anonymous) @ bootstrap:83
(anonymous) @ bootstrap:83
Show 5 more frames
vue.runtime.esm.js:1737 TypeError: Cannot read property 'subscribe' of undefined
   at s.created (bundle.es.js:36) 
vue.runtime.esm.js:1737 TypeError: Cannot read property 'dispatch' of undefined
    at s.render (bundle.es.js:44)

alternative provider_sample.vue

<template>
    <Provider 
        :mapDispatchToProps="mapDispatchToProps" 
        :mapStateToProps="mapStateToProps"
        :store="store">
        <template slot-scope="{ping_pong, user, actions}"> 
            <Sample :actions="actions" :ping_pong="ping_pong" :user="user" />
        </template>
    </Provider>
</template>

<script>

import store            from "./configure_store.js"

import { createStore, bindActionCreators } from 'redux';
import Provider from 'vuejs-redux';
import * as Actions  from './ping_actions.js';
import * as Actions2 from './users_actions.js';

import Sample from './sample.vue'

import { rootReducerSample } from './components/module_sample/root';

export default{  
   data(){
      return { store }
//or
//return { store: createStore(rootReducerSample)}
    },
    methods: {
        // extract state
        mapStateToProps(state) {
            return { ping_pong: state.ping_pong,
                     user:      state.user  }
        },
        // create binding actions - dispatch 
        mapDispatchToProps(dispatch) {
            return { actions: bindActionCreators( Object.assign({}, Actions, Actions2), dispatch) }
        }
    },
    components: {
        Sample,
        Provider
    }
}
</script>

<style>
</style>

configure_store.js

import { createEpicMiddleware }         from 'redux-observable';
import { createStore, applyMiddleware } from 'redux';
import { combineEpics } from 'redux-observable';
import { combineReducers } from 'redux';
import { rootEpicSample }    from './components/module_sample/root'; 
import { rootReducerSample } from './components/module_sample/root'; 

const rootEpic = combineEpics(
    rootEpicSample
    );

const rootReducer = combineReducers({
        rootReducerSample
});

const epicMiddleware = createEpicMiddleware();

const middlewareChoice = applyMiddleware(epicMiddleware)

const store = createStore(
    rootReducer,
    middlewareChoice
);
epicMiddleware.run(rootEpic);

export store
johai commented 5 years ago

after some debug and learn about vuejs instance life cycle

i try to pass to vue-router, props store

like that main.js

import reduxobservablesampleoneone from './components/module_sample/provider_sample_oneone.vue'

import storeE from "./configure_store.js"

const routess = {
....
    { path: '/reduxobservablesampleoneone', component: reduxobservablesampleoneone,
      props: { default: { store: storeE},}, },
...
}
const router = new VueRouter({
    mode: 'history',
    routes: routess,
})

provider_sample_oneone.vue

<template>
    <Provider 
        :mapDispatchToProps="mapDispatchToProps" 
        :mapStateToProps="mapStateToProps"
        :store="store" >
        <template slot-scope="{ ping_pong, actions }" >
            <Sample :ping_pong="ping_pong" :actions="actions" />
        </template>
    </Provider>
</template>

<script>
import { createStore, bindActionCreators } from 'redux';
import Provider from 'vuejs-redux';

import * as Actions  from './ping_actions.js';

import Sample from './sample_one.vue'

export default{  
          props: [
           "store",
          ],
    methods: {
        // extract state
        mapStateToProps(state) {
            return { ping_pong: state.ping_pong }
        },
        // create binding actions - dispatch 
        mapDispatchToProps(dispatch) {
            return { actions: bindActionCreators(Actions, dispatch) }
        },
    },
    components: {
        Sample,
        Provider,
    }
}
</script>

<style>
</style>

it could be cool to make a plugins for initializing a store, and take default store when you don't do multi-store in the render phase, like your provider do.

I like your use of slot-scope instead of connect and revux pluging for initializing store.

johai commented 5 years ago

I try to extend vuejs-redux with this

I find an issue in my code, i use lambda function like in other language, but the new arrow function doesn't share scope like others, for data binding

I fixed the previous comment.

titouancreach commented 5 years ago

Hello @choupachupss, Thanks for posting an issue :)

I'm not sur to understand the issue but I will try to answer your questioning

the error on chrome

For this error, it comes from <Provider :store="store">, in order for Vue to resolve the variable store, it should be declared somewhere in the javascript component (either in props or in data). Else, store is undefined or null. I think you should have a warning in the console (before the error) because of the props validation. I think you resolved this issue yourself since in the comment, you brought your store into your scope.

it could be cool to make a plugins for init a store

Sorry, I don't understand here. You can init a store via the redux function createStore

and take default store when you don't do multi-store in the render phase, like your provider do.

Do you mean you don't want to pass the store to the provider every time if you have only one store ?

If yes, I agree. This is painful to write. You should take a look here

There is an easy solution you can make to reduce this pain point. The idea is to create a component that takes the same props as <Provider />, excluding the "store" and pass everything to the provider.

Then you can use <MyFancyProvider :mapDispatchToProps="..." :mapStateToProps="..." />

johai commented 5 years ago

it could be cool to make a plugins for init a store

Sorry, I don't understand here. You can init a store via the redux function createStore

I mean, if we initiate store on load pluging sequence, a store could be accessed to all component. like revux or vuex.

My requirements are simple :

I don't know if we need merge, i don't think we have a use case.

I think your comment have some issues, please correct it, i don't understand the code and the idea.

With my reads and understanding of vue, need a pluging for initializing global store and use component for store overwrite. we just need to know dispatch and actions for redux like ?

titouancreach commented 5 years ago

I read the updated issue and the alternative provider_sample.vue is the way the things work.

Why ?

Originally, when I created this I had a particular problem:

This is why I didn't propagate the store via the inject/provide API.

My requirements is simple :

one global store creation of a store for some component store pass by default, and overwrite with your Provider

I understand better now, let me try to recap: 90% of the time you want to use the global store and it's a pain point to specify the store each time in the provider.

I understand, and there is a little trick we can do here. As I said in my previous comment, the idea is to make your own provider. Your own provider take (as a props) the function mapDispatchToProps and mapStateToProps, it imports your store from the "./configure_store". Once you have all the data needed, you call the raw Provider.

Here is a very simple example of the idea above:

https://codesandbox.io/s/vm9zq1746l

Look at the code of FancyProvider.vue. I retrieve all the data from the Provider and pass it down to the children (via scoped slot again).

You can also look CounterProvider.vue. Now we don't mention the "store". Everything is hidden in our FancyProvider. The idea, for your use case is to rename FancyProvider to something like GlobalStoreProvider or something like this and use it 90% of the time.

When you need an other store, just use the Provider provided by the package.

I hope this help, let me know,

cheers

titouancreach commented 5 years ago

By the way, this case is quite common (don't want to provide the store each time). I will add some explanation in the README soon.

johai commented 5 years ago

I think, you answer to the requirement, i have.

It's not just global store of APP.

It's a global store, for a tree of components. With vuejs/react like framework, all graphical components, could be managed on their own separate file x.vue. So in one page, form, you can pass the store to 2-3-4-X depth. Be not mandatory to declare store on all component it's a very cool feature.

The GlobalStoreProvider can handle this use case too.

I don't know if this solution can be good with vue-router, but for the case of a page with x depth, it's good

I wil try all of what you provided and will be back for OK or KO statuts

titouancreach commented 5 years ago

I'm happy if it help. If not, don't hesitate to try things and go back to me! I close the issue for now, don't hesitate to reopen it or create a new one if needed!

johai commented 5 years ago

For information,

i have this error, with not up to date vuejs version, i was on 2.5.17

TypeError: this.$scopedSlots.default is not a function
    at s.render (bundle.es.js:44)

vuejs need version 2.6.10

johai commented 5 years ago

first fact: i tried to do a more complexe case . with two reducers + router + share of state between two reducers.

But value are never update.

https://codesandbox.io/s/049k911nyv

I erased router part. problem with import { createStore, combineReducers } from "redux"; combineReducers

second fact :

I modify your sample and in this case i can update, with a newer member on the state. https://codesandbox.io/s/v0l04px2q0

third fact :

I don't understand if i need state.newmenber or action.newmember for passing a param to dispatch like i saw on redux react for action with a param

export function increment(count3) {
  return { type: INCREMENT, count3  };
}
titouancreach commented 5 years ago

For information,

i have this error, with not up to date vuejs version i was on 2.5.17

TypeError: this.$scopedSlots.default is not a function
    at s.render (bundle.es.js:44)

vuejs need version 2.6.10

Thanks for the information

titouancreach commented 5 years ago

first fact: i try to do a case more complexe. with two reducer + router + share of state between two reducer.

But value are never update.

https://codesandbox.io/s/049k911nyv

Your code is wrong, you try accessing state.count in mapStateToProps and all the reducer but it doesn't exist because you create your store like this:

const rootReducerSample = combineReducers({
  counterr: counterr,
  counterre: counterre
});

second fact :

I modify your sample and in this case i can update, with a newer member on the state. https://codesandbox.io/s/v0l04px2q0

nice :)

third fact :

I don't understand if i need state.newmenber or action.newmember for pass a param to dispatch like i saw on redux react for action with param

export function increment(count3) { return { type: INCREMENT, count3 }; }

This plugin only help integrate Redux with Vue. All the concepts such as "action creators", "reducer" and co are the same than in Redux.

You might want to see the Redux documentation

johai commented 5 years ago

hum, after another read of a complexe case of redux documenation, i saw my mistake on store with multiple reducers.

This plugin only help integrate Redux with Vue. All the concepts such as "action creators", "reducer" and co are the same than in Redux.

You might want to see the Redux documentation

I read the redux tutorials, but it's not clear with the sample you provide, how we can do this

The only complexe use case was with rematch. false

I can't use the simplicity of rematch, because i use redux-observable, no porting ready for production available for `rematch

I will PR to update the example folder when i will got it.

titouancreach commented 5 years ago

third fact :

I don't understand if i need state.newmenber or action.newmember for pass a param to dispatch like i saw on redux react for action with param

export function increment(count3) { return { type: INCREMENT, count3 }; }

increment is an action creator, it returns a new action. The action is sent to the reducer. The action is a description of how update your state such as: f(action, state) = new state

In your reducer, you have:

action.type === "INCREMENT" action.count3 ==== the content of the count3 variable"

I don't know your mother tongue but if it's french, you can write in french cause I've trouble, sometimes, understanding what you say

johai commented 5 years ago

I will PR, for add common and more complexe use case for redux and vuejs.

Because redux use react sample for complexe case and if you don't know react and you want use redux, in my opinion it's not easy.

Thank to your work, it's more simple to add redux to vuejs.

vuex it's cool, but it's too framework depend only on vuejs.

I read that it's not a redux pattern to share same state between different reducers.