Importing redux store is undefined (loading order issue?) - javascript

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.

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

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

Uncaught [Invariant Violation: Could not find "store" in either the context or props

Basically I am importing componentB into componentA
and in componentA im basically doing something like
const ComponentA = () => {
const ComponentB = blah.map(() => {
})
//more stuff
return(
<div> stuff here </div>
)
}
but when I run the tests (already built) for ComponentA file they blow up with that error message
both components are correctly connected to the store:
export default connect(mapStateToProps)(ComponentA)
export default connect(mapStateToProps, mapDispatchToProps)(ComponentB)
what would cause that error?
I know before I've had to import it differently. currently im just importing it like this: import componentB from '/components'
but I did try import { componentB } from '/components' so just exporting the component by passing the store but then it doesn't work coz it comes unconnected from the store
any ideas?
When you're testing redux connected components you need to render them with a fake store, or in-memory store. Usually best practice is to render the un-connected component as import {component} from '...'; and then pass down the props that your component is expecting. The redux library already has test cases so there's no point duplicating that work.

Vuex store modules not loading in right order when importing store directly

I'm probably not seeing obvious, but after hours I don't get it still.
The problem: when I import my Vuex store into a non-component file (a api service), one module gets loaded properly while the other one only by name, but is otherwise empty.
// store.js
import * as collections from "./modules/collections";
import * as auth from "./modules/auth";
export default new Vuex.Store({
modules: {
auth,
collections
}
});
Both these modules are near-identical. Both have
export const getters = {}
export const actions = {}
export const mutations = {}
export const state = {}
Now when in some other non-component file I DON'T include the store, my vue store looks like this:
{"auth":{"authenticated":false,"authToken":"123","loginPending":false,"loginError":{}},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
Now when I import the store to use it in my service like so:
import store from '../store'
// NO OTHER MENTIONS OF STORE, ONLY IMPORTING
Suddenly only my auth module is "empty"
{"auth":{},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
It has something to do with module loading.
Order of loading by adding console.log statements:
Without the import:
INIT AUTH
INIT collections
INIT store
With the import:
INIT collections
INIT store
IMPORT STATEMENT -- INTO SERVICE
INIT AUTH
I'm using the Vue Webpack Boilerplate
Sigh, sorry. Circular dependency indeed. Was expecting a warning if I'd did that, but didn't.
Thanks Emile Bergeron.

MobX and HMR: Please avoid replacing stores as the change might not propagate to all children

I try to enable HMR on my project with typescript and webpack 2 but whenever I make a change I see the following output in the logs and the store is reset to its original values(discards state)
index.js:832 MobX Provider: Provided store 'appStore' has changed. Please avoid replacing stores as the change might not propagate to all children
The UI is refreshed partially after loading the hot update bundle which is good and expected but since the store lost its state, the UI is not the expected one.
What is the right pattern for keeping the state of mobx stores across HMR updates?
Currently the coode looks like the following:
const uiStore = new UiStore();
const appStore = new AppStore();
function render() {
ReactDOM.render(
<AppContainer>
<Provider appStore={appStore} uiStore={uiStore}><App/></Provider>
</AppContainer>, document.getElementById('root'))
}
const hot = (module as any).hot
if (hot)
hot.accept(() => {
render()
})
render()
The problem was that after every hot reload, my index file that was referencing App component was re-required by the webpack on the client side and this was destroying the uiStore and appStore objects that was initialised in the index file.
Declaring the store as a member of the window object has solved the problem. The stores now survive across hot module replacements.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import {observable} from 'mobx';
import {Provider} from 'mobx-react';
import {AppStore, UiStore, Stores} from './types/index';
import App from './components/App';
import './index.css';
declare var stores:Stores;
(window as any).stores = (window as any).stores || new Stores(new UiStore(), new AppStore());
function render() {
ReactDOM.render(
<AppContainer>
<App {...stores} />
</AppContainer>, document.getElementById('root'));
}
const hot = (module as any).hot
if (hot)
hot.accept(() => {
render();
})
render();
Each time you reload new page from another, your top component might be re-rendered which cause call
const uiStore = new UiStore();
const appStore = new AppStore();
each times.
this might be complained by mobx because you are replacing whole stores with new instance, which is not intended by Mobx.
Might be better if you create uiStore, appStore as a state, which still remain renders new pages.

Redux - managing preload state

I'm building an application, where I need to preload people and planet data (it's likely that in the future more preload requirements may be added) on launch of the application. I want to have value in the store that represents the global state of the app as loaded: <boolean>. The value would be true only then when the preload requirements people.loaded: true and planet.loaded: true are true. The store would look something like this:
Store
├── loaded: <Boolean>
├── people:
│ ├── loaded: <Boolean>
│ └── items: []
├── planets:
│ ├── loaded: <Boolean>
│ └── items: []
Separate action creators make the needed async requests and dispatch actions which are handled by the People and Planets reducers. As shown below (uses redux-thunk):
actions/index.js
import * as types from '../constants/action-types';
import {getPeople, getPlanets} from '../util/swapi';
export function loadPeople () {
return (dispatch) => {
return getPeople()
.then((people) => dispatch(addPeople(people)));
};
}
export function loadPlanets () {
return (dispatch) => {
return getPlanets()
.then((planets) => dispatch(addPeople(planets)));
};
}
export function addPeople (people) {
return {type: types.ADD_PEOPLE, people};
}
export function addPlanets (planets) {
return {type: types.ADD_PLANETS, planets};
}
export function initApp () {
return (dispatch) => {
loadPeople()(dispatch);
loadPlanets()(dispatch);
};
}
../util/swapi handles fetching people and planet data either from LocalStorage or making a request.
initApp() action creator calls other action creators within site.js just before rendering to DOM as shown below:
site.js
import React from 'react';
import {render} from 'react-dom';
import Root from './containers/root';
import configureStore from './store/configure-store';
import {initApp} from './actions';
const store = configureStore();
// preload data
store.dispatch(initApp());
render(
<Root store={store} />,
document.querySelector('#root')
);
1. What are the best practices for managing global preload state of the application in Redux?
2. Is having a global loaded state in the store necessary?
3. What would be a scalable way of checking app loaded state in multiple React components? It doesn't seem right to include People and Planet state for containers that just needs to know the global app state and doesn't handle rendering of People or Planets. Also that would be painful to manage when the global loaded state would be needed in multiple containers.
Quoting part of Dan's answer from Redux - multiple stores, why not? question.
Using reducer composition makes it easy to implement "dependent updates" a la waitFor in Flux by writing a reducer manually calling other reducers with additional information and in a specific order.
4. Does Dan by calling other reducers mean calling nested reducers?
First, let me correct your example.
Instead of
export function initApp () {
return (dispatch) => {
loadPeople()(dispatch);
loadPlanets()(dispatch);
};
}
you can (and should) write
export function initApp () {
return (dispatch) => {
dispatch(loadPeople());
dispatch(loadPlanets());
};
}
You don’t need to pass dispatch as an argument—thunk middleware takes care of this.
Of course technically your code is valid, but I think my suggestion reads easier.
What are the best practices for managing global preload state of the application in Redux?
What you’re doing seems correct. There are no specific best practices.
Is having a global loaded state in the store necessary?
No. As David notes in his answer, you’re better off storing only necessary state.
What would be a scalable way of checking app loaded state in multiple React components? It doesn't seem right to include People and Planet state for containers that just needs to know the global app state and doesn't handle rendering of People or Planets. Also that would be painful to manage when the global loaded state would be needed in multiple containers.
If you’re concerned about duplication, create a “selector” function and place it alongside your reducers:
// Reducer is the default export
export default combineReducers({
planets,
people
});
// Selectors are named exports
export function isAllLoaded(state) {
return state.people.loaded && state.planets.loaded;
}
Now you can import selectors from your components and use them in mapStateToProps function inside any component:
import { isAllLoaded } from '../reducers';
function mapStateToProps(state) {
return {
loaded: isAllLoaded(state),
people: state.people.items,
planets: state.planet.items
};
}
Does Dan by calling other reducers mean calling nested reducers?
Yes, reducer composition usually means calling nested reducers.
Please refer to my free Egghead tutorial videos on this topic:
Reducer Composition with Arrays
Reducer Composition with Objects
Reducer Composition with combineReducers()
Implementing combineReducers() from Scratch
A loaded flag in your store state makes perfect sense.
However you've got too many of them. You've got state.people.loaded, state.planets.loaded as well as state.loaded. The latter is derived from the first two. Your store really shouldn't contain derived state. Either have just the first two or just the latter.
My recommendation would be to keep the first two, i.e. state.people.loaded and state.planets.loaded. Then your connected component can derive an ultimate loaded state. e.g.
function mapStateToProps(state) {
return {
loaded: state.people.loaded && state.planets.loaded,
people: state.people.items,
planets: state.planet.items
};
}

Categories