Is reducer and global variable are the same? - react-redux

Can I get the reducer state in Browser console like accessing declared javascript variables.?

You can use a middleware to store the redux state in a global variable.
import { createStore, applyMiddleware } from 'redux';
const stateInGlobal = store => next => action => {
const result = next(action);
window.reduxState = store.getState();
return result;
};
const store = createStore(reducer, undefined, applyMiddleware(stateInGlobal))
Now you can access to redux state through the variable reduxState.

Related

Apollo Client reactive variable state is not kept in cache after refreshing the page

I have Apollo Client running on my React app, and trying to keep authentication info in a Reactive Variable using useReactiveVar. Everything works in the dummy function when I first set the variable, however it resets the state after refreshing the app.
Here's my cache.js:
import { InMemoryCache, makeVar } from "#apollo/client";
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isLoggedIn: {
read() {
return isLoggedInVar();
},
},
},
},
},
});
export const isLoggedInVar = makeVar();
export default cache;
Here's the component that reads the variable and renders different elements based on its state:
import React from "react";
import { useReactiveVar, useMutation } from "#apollo/client";
import MainButton from "../common/MainButton";
import { isLoggedInVar, userAddressVar } from "../../cache";
import { CREATE_OR_GET_USER } from "../../mutations/User";
const Profile = () => {
const isLoggedIn = useReactiveVar(isLoggedInVar);
const [createOrGetUser] = useMutation(CREATE_OR_GET_USER);
const handleCreateOrGetUser = () => {
const loginInput = {
address: 'text',
};
createOrGetUser({
variables: {
loginInput: loginInput,
},
}).then((res) => {
isLoggedInVar(true);
});
};
const profileComponent = isLoggedIn ? (
<div>Logged In</div>
) : (
<div onClick={handleCreateOrGetUser} className="profile-image"></div>
);
return (
<div className="profile-container">
{profileComponent}
</div>
);
};
export default Profile;
This component gets re-rendered properly when I invoke handleCreateOrGetUser, however, when I refresh the page, it resets the isLoggedInVar variable.
What would be the proper way to use Reactive Variables here to persist the cache?
It's not currently achievable using Apollo API according to their documentation.
There is currently no built-in API for persisting reactive variables,
but you can write variable values to localStorage (or another store)
whenever they're modified, and initialize those variables with their
stored value (if any) on app load.
There is a PR for that. https://github.com/apollographql/apollo-client/pull/7148

React-Redux re-render on dispatch inside HOC not working

I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.
I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.
My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.
The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.
My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.
It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?
The HomePage HOC:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'
// unconnected stateless react component
const HomePage = (props) => (
<div>
{ !props.auth
? <AnonymousPartialPage />
: <AuthenticatedPartialPage /> }
</div>
);
const mapStateToProps = (state) => {
const store = createStore(
combineReducers({
auth: authReducer
})
);
// When the user logs in, in the Anonymous component, the local storage is set with the response
// of the API when the log in attempt was successful.
const storageAuth = JSON.parse(localStorage.getItem('auth'));
if(storageAuth !== null) {
// Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
store.dispatch(logOutOfApi());
// Make sure the auth info in local storage is contained in the state.auth object.
store.dispatch(loggedIntoApi(...storageAuth))
}
return {
auth: state.auth && state.auth.jwt && storageAuth === null
? state.auth
: storageAuth
};
}
export default connect(mapStateToProps)(HomePage);
with the Anonymous LOC being:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';
export class AnonymousPartialPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
const loginData = { ... };
// This is where I thought the problem initially occurred as I
// would get an error that `this.props` was undefined in the final
// then` of the `fetch`. After doing this, however, the error went
// away and I can see that `props.dispatch is no longer undefined
// when using it. Now though, nothing happens.
const props = this.props;
fetch('https://.../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data && data.jwt) {
props.dispatch(loggedIntoApi(data));
localStorage.setItem('auth', JSON.stringify(data));
}
// else show an error on screen
});
};
render() {
return (
<div>
... onSubmit gets called successfully somewhere in here ...
</div>
);
}
}
export default connect()(AnonymousPartialPage);
the action:
// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
type: 'LOGGED_INTO_API',
auth: auth_token
});
// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
type: 'LOG_OUT_OF_API'
});
and finally the reducer:
const authDefaultState = { };
export default (state = authDefaultState, action) => {
switch (action.type) {
case 'LOGGED_INTO_API':
// SOLUTION : changed this line "return action.auth;" to this:
return { ...action.auth, time_stamp: new Date().getTime() }
case 'LOG_OUT_OF_API':
return { auth: authDefaultState };
default:
return state;
}
};
My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().
Essentially:
x = {
foo:bar
}
x.foo = "baz"
dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!
Instead I created a new object:
x = {
foo:"bar"
}
y = JSON.parse(JSON.stringify(x)) // creates an entirely new object
dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender
// BE CAREFUL OF THE FOLLOWING!
y = x
dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender
Hope this helps!

Passing store to switchMap function in observable epic

I need to pass store value to switchMap function on epic but seems like only action is passed and store is 0.
export const addVideoEpic = (action$, store) =>
action$.ofType(videoTypes.ADD_VIDEO_REQUEST)
.filter(() => of(store.getState()))
.switchMap((action, store) => {
const data = {
url: store.url
};
console.log(store) // 0
return from(addVideo(data))
});
How to pass store value down to switchMap
In order to pass a store into the epic, your epic must be aware of the existence of a store. In your store creation class, please make sure that you have something like:
const epicMiddleware = createEpicMiddleware();
const persistedReducer = persistReducer(persistConfig, rootReducer as Reducer)
export const store = createStore(
persistedReducer,
applyMiddleware(epicMiddleware)
);
epicMiddleware.run(rootEpic);
And assuming that your version of redux-observable is ^1.0.0 or newer and rxjs is ^6.0.0 or newer, you can access the store and whatever state it stores as such:
export function fetchRandomStuffEpic(
action$: ActionsObservable<RandomAction>,
store$: StateObservable<RootState>
) {
return action$.pipe(
ofType(RANDOM),
switchMap(action => {
// store.getState() no longer works in the newer versions
// and has to accessed as a value property with store$
const { authToken } = store$.value.userData;
// whatever code that follows
})
);
}

Using Redux-Persist with Baqend React/Redux Starter

Does anyone know how to add redux-persist https://github.com/rt2zz/redux-persist
to the store from the starter kit: https://www.baqend.com/guide/starter-kits/react/
import { applyMiddleware, combineReducers } from 'redux'
import { createStoreWithBaqend, baqendReducer } from 'redux-baqend'
import middlewares from '../middleware'
import reducers from '../reducers'
import { db } from 'baqend'
export default (initialState = {}) => {
const reducer = combineReducers({
baqend: baqendReducer,
...reducers
})
const middleware = applyMiddleware(
...middlewares
)
return createStoreWithBaqend(
db.connect('remarkable-apple-XX', true),
reducer,
initialState,
middleware
)
}
I haven't tried this myself yet, but the documentation from redux-persist looks like you just add the autoRehydrate and wrap the created store in the persistStore method. The createStoreWithBaqend method is basically the same like the normal createStore method, but adds some baqend specific stuff to your store.
I would try it like this:
export default (initialState = {}) => {
const reducer = combineReducers({
baqend: baqendReducer,
...reducers
})
const middleware = applyMiddleware(
...middlewares
)
const store = createStoreWithBaqend(
db.connect('remarkable-apple-XX', true),
reducer,
initialState,
compose(
middleware,
autoRehydrate()
)
)
return persistStore(store)
}
Remember to import compose from the redux library. Hope this helps.

Redux + storybook throws warning about changing store on the fly even with module.hot implemtended

I'm using storybook and I want to add redux as decorator.
Whe running storybook, I got warning in console:
<Provider> does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
It's my code for config storybook:
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */
import React from 'react';
import { configure, storiesOf } from '#storybook/react';
import { Provider as ReduxProvider } from 'react-redux';
import forEach from 'lodash/forEach';
import unset from 'lodash/unset';
import Provider from 'components/Provider';
import initStore from 'utils/initStore';
import messages from '../lang/en.json';
const req = require.context('../components', true, /_stories\.js$/);
const ProviderDecorator = (storyFn) => {
const TheProvider = Provider(() => storyFn());
return (
<ReduxProvider store={initStore()}>
<TheProvider key={Math.random()} now={1499149917064} locale="en" messages={messages} />
</ReduxProvider>
);
}
function loadStories() {
req.keys().forEach((filename) => {
const data = req(filename);
if (data.Component !== undefined && data.name !== undefined && data.stories !== undefined) {
const Component = data.Component;
const stories = storiesOf(data.name, module);
stories.addDecorator(ProviderDecorator);
let decorator = data.stories.__decorator;
if (data.stories.__decorator !== undefined) {
stories.addDecorator((storyFn) => data.stories.__decorator(storyFn()));
}
forEach(data.stories, (el, key) => {
if (key.indexOf('__') !== 0) {
stories.add(key, () => (
<Component {...el} />
));
}
});
} else {
console.error(`Missing test data for ${filename}!`)
}
});
}
configure(loadStories, module);
and initStore file:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunkMiddleware from 'redux-thunk';
import { persistStore, autoRehydrate } from 'redux-persist';
import reducers from 'containers/redux/reducers';
export default () => {
const store = createStore(
reducers,
{},
composeWithDevTools(applyMiddleware(thunkMiddleware), autoRehydrate()),
);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../containers/redux/reducers', () => {
const nextReducers = require('../containers/redux/reducers'); // eslint-disable-line global-require
store.replaceReducer(nextReducers);
});
}
persistStore(store);
return store;
};
So as you can see I followed instructions from link in warning. What have I done wrong and how can I remove this warning? I know it won't show on production server, but it's pretty annoying in dev mode. :/
The reason this is happening has to do with the way Storybook hot-loads.
When you change your story, that module is hot-loaded, meaning that the code inside it is executed again.
Since you're using a store creator function and not a store instance from another module, the actual store object that is being passed to ReduxProvider on hot-load is new every time.
However, the React tree that is re-constructed is for the most part identical, meaning that the ReduxProvider instance is re-rendered with new props instead of being re-created.
Essentially, this is changing its store on the fly.
The solve is to make sure that ReduxProvider instance is new, too, on hot-load. This is easily solved by passing it a unique key prop, e.g.:
const ProviderDecorator = (storyFn) => {
const TheProvider = Provider(() => storyFn());
return (
<ReduxProvider key={Math.random()} store={initStore()}>
<TheProvider key={Math.random()} now={1499149917064} locale="en" messages={messages} />
</ReduxProvider>
);
}
From React Keys:
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.

Resources