So, I am passing store thru ApolloProvider
<ApolloProvider store={store} client={apolloClient}>
Now, ApolloProvider is putting it, the store, in client. But its putting the store object, not the actual reducer object. So, I am getting the following, which seems odd to me..
client
---> store
-----------> getState
-----------> dispatch
but no "state"? I mean, sure if I would call getState(), but that seems quite different from when I usually do redux without Apollo. I get my "state tree"... here, I am getting it...
What is the "normal" or "best practices" when using redux w/ graphql?
It seems excessive to access a state prop like so:
if (this.props.client.state.getState().whatever.foo)
Use queries to local state and React Hooks like:
import gql from 'graphql-tag';
import { useQuery } from '#apollo/react-hooks';
//...
export default () => {
const { data } = useQuery(gql('query GetTableFilter { selectedMall #client }'));
// ... do something with data.selectedMall
Related
This is a topic that's been discussed a lot through github issues and by now I've noticed two main opinions: It's not possible or it should not be done at all.
The argument for both sides is that redux is not meant for it, that the .replaceReducer function is only meant for the purposes of hot-reloading (even though redux itself mentions it as a possibility for code-splitting).
The goal
Anyway, what I would like to achieve (ideally) is a system that only sends the relevant slices and relevant redux code for a specific route in NextJs. And (even more ideally) when navigating between pages the store should just get extended and not re-created.
My initial approach
My first idea was to implement a recipe from the link above, attaching and exposing the injectReducer function onto my store during the store setup:
const store = configureStore({
reducer: {
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkqApi.middleware)
});
store.dynamicReducers = {};
store.injectDynamicReducer = (name, reducer) => {
if (Object.keys(store.dynamicReducers).includes(name)) {
return;
}
store.dynamicReducers[name] = reducer;
store.replaceReducer(
combineReducers({
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer,
...store.dynamicReducers
})
);
};
const makeStore = () => store;
export const wrapper = createWrapper(makeStore);
export const injectReducer = (sliceName, reducer) => store.injectDynamicReducer(sliceName, reducer);
So basically every page would have a globalsSlice, containing the user info and some other global data, and Redux Toolkit Query API slice (which would then be code-split using RTKQ injectEndpoints functionality).
With this setup, each page that wants to inject its own custom slice (reducer) would do something like this:
const SomePage = () => {
const someData = useSelector(somePageSliceSelectors.selectSomeData);
return (
<Fragment>
<Head>
<title>Some Page</title>
</Head>
</Fragment>
)
};
export default SomeRoute;
injectReducer('somePageSlice', somePageReducer);
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
// Whatever necessary logic we need
});
Initially this seemed to have worked fine, but then when I realized that next-redux-wrapper works by calling the makeStore factory on every request, and I'm manipulating and mutating a global store object, there has to be something wrong with this, ie a race condition that I haven't been able to cause by testing. Also another problem occurres when using Redux Toolkit Query. For example, if I need to get a cookie from the original request (the one that nextjs receives) and then re-send it to another API endpoint that is handled by redux toolkit query, I would need to extract the cookie from the request context, to which I don't have access unless I do something like this:
export const makeStore = (ctx) => {
return configureStore({
reducer: ...,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: ctx,
},
}).concat(...),
});
};
which further implies that I should definitely not be mutating the global store object.
So then I thought alright, instead of manipulating the global store I could try doing it in GSSP:
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
store.injectDynamicReducer('somePageSlice', somePageReducer);
});
But no luck here, the slice does not get loaded and the state does not get constructed. It is my guess that the Provider in the _app gets rendered before this, but I'm not sure.
In conclusion, I'd like to know whether anyone has tried and succeeded in implementing redux code splitting using RTK, RTKQ and NextJs. Also, I would like to ask an addition question: Is it necessary? What I mean by this is, if I were to not code-split at all, and send all slices on every request, how performance impactful would this be? Also, since I'm not sure exactly how the NextJs bundler works and how code chunking is done: If a certain page receives a slice it doesn't use at all, will it only receive its initial state or all of its logic (all the selectors, reducers and actions)? If not then maybe this isn't so bad, since initial states are just empty objects.
I hope I've presented the problem clearly enough, as it is a very complex problem, but feel free to ask follow up questions if something doesn't make sense.
Thanks in advance.
How can I get values from local storage in next.js?When i give localStorage.getItem() in console,it is prnting the values.But when I assign this to a variable it is giving LocalStorage is not defined error.I have also added redux-persist in my localstorage
localStorage.getItem('id')
Local Storage is a Web API native to modern web browsers. It allows websites/apps to store data in the browser, making that data available in future browser sessions.
There are two React lifecycle methods we can use in our component to save/update the browsers localStorage when the state changes:
componentDidMount()
componentDidUpdate()
componentDidMount will run once your component has become available and loaded into the browser. This is when we gain access to localStorage. Since localStorage doesn’t reside in Node.js/Next.js since there is no window object, we will have to wait until the component has mounted before checking localStorage for any data. So If you want to assign the local storage value into a variable, please do this inside the componentDidMount method.
componentDidMount() {
const data = localStorage.getItem('id')
console.log(data);
if(data) {
//here you can set your state if it is necessary
}
}
And If we want to update our local storage value through the state we can easily update the localStorage value with our changes value by using componentDidUpdate. This method gets run each time the state changes so we can simply replace the data in localStorage with our new state.
componentDidUpdate() {
localStorage.setItem('id', JSON.stringify(this.state))
}
localStorage is a property of object window. It belongs to the browser, not next.js nor React, and accessing localStorage is not possible until React component has been mounted. So you need to ensure that your React app is mounted before calling localStorage, e.g. calling localStorage.getItem inside componentDidMount.
When working with a framework like Next.js that executes code on the server side, using localStorage produces an error like "localStorage is not defined" or "window is not defined"
To fix this, check to see if window is defined so that the code will run only when it's available.
This is a great article that explains more: https://blog.logrocket.com/using-localstorage-react-hooks/
See the section called, "Problems accessing localStorage for an SSR application"
You can create a file called "useLocalStorage.tsx" or whatever, and it would contain something like this:
import { useState, useEffect } from "react";
function getStorageValue(key, defaultValue) {
// getting stored value
if (typeof window !== 'undefined') {
const saved = localStorage.getItem(key);
return saved || defaultValue;
}
}
export const useLocalStorage = (key, defaultValue) => {
const [value, setValue] = useState(() => {
return getStorageValue(key, defaultValue);
});
useEffect(() => {
// storing input name
localStorage.setItem(key, value);
}, [key, value]);
return [value, setValue];
};
Then you can just import it into the file you want to use it in like this:
import { useLocalStorage } from './useLocalStorage'
Then you can call it to get the "id" from localStorage:
const [id, set_id] = useLocalStorage("id", "");
First think to take a note is, localStorage has nothing to do with next.js or redux-persist. localStorage is the internal window object and can be directly accessible without any definition.
I think you are trying to access the localStorage before it is being set, so you get that error.
Simple solution to this is to use Conditional (ternary) operator
,
const id = localStorage.getItem('id') ? localStorage.getItem('id') : "set your own default value";
console.log(id);
I am new to react native with redux. I am trying to figure out how all the pieces in react-native redux integration. The one thing giving me trouble is understanding the difference types and selector give me more details.
MapStateToProps -> has his name say, you can map state objects to props. Example:
You have a store like this:
{
name:'paul',
surname:'watson'
}
Then you need show in your component the name, so in your container you can access to this data stored in store with mapstatetoprops, like this:
const mapStateToProps = (state, ownProps) => ({
myname: state.name,
})
MapDispatchToProps -> thats when you need dispatch an action, you map an action to a prop to you can use in your component
You have an action like:
const setMyName = payload => ({
type: SET_MY_NAME,
payload,
})
then you need update your name in store when user click something throw this action, so you can map this action in a prop to call like updateName('pepito') with mapDispatchToProps, like this:
const mapDispatchToProps = {
updateName: setMyName,
}
Selectors -> it's just an abstraction code, selectors make your life more easy.
Selectors are functions that take Redux state as an argument and return some data to pass to the component, like this:
const getDataType = state => state.editor.dataType;
Thats a basic concepts, you should read oficial document and search, in internet have a lot of articles about this.
My work is based on these directions: https://redux-observable.js.org/docs/recipes/AddingNewEpicsAsynchronously.html
So I am having an issue with getting this test to execute the epics, am I missing something? In any case the code explains what is going on...
I would expect my call of to store.dispatch({type:'GO'}) to chain all of my epics. I see my reducer is wired up but the Epics do not get called.
fyi I need this to work to test what I think is a bug. In my actual application when I dynamically load a new epic using epicLoader$.next(<someepic>) the dynamically loaded epic is now called twice... but I can't prove that until this test works.
As a workaround I have manually added the epics that I wanted to add dynamically and it is working fine but this isn't possible past my POC phase. I'll need to load the epics from separate files that will be pushed to the app.
Help me Obi wan...
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/do';
import { Observable } from 'rxjs/Observable';
import {combineReducers, createStore, applyMiddleware} from 'redux';
import { createEpicMiddleware,combineEpics } from 'redux-observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
test('dynamicly loading epic calls it twice',done => {
let epic1 = (action$)=>action$
.ofType('GO')
.do((action)=>console.log('epic1',action))
.mapTo({type:'EPIC1'});
let epic2 = (action$,{epicLoader$})=>action$
.ofType('EPIC1')
.do((action)=>console.log('epic2',action))
.do(()=>epicLoader$.next(epic3))
.do(()=>epicLoader$.next(epic4))
.mapTo({type:'EPIC2'});
let epic3 = (action$)=>action$
.ofType('EPIC2')
.do((action)=>console.log('epic3',action))
.mapTo({type:'EPIC3'});
let epic4 = (action$)=>action$
.ofType('EPIC2')
.do((action)=>console.log('epic4',action))
.do(()=>done())
.mapTo({type:'EPIC4'});
const epic$ = new BehaviorSubject(combineEpics({epic1,epic2}));
const rootEpic = (action$, store, args) =>
epic$.mergeMap(epic =>
epic(action$, store, args)
);
const epicMiddleware = createEpicMiddleware(rootEpic,
{ dependencies: {
epicLoader$: epic$,
}});
const reducer =(state = {},action)=>{
console.log('reducer',action);
return state;
};
const store = createStore(
combineReducers({
reducer
}),
applyMiddleware(epicMiddleware)
);
//start up the chain of events.
store.dispatch({type:'GO'});
});
I think I see two issues with the provided code:
Providing object to combineEpics instead of just arguments
You're passing an object to combineEpics, but it's supposed to just be plain old arguments. This is different than combineReducers
// bad
const epic$ = new BehaviorSubject(combineEpics({epic1,epic2}));
// good
const epic$ = new BehaviorSubject(combineEpics(epic1,epic2));
Support for passing an object has been discussed here: https://github.com/redux-observable/redux-observable/issues/58. It hasn't been supported yet because the keys would be meaningless, unlike with combineReducers.
This actually does throw an error when the rootEpic is subscribed to, but it's unfortunately swallowed because of a outstanding bug in rxjs
destructuring of store instead of the third dependencies argument
// bad
let epic2 = (action$,{epicLoader$})=>action$
// good
let epic2 = (action$,store,{epicLoader$})=>action$
Once these are fixed, it appears to work as desired:
I did notice that epic4 is listening for EPIC2, which you might have actually meant to listen for EPIC3 maybe?
How is this code processed in relation to the way it is written in the redux-devtools documentation?
https://github.com/auth0-blog/redux-auth/blob/master/index.js#L10-L12
let createStoreWithMiddleware = applyMiddleware(thunkMiddleware, api)(createStore)
let store = createStoreWithMiddleware(quotesApp)
I'm not sure how to rewrite this to include DevTools but I did find this GitHub link including a pull request to include DevTools, which I've since gotten working. However, I still do not understand how it is being applied and what's going on with the let something = function(param1,param2)(function). I know that with that syntax the return value of applyMiddleware is being sent to createStore, but the createStore syntax takes a reducer, initialState, and an enhancer. How is this being applied here?
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" preserveScrollTop={false} />
</DockMonitor>
)
let createStoreWithMiddleware = applyMiddleware(thunkMiddleware, api)(createStore)
let store = createStoreWithMiddleware(quotesApp, DevTools.instrument())
The syntax confuses me as opposed to the following syntax from the redux-devtools documentation.
What happens to initialState? In the example there is no reference to initialState anywhere.
The store enhancer definition signature looks roughly like this (snipped from the definition of `applyMiddleware):
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
// snip actual enhancer logic
return {
...store,
dispatch
}
}
}
So, the enhancer definition actually returns a function that takes a reference to the createStore function itself.
Unfortunately, somehow people seem to have copied that very functional-oriented calling pattern from somewhere, which is really hard to understand. Not sure if it was in an earlier version of the docs, or what. Note that that particular usage pattern doesn't allow defining initialState (or, as it's about to be renamed, preloadedState).
So yes, the current definition pattern, and the one that I think is much more readable, is:
const middlewares = [thunk, myMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
const enhancers = compose(middlewareEnhancer, someOtherEnhancer);
const store = createStore(reducer, preloadedState, enhancers);