Importing redux store is undefined (loading order issue?)

Viewed 9418

Update 2 - Add minimal 'working' example showing the issue

I trimmed down the project as far as I could while still showing the issue, to allow people to try out ideas/debug if they're interested github:store_import_test

The error happens in: request.js

Note: I'm aware the bounty is about to expire, but I'll re-enable it if that happens. I do appreciate all ideas/help put out so far!

End update 2

Update 1 - purpose description:

I want to access a value from the store (that can change overtime) in a 'utility function'. According to the redux docs subscribe is a valid option.

End update

I'm trying to import my redux-store outside of a component (in request.js , see below) similar to: What is the best way to access redux store outside a react component?

However, all those solutions (including https://github.com/reactjs/redux/issues/776) don't work because my request.js tries to import the store before createStore() is called in store.js resulting in store being undefined.

My directory structure looks like this

 .  
 ├── app  
 │   ├── api  
 │   ├── network 
 │   |    └── request.js 
 │   ├── app.js  
 │   ├── store.js  
 ├── index.android.js  
 ├── index.ios.js

The index.android/ios.js are the entry points and just load app.js

index.android/ios.js

import App from './app/app'

app.js

import store from './store'
class App extends React.Component {
    render() {
        return (
            <Provider store={store}>
                    <RootStackNavigation />
            </Provider>
        )
    }
} 

store.js

...
const store = createStore(reducer, initialState, middleware())
export default store

request.js

import store from '../../store'
store.subscribe(listener)
...
const someFunc(){}
export default someFunc

My thoughts / what I tried / where I get lost

Note: the store import path in request.js is valid, double checked Note2: the store is usable in app.js and the remainder of the program

I would think that the import store from '../../store' in request.js would trigger the const store = createStore(reducer, initialState, middleware()) line but apparently it seems it does.

attempt 1

I tried to also export the store as such:

export const store = createStore(reducer, initialState, middleware())

and imported it in request.js as:

import {store} from '../../store

thinking maybe the 'clever' default loading does some magic I don't know/understand. Same error, undefined

attempt 2 add getStore() to store.js

let store = null
store = createStore(reducer, initialState, middleware())
export function getStore() {
    if (store == null) {
        store = createStore(reducer, initialState, middleware())
    }
    return store
}

export default store

Doesn't work, the arguments to createStore have not been initialised yet.

I must be misunderstanding the loading process or mixing it with that of python, but what's preventing this from working? Others seem to be using the same solution (see the above mentioned posts) successfully.

4 Answers

Found the problem. I was right - it’s a cycle. In store.js you require reducers/index.js then navigation.js then ../navigators/RootNavigationConfiguration which requires Home.js which requires /api/network which requires request.js and it requires store which at this point is not initialized. Try moving the store.subscribe(listener) along with the listener function to store.js right before you export it. Don’t forget to remove import store.js from request.js

I feel that you want to make antipattern. Your needcase is whispering to my ears "saga", but let's help you as it is.

I want to give you closed issue from github, but I want to make it clear where "they" are getting store - import {store} from "store"; is exported const store = configureStore(); and configure store (to avoid missunderstanding) is returning redux.createStore(...).

After you know what I wrote - you can understand what "they" are writing there and there.

Did you write "../../store" in request.js, but your directory structure says it should be "../store".

You can simply create your store in the App Component to reduce complications, and pass it through the to sub-components.

let store = createStore(reducers);

export class App extends Component {
    constructor() {
        super();
        this.state = store.getState();
        this.syncState = this.syncState.bind(this);
    }

    syncState() {
        this.setState(store.getState());         
    }

    componentDidMount() {
        this.setState(store.getState());
        const unsubscribe = store.subscribe(this.syncState);
    }

    render() {
        return (
            <Provider store={store}>
                <AppNavigator/>
            </Provider>
        )
    }
}

Your sub-components then need to import connect from react-redux.

import {connect} from 'react-redux';

export class SubComponent extends Component {
  //Implementation
  //this.props.myLocalCopy - gives you the value
  //this.props.someTask(data) - Updates the store and updates your props.
}

function mapStateToProps(state) {
    return {
        myLocalCopy: state.someStoreProperty
    };
}

function mapDispatchToProps(dispatch) {
     return {
         someTask: (data) => {
             dispatch(actionCreators.someTask(data));
         }
     }
}

export default connect(mapStateToProps, mapDispatchToProps)(SubComponent)

This will help you to synchronize your app state with store throughout the workflow. Hope that helps! :)

Related