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
};
}
Related
I'm totally new to Redux.
My understanding is that redux acts like a react hook, for state, and that it is globally available.
I think I should be able to do this (in one component):
import { useStore } from 'react-redux';
function addToStore() { const [user_name, setUser_name] = useStore("jimmy") };
And then recall the variable (in another component) like this:
import { useStore } from 'react-redux';
function getFromStore() { const id = useStore.user_name }
However, this obviously doesn't work. What am I doing wrong? I've tried reading the documentation, but it is too complicated for me, doesn't deal with just a single variable storage.
Easiest Answer:
Turns out this can be done very simply using window.sessionStorage (Mozilla)
So far I've been able to easily store and retrieve variables across the app, without using Redux.
Best Answer:
#liambgs recommended React Context. Turns out this is quite a bit simpler than Redux, but offers much more out of the box than sessionStorage.
To implement Context:
Requires no new installation
Just import { reactContext } from 'react'
Create new component: class UserContextProvider extends Component {}
Wrap all other components in this new component (in App.js)
Move all user api's into UserContextProvider and store data in local state
render() { return ()} the component with value={{this.state}}
This gave all children components access to the UserContext state.
In my CRA project, I have a configureStore.ts file that exports my redux store:
// ...
export const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
and I subscribe to the store manually in staticIntl.ts, which is imported hundreds of times throughout the whole project
import { store } from "../configureStore"
class IntlManager {
constructor() {
store.subscribe(this.onStoreChange);
}
// ...
}
const manager = new IntlManager()
But when I run the project, the store.subscribe fails due to
TypeError: Cannot read property 'subscribe' of undefined
I am practically certain this is due to import order/availability
I managed to "fix" the issue by pushing the subscribe callback to the end of the task queue via no-delay setTimeout (I know it actually rounds up to couple of MS, but I don't think I really need to mind about that):
constructor() {
setTimeout(() => store.subscribe(this.onStoreChange));
}
Is this safe? I am mostly concerned about three things:
is it possible for the subscribe to still fail due to the same reason, even when wrapped in setTimeout ? For example if somehow processing my modules takes a long time
Is it possible for a first store update to happen before subscribtion (and thus missing on the subscribe callback call)?
Could such update happen as a result of my React app fetching data from the server and dispatching the response?
Are there any other potential pitfalls of my fix/hack?
Also, what other ways could I fix my problem? I'd be especially thankful for a link to a propper guide.
I practically certain this is due to import order
Yes, this sounds very much like a problem with circular dependencies. Your configureStore imports something that depends on staticIntl, which in turn imports the store from a module that is not yet evaluated.
Do not use setTimeout, fix the underlying problem with your module dependencies.
In particular, I would recommend
move const manager = new IntlManager() into a different file where you clearly control that it is executed after the createStore(…) call.
Use dependency inversion - remove the store.subscribe(this.onStoreChange) line from the IntlManager constructor and instead write store.subscribe(manager.onStoreChange) after you created store.
I'm assuming IntlManager is a singleton, and you only need one of these in your app - if this is not true, ignore the following.
Following the classic redux setup https://redux.js.org/recipes/configuring-your-store my advice is to pass the store as a prop to your main App component. And then create an instance of IntlManager by passing the store prop to the constructor:
const store = configureStore()
render(
<Provider store={store}>
<App store={store} />
</Provider>,
document.getElementById('root')
)
in App component:
const { store } = this.props;
const manager = new IntlManager(store);
Wether this works depends a bit on how IntlManager is actually used by your app. You wrote that it is "imported hundreds of times throughout the whole project". This is ok I think, you might have to rewrite the class a bit so that you can instantiate it right away, but subscribe to the store later, when the store is actually available. I envision something like this:
in App component:
import manager from '.../IntlManager';
// somewhere in componentDidMount for example:
const { store } = this.props;
manager.subscribeToStore(store);
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! :)
I'm displeased with the formulation of the question. Feel encouraged to suggest an improvement. Also, please keep in mind that due to ignoyance (ignorance leading to annoyance), I might have flawed diagnostics of hte issue. Sorry about that.
In this answer it's suggested to use this.$store.xxx and it fails in my code because this is undefined. I strongly suspect something stupid being done by the author of the code (that would be me), so I'll present the schematics of my component layout.
The way it's intended is that I have a landing page index.js that creates two components - one for the visuals of the application and one for the storage of information. The visual App will consist of a navigation bar (and a rendering area later on). The navbar will dispatch commands to the store (and to the viewing area) rendering different *.vue files showing tables, lists etc.
So, how come I get to see the text this is undefined? Is my structure entirely flawed or am I just missing a small detail here and there?
index.js
import Vue from "vue"
import Store from "./vuex_app/store"
import App from "./vuex_modules/app.vue"
new Vue({ el: "#app-base", components: { App }, store: Store });
store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { ... };
const mutations = { ... };
export default new Vuex.Store({ state, mutations });
app.vue
<template><div id="app">App component<navigation></navigation></div></template>
<script>
import navigation from "./navigation.vue"
export default { components: { navigation } }
</script>
navigation.vue
<template><div id="nav-bar"><p v-on:click="updateData">Update</p></div></template>
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
actions: { updateData },
getters: { ... }
},
methods: {
updateData: () => {
console.log("this is " + this);
this.$store.dispatch("updateData");
}
}
}
</script>
actions.js
export const updateData = ({dispatch}, data) => {
console.log("invoked updateData");
dispatch("UPDATE_DATA", data);
};
Vue.js offers a pretty nice reactive type, minimalist JavaScript framework. Unfortunately, per this link there may be some unusual usage requirements. In this case,
Don’t use arrow functions on an instance property or callback (e.g.
vm.$watch('a', newVal => this.myMethod())). As arrow functions are
bound to the parent context, this will not be the Vue instance as
you’d expect and this.myMethod will be undefined.
The question:
What is the most maintainable and recommended best practice for organising containers, components, actions and reducers in a large
React/Redux application?
My opinion:
Current trends seem to organise redux collaterals (actions, reducers, sagas...) around the associated container component. e.g.
/src
/components
/...
/contianers
/BookList
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
/BookSingle
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
app.js
routes.js
This works great! Although there seems to be a couple of issues with this design.
The Issues:
When we need to access actions, selectors or sagas from another container it seems an anti-pattern. Let's say we have a global /App container with a reducer/state that stores information we use over the entire app such as categories and enumerables. Following on from the example above, with a state tree:
{
app: {
taxonomies: {
genres: [genre, genre, genre],
year: [year, year, year],
subject: [subject,subject,subject],
}
}
books: {
entities: {
books: [book, book, book, book],
chapters: [chapter, chapter, chapter],
authors: [author,author,author],
}
},
book: {
entities: {
book: book,
chapters: [chapter, chapter, chapter],
author: author,
}
},
}
If we want to use a selector from the /App container within our /BookList container we need to either recreate it in /BookList/selectors.js (surely wrong?) OR import it from /App/selectors (will it always be the EXACT same selector..? no.). Both these appraoches seem sub-optimal to me.
The prime example of this use case is Authentication (ah... auth we do love to hate you) as it is a VERY common "side-effect" model. We often need to access /Auth sagas, actions and selectors all over the app. We may have the containers /PasswordRecover, /PasswordReset, /Login, /Signup .... Actually in our app our /Auth contianer has no actual component at all!
/src
/contianers
/Auth
actions.js
constants.js
reducer.js
selectors.js
sagas.js
Simply containing all the Redux collaterals for the various and often un-related auth containers mentioned above.
I personally use the ducks-modular-redux proposal.
It's not the "official" recommended way but it works great for me. Each "duck" contains a actionTypes.js, actionCreators.js, reducers.js, sagas.js and selectors.js files. There is no dependency to other ducks in these files to avoid cyclic dependency or duck circle, each "duck" contains only the logic that it have to managed.
Then, at the root I have a components and a containers folders and some root files :
components/ folder contains all the pure components of my app
containers/ folder contains containers created from pure components above. When a container need a specific selector involving many "ducks", I write it in the same file where I wrote the <Container/> component since it is relative to this specific container. If the selector is shared accros multiple containers, I create it in a separate file (or in a HoC that provides these props).
rootReducers.js : simply exposes the root reducers by combining all reducers
rootSelectors.js exposes the root selector for each slice of state, for example in your case you could have something like :
/* let's consider this state shape
state = {
books: {
items: { // id ordered book items
...
}
},
taxonomies: {
items: { // id ordered taxonomy items
...
}
}
}
*/
export const getBooksRoot = (state) => state.books
export const getTaxonomiesRoot = (state) => state.taxonomies
It let us "hide" the state shape inside each ducks selectors.js file. Since each selector receive the whole state inside your ducks you simply have to import the corresponding rootSelector inside your selector.js files.
rootSagas.js compose all the sagas inside your ducks and manage complex flow involving many "ducks".
So in your case, the structure could be :
components/
containers/
ducks/
Books/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
Taxonomies/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
rootSelectors.js
rootReducers.js
rootSagas.js
When my "ducks" are small enough, I often skip the folder creation and directly write a ducks/Books.js or a ducks/Taxonomies.js file with all these 5 files (actionTypes.js, actionCreators.js, reducers.js, selectors.js, sagas.js) merged together.