So, I'm checking out redux codebase,
To create a store, we're calling the createStore function exposed by redux,
createStore(ourReducer,{}, applyMiddleware(someMiddleware));
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
} ...
In createStore function above, enhancer is called in this part, which is also the applyMiddleware function that I passed on the first line...
enhancer(createStore)(reducer, preloadedState)
But when I checked the applyMiddleware function, there are 3 parameters
(reducer, preloadedState, enhancer)...
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
...
It's basically always undefined right? I'm just curious why it's there or if I'm missing something.
enhancer(createStore)(reducer, preloadedState)
Yeah, this one comes up (surprisingly) frequently. It's a holdover from the "old-style" way of using createStore. See Redux issue #2128 for discussion and history as to why that argument exists.
Related
I'm trying to use NextJS with Apollo, but I can't understand where should I insert my dataSources, so I'd be able to access it through context in my resolvers (as stated in the docs).
Here's my apollo.ts (the same from NextJS with-typescript-graphql example):
import { IncomingMessage, ServerResponse } from 'http'
import { useMemo } from 'react'
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from '#apollo/client'
import resolvers from './resolvers'
import typeDefs from './schema'
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined
export type ResolverContext = {
req?: IncomingMessage
res?: ServerResponse
}
function createIsomorphLink(context: ResolverContext = {}) {
if (typeof window === 'undefined') {
const { SchemaLink } = require('#apollo/client/link/schema')
const { makeExecutableSchema } = require('#graphql-tools/schema')
const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
return new SchemaLink({ schema, context })
} else {
const { HttpLink } = require('#apollo/client')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
}
function createApolloClient(context?: ResolverContext) {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context),
cache: new InMemoryCache(),
})
}
export function initializeApollo(
initialState: any = null,
// Pages with Next.js data fetching methods, like `getStaticProps`, can send
// a custom context which will be used by `SchemaLink` to server render pages
context?: ResolverContext
) {
const _apolloClient = apolloClient ?? createApolloClient(context)
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState: any) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
I tried to put it alonside with typeDefs in the parameters for makeExecutable, but it's still undefined when I try to use it.
My confusion is because this setup doesn't use the new ApolloServer() constructor, as the example in the docs and I can't find some docs which relates to my setup.
Warning: You have opted-out of Automatic Static Optimization due to
getInitialProps in pages/_app. This does not opt-out pages with
getStaticProps
I tried different options, but I can’t achieve static page generation, even if I take out the functionality I need from getInitialProps from_app, then wrapping it in withRedux, I still get it in the end. I tried with this - https://github.com/kirill-konshin/next-redux-wrapper - but could not get the result, I assume that this is because of the redux-saga and the whole application will use getInitialProps
/store.js
const ReduxStore = (initialState /*, options */) => {
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware)
// other store enhancers if any
);
const store = createStore(
rootReducer,
initialState,
enhancer
);
store.runSaga = () => {
// Avoid running twice
if (store.saga) return;
store.saga = sagaMiddleware.run(saga);
};
store.stopSaga = async () => {
// Avoid running twice
if (!store.saga) return;
store.dispatch(END);
await store.saga.done;
store.saga = null;
// log('Stop Sagas');
};
store.execSagaTasks = async (ctx, tasks) => {
// run saga
await store.runSaga();
// dispatch saga tasks
tasks(store.dispatch);
// Stop running and wait for the tasks to be done
await store.stopSaga();
// Re-run on client side
if (!ctx.isServer) {
store.runSaga();
}
};
store.runSaga();
return store;
};
export default ReduxStore;
//_app.js
import { Provider } from 'react-redux';
import withRedux from 'next-redux-wrapper';
import App from 'next/app';
class MyApp extends App {
render() {
const {Component, pageProps, store} = this.props;
return <Provider store={store}>
<Component {...pageProps}/>
</Provider>;
}
}
export default withRedux(makeStore)(MyApp);
Has anyone experienced this or have any ideas? I will be grateful for any help
My redux store is fairly large; Redux Devtools suggests sanitizing my larger objects to improve performance.
I've followed the docs here: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#excessive-use-of-memory-and-cpu
I've tried a number of combinations here, but none have given me the output I expect.
The current version, seen below, results in state being returned as a function, not an object. I know I'm doing something wrong, but I'm not sure what. Any guidance would be deeply appreciated.
Here's my store.js:
'use strict'
// libraries
import { createStore, applyMiddleware, compose } from 'redux'
// middleware
import logger from 'redux-logger'
import thunk from 'redux-thunk'
// reducers
import reducer from './reducers'
const withLogger = false ? (thunk, logger) : thunk
const isProd = process.env.NODE_ENV === 'production'
const middleware = isProd ? thunk : withLogger
const composeEnhancers = isProd
? compose
: window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(middleware)),
// The addition of this code breaks my store
window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__({
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
})
// End breaking code
)
Second try
I've made a couple of updates, and can now see the sanitizers' effect in devtools - depending on placement in my createStore function. Unfortunately this changes my composeEnhancers behavior (fires, or does doesn't fire depending on placement)
// middleware with or without logger
const middlewareEnhancer =
true || ENV === 'production' // change to false to prevent logger output
? applyMiddleware(thunk, logger)
: applyMiddleware(thunk)
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
// compose
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(middlewareEnhancer) ||
compose(middlewareEnhancer)
const store = createStore(
// createStore signature > reducer, preLoadedState, enhancer
rootReducer,
// devtools extension works when I place it here per the examples in docs
// BUT composed enhancers fail
// Obviously, since the format wouldn't match the createStore signature
// I have no idea how `__REDUX_DEVTOOLS_EXTENSION__` should be used in conjunction with composeEnhancers
undefined,
composeEnhancers,
// devtools extension fails when placed here
// composed enhancers run
window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__({
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
})
)
Finally, persistence ftw!
I hate giving up; figured it out after rereading all the documentation posted by #markerikson. Always read the docs :'(
This may not be of use to anyone using configureStore and Redux Toolkit, but I'm documenting it regardless.
My big mistake was that actionSanitizer and stateSanitizer are Devtools Extension options, and should be added as such. Feel a fool, but at least I won't forget it.
The only thing left to do is implement redux-devtools-extension to avoid using window.__SOMEFUNC__ as suggested by markerikson.
The actual solution:
'use strict'
// libraries
import { createStore, applyMiddleware, compose } from 'redux'
// middleware
import logger from 'redux-logger'
import thunk from 'redux-thunk'
// reducers
import rootReducer from './reducers'
// middleware with or without logger
const middlewareEnhancer =
true || ENV === 'production' // change to false to prevent logger output
? applyMiddleware(thunk, logger)
: applyMiddleware(thunk)
// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
!!action.id
&& action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
? { ...action, data: '<<LONG_BLOB>>' }
: action
// compose
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// add sanitizers here as devtools options
// see https://github.com/zalmoxisus/redux-devtools-extension/tree/94f7e53800f4665bddc9b7438c5cc75cfb4547cc#12-advanced-store-setup
// section 1.2
actionSanitizer,
stateSanitizer: state =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
}) || compose
const enhancer = composeEnhancers(middlewareEnhancer)
const store = createStore(rootReducer, undefined, enhancer)
export default store
As a first observation, this line seems wrong:
const withLogger = false ? (thunk, logger) : thunk
I'd strongly encourage you to first switch over to using the configureStore function from our official Redux Toolkit package, which handles the store setup process for you. From there, you can still pass DevTools configuration options to configureStore() if desired.
Only to complete the answer for those using the redux toolkit, here is an example entry that works well for me.
const devToolsConfiguration = {
actionSanitizer: (action) => {
switch (true) {
case action.type.includes(RESOLVED):
return typeof action.payload !== 'undefined'
? { ...action, payload: '<<LONG_BLOB>>' }
: { ...action, results: '<<LONG_BLOB>>' };
/* ... more entries */
default:
return action;
}
},
stateSanitizer: (state) =>
state.data?.matrix
? { ...state, data: { ...state.data, matrix: '<<LONG_BLOB>>' } }
: state,
};
I then reference the configuration in the toolkit's configureStore function:
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: false,
serializableCheck: false,
immutableCheck: false,
}).prepend(middlewares),
preloadedState: initialState,
devTools: devToolsConfiguration, // <<< here
});
My question is why middlewareAPI can't use :
const middlewareAPI = {
getState: store.getState,
dispatch: dispatch
}
to replace the definition in the source code as below:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args) // why not just use `dispatch: dispatch`
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
Anyone can tell me the difference ? Thanks.
It's a somewhat complicated combination of JS variable scoping/hosting, and needing to ensure that the passed-in dispatch method actually points back to the start of the middleware chain.
Please see the newly-added (and not yet published) Redux FAQ entry on why applyMiddleware uses a closure for more details.
Can anyone offer a tip on filtering actions from Redux-Logger? I'm attempting to filter ##redux-form/BLUR and the like coming from Redux Form.
Based upon the Redux Logger Recipe here https://github.com/evgenyrodionov/redux-logger#log-everything-except-actions-with-certain-type
Log everything except actions with certain type
createLogger({
predicate: (getState, action) => action.type !== AUTH_REMOVE_TOKEN
});
Based upon the recipe cited above I would expect to provide a statement with the expression formatted similarly and to return false. I am logging successfully passing the collapsed option, so I wouldn't suspect I'm doing anything wrong in applyMiddlewear().
predicate:(getState, action) => action.type !== ##redux-form/FOCUS || ##redux-form/BLUR || ##redux-form/FOCUS
From the creator of Redux-Logger:
predicate:(getState, action) => !action.type.includes('##redux-form')
Full Example:
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger';
const logger = createLogger({
predicate: (getState, action) => !action.type.includes('##redux-form'),
//...other options
});
const store = createStore(
reducer,
applyMiddleware(logger)
);