I'm looking for a way to dispatch an action once I know multiple actions have been dispatched. I know that I can do this now by storing some variables in store. But is there a better way of doing it (Saga, Discoverables) etc. I tried to go through their documentation, but couldn't understand if it can be used for this purpose.
E.g in the below code, I want to dispatch some action in middleware when I know that 2 required actions have dispatched
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {createStore, applyMiddleware} from 'redux'
import {Provider} from 'react-redux'
const dummyReducer = (state=null,action) => {
return state
}
const defaultAction1 = { type: 'DEFAULT_ACTION1'}
const requiredAction1 = { type: 'REQUIRED_ACTION1'}
const requiredAction2 = { type: 'REQUIRED_ACTION2'}
const middle = (store) => (next) => (action) => {
if(action.type === 'REQUIRED_ACTION1')
console.log('Required actions satisfied')
next(action)
}
const store = createStore(dummyReducer, applyMiddleware(middle))
store.dispatch(defaultAction1)
//Dispatch an action after 2 secs
setTimeout(store.dispatch.bind(null,requiredAction1),2000)
setTimeout(store.dispatch.bind(null,requiredAction2),4000)
class DummyComponent extends Component{
render(){
return(<h1> nothing to see here </h1>)
}
}
ReactDOM.render(<Provider store={store}>
<DummyComponent />
</Provider>,
document.getElementById('root')
);
You can do this in saga simply by:
yield all([
take(REQUIRED_ACTION1),
take(REQUIRED_ACTION2),
])
yield put({type: FINAL_ACTION})
Actions must be self-explanatory and complete, it's a good practice to dispatch actions with all the needed info in their payloads.
Another reason that you should always try is dispatching different actions based on their purposes and take advantage of Redux tooling to debug your application properly. Redux updates the states synchronously and this is actually an advantage of its design. That said you know when one action goes through all the reducers you will have the proper state to reflect the changes on your UI part.
If you rely on two actions you somewhat can't rely on a single source of the truth which is your action1 and action2. And this forces you to wait for both of these in a middleware and it turns your actions into effects and effects are used in a different context.
Related
Every time when my redux state is updating my whole page is loading. Not exactly loading resources back from server but refreshing the components inside the page?
The following is my store code.
P.S : I am using multiple stores and combining them using combine reducers.
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";
import { User } from "./User";
import { Restaraunt } from "./Restaraunt";
import { Dish } from "./Dishes";
import { Cart } from "./Cart";
import { createForms } from "react-redux-form";
import { InitialFeedback, RegisterUserDetails, RegisterRestarauntDetails, addDishDetails } from "./forms";
export const storeConfig = () => {
const store = createStore(
combineReducers({
user: User,
restaraunts: Restaraunt,
dishes: Dish,
cart: Cart,
...createForms({ feedback: InitialFeedback }),
...createForms({ registeruser: RegisterUserDetails }),
...createForms({ registerres: RegisterRestarauntDetails }),
...createForms({ addDish: addDishDetails })
}),
applyMiddleware(thunk, logger)
);
return store;
};
I So I am using the cart that I mentioned a particular page. So when ever my cart is updating then my whole page is loading again.
This a bit too broad to be honest.
You should take a look at every component in your react app and make sure that they only re-render when needed.
Things you can do:
You can use shouldComponentUpdate lifecycle if you have class-based components that should update only when few of the props change.
You can use React.memo to memoize a component and prevent unnecessary rerender.
useCallback hook on functions for memorizing them.
This is one of the strangest things I have ever seen. It makes absolutely no sense to me. The short version is I have a Redux action creator function. If I import this function into this one particular component file, it makes every function imported from its file undefined.
So, let's start with the file filterInputModal.actions.js. This contains my Redux action functions, created using redux-starter-kit:
export const showAddCategoryModal = createAction('showAddCategoryModal');
That is the function I've been working with. Now, this function has long since been imported into my ManageVideoFilters.js component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showAddCategoryModal } from 'store/filterInputModal/filterInputModal.actions';
const ManageVideoFilters = (props) => {
/* Component logic */
};
/* PropTypes and mapStateToProps */
const mapDispatchToProps = (dispatch) => bindActionCreators({
showAddCategoryModal: () => showAddCategoryModal() // Done this way to avoid passing in a payload, since certain default event payloads cause Redux to print console errors
});
export default connect(mapStateToProps, mapDispatchToProps)(ManageVideoFilters);
So far so good. Before we go and break everything, let's take a look at my filterInputModal.reducer.js Redux reducer, also created using Redux Starter Kit:
import { createReducer } from 'redux-starter-kit';
import { showAddCategoryModal } from './filterInputModal.actions';
const initialState = {}; // The initial state for the reducer goes here
const handleShowAddCategoryModal = (state) => {
/* handle updating the state */
return state;
};
const actionMap = {
[showAddCategoryModal]: handleShowAddCategoryModal
};
export default createReducer(initialState, actionMap);
The action map uses the action creator functions toString() as the key, and then I provide my own functions to handle updating the state. Again, at this point, everything is perfect. We will come back to the reducer in a sec, first let's break things.
Now we're going to my VideFileEdit.js component. If we add the following line to this component, everything breaks:
import { showAddCategoryModal } from 'store/filterInputModal/filterInputModal.actions';
So, how does it break?
The import of the showAddCategoryModal function in filterInputModal.reducer.js now is undefined.
Because the reducer is using the functions as the keys to handle actions, the reducer is no longer able to handle the action properly and update the state.
It gets weirder though. Here are some of the weird behaviors I'm seeing.
If I import this action into any other component, everything is fine. The import in the reducer is unchanged.
The import of the function in both ManageVideoFilters.js and VideoFileEdit.js is fine.
So, what can I try next? This is really strange and doesn't make any sense to me. I've never seen this before.
As the commenter said, the problem was recursive imports. My filterInputModal.reducer.js exported some constants, which were imported into my filterInputModal.actions.js. The actions from filterInputModal.actions.js were then imported into filterInputModal.reducer.js. Hence the recursive import.
I moved the constants into a new file, filterInputModal.constants.js, and viola, problem solved.
I am quite new to React and Redux framework. What I am trying to do is reading a propery say zipCode from an API call on componentDidMount. And now I have to save it to redux store.
I am really confused reducers and actions etc. And not sure how and where to configure those.
Help much appreciated.
Redux can seem confusing at first glance, as I have experienced. But like everything, you will start to understand it quickly, especially if you practise making your own redux application(with the support of this community) and succeed by trial and error.
What you must understand about Redux are the following (I wont go into detail, ill keep it simple and basic, as well as relate it to your question):
1. Actions: These are as the name suggests, actions fired by the view/component. E.g. when you request to fetch the zipcode, this will be dispatched to the action.
componentDidMount(){
this.props.fetchZipCode(85001);
}
action.js
export const fetchZipCode= (zip) => ({
type: 'FETCH_ZIPCODE',
zip,
});
2. Reducers: These handle the actions request. As described in Redux docs 'The reducer is a pure function that takes the previous state and an action, and returns the next state'
reducer.js
const ZipcodeInfo = (state = 0, action) => {
switch (action.type) {
case 'REQUEST_ZIPCODE':
return {
...state
};
case 'RECEIVE_ZIPCODE':
return {
...state
zip: action.data.zipcode
};
case 'RECEIVE_ZIPCODE_FAILED':
return {
...state
}
default:
return state;
}
};
3. Store: This is what brings everything together. It holds application state, allows access to the store.
index.js (note: this is the root file of the application, so all states can be passed from the store to all components in your app)
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux'
import ZipcodeInfo from './reducers'
const store = createStore(ZipcodeInfo)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
4. Middleware: Now the middleware is not necessary, but if you are making async calls you will need to use a middleware to pass information to the API etc... There a many middlewars to use for redux.
Redux-saga middleware example
import {call, put, takeEvery, fork} from 'redux-saga/effects';
import * as service from '../Services/api';
import * as actions from '../actions/zipcode';
//The worker: will handle the action and terminate
function* fetchZipcode(action){
try{
yield put(actions.requestZipcode());
const [zipcode] = yield [call(service.getZipcodeAPI, action.zip)];
yield put(actions.receiveZipcode(zipcode));
} catch(e){
yield put(actions.receiveZipcodeFailed(e));
}
}
//The watcher: will watch for dispatched actions and fork a worker on every action
function* watchfetchZipcode(){
yield takeEvery('FETCH_ZIPCODE', fetchZipcode);
}
export default function* zipcodeSaga(){
yield fork(watchfetchZipcode);
}
I personally prefer Redux-Saga as a middleware, but it may be confusing as it uses generators(from es6) and more unknown terms.
Watch the below tutorial. It's a simple example to understand.
https://www.youtube.com/watch?v=OSSpVLpuVWA
I've got an actions file
import keyMirror from 'keymirror';
export const actionTypes = keyMirror({
OPEN_LIGHTBOX: null,
CLOSED_LIGHTBOX: null
});
export const openLightbox = (closable = false) => ({
type: 'OPEN_LIGHTBOX',
payload: {lightboxOpen: true, lightboxClosable: closable}
});
and a reducer, I'm not going to put the code in here because it doesn't seem necessary. It is at any rate working.
I have a react component that says
module.exports = connect((state) => {
console.log('state change'); // eslint-disable-line no-console
console.log(state); // eslint-disable-line no-console
return {
findShopDomainData: state.findShopDomain
}
})(Autocomplete);
I can register state changes inside of the react component, I get the console log statements.
But I also have stuff outside of react that needs to call these redux actions. So I import into a JS file.
import { createStore } from 'redux';
import { findShopDomain } from 'path to reducer file happens here'
import { openLightbox } from 'path to action file'
then I
let reduxStore = createStore(findShopDomain);
reduxStore.dispatch(openLightbox(true));
If I do that action in my vanilla js file I get to the reducer, but the state change is never registered in my connected React Component. I'm supposing it's because the connect function probably turns it into a new reducer, and in essence I actually have two reducers siting around that won't be affecting each other.
At any rate I would really like some way to have reduxStore.dispatch(openLightbox(true)); in my vanilla js be registered by my react component.
Using global scope
window.store = createStore(findShopDomain);
window.store.dispatch(openLightbox(true))
I am using react-redux and I am having a issue where I loose my redux state when the page is refreshed.
Now before I go further on that this is the scenario, which I may be messing up.
Question one: Can I connect to more than one layout?
I have a dashboard and a "app" layout. Both have separate layouts. I connect both in the same manner:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../actions/actionCreators';
function mapStateToProps(state) {
return {
profile: state.profile,
child: state.child,
}
}
function mapDispachToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
const LayoutApp = connect(mapStateToProps, mapDispachToProps) (DashboardLayout);
export default LayoutApp;
The dashboard connects just fine. I am able to hit reducers when i need to and update the store, however the dashboard links to the app for certain parts of data manipulation for you to play with. When it links I get the data in props as expected, however as soon as any page refreshes within the app layouts I loose the props being sent by maptoprops.
I have tried to combine into one master layout however that seems to have the same affect. I have also tried to save to state immediately when i first receive data but that seems to be lost as well upon refresh which makes me think it is resetting it.
Summary:
- DashboardLayout (connects to redux)
- AppLayout (connects to redux) however after a page refresh it looses props to the Applayout and needed data is gone.
Get to know redux-persist
https://github.com/rt2zz/redux-persist
You can install it using
npm i --save redux-persist
persistStore is the function that allows you to persist store.
import {persistStore} from 'redux-persist'
and autoRehydrate is the action that is performed whenever the state needs to be rehydrated
import {autoRehydrate} from 'redux-persist'
following is the structure that may be useful.
import {compose, applyMiddleware, createStore} from 'redux'
import {persistStore, autoRehydrate} from 'redux-persist'
// add `autoRehydrate` as an enhancer to your store (note: `autoRehydrate` is not a middleware)
const store = createStore(
reducer,
undefined,
compose(
applyMiddleware(...),
autoRehydrate()
)
)
// begin periodically persisting the store
persistStore(store)
and then for your reducer
import {REHYDRATE} from 'redux-persist/constants'
//...
case REHYDRATE:
var incoming = action.payload.myReducer
if (incoming) return {...state, ...incoming, specialKey:
processSpecial(incoming.specialKey)}
return state
following are the methods which can be used to work out of redux-persist
persistor.pause() - pauses redux persist
persistor.resume() - resumes redux persist
persistor.purge() - deletes all persisted data
persistor.rehydrate() - calls reducer to rehydrate store
types of storage redux persist allows
// sessionStorage
import { persistStore } from 'redux-persist'
import { asyncSessionStorage } from 'redux-persist/storages'
persistStore(store, {storage: asyncSessionStorage})
// react-native
import {AsyncStorage} from 'react-native'
persistStore(store, {storage: AsyncStorage})
// web with recommended localForage
import localForage from 'localforage'
persistStore(store, {storage: localForage})
it is the most basic use redux-persist hope it helps.
You can connect to as many components as you want to same store properties - that's not issue. Just to note here, connect is listening for changes so when action changes store it would cause all components listening to changed store parts to re-render (or at least do computation and then compare shadowdom).
Store is stored only when running application - after page close/resfresh it's cleared.
There is good thread about persistence of store (eg. to local storage): Where to write to localStorage in a Redux app?
As mentioned in the previous answers storing the state in local storage is one possible solution however some other possible solutions are
Make sure your links use the Push API ie <Link to={"route"}/> (react-router) this will ensure the store is not removed between route changes
Set your initial state when you create your store
const rootReducer = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter
});
const initialState = {
todos: [{id:123, text:'hello', completed: false}]
};
const store = createStore(
rootReducer,
initialState
);
This will hydrate your reduce store when it is initialized
There are some other libs/frameworks/boilerplates that you could use that also implement server rendering with will render the HTML to the page on the initial render and then load React once the DOM has loaded some of this libs are
Next.js,Electroide,react-redux-universal-hot-example
Going down this route will add significant complexity to your app so you should weigh up the pros and cons before embarking down this route
I had this problem, so as somebody mentioned here, i used the localStorage.
In my reducer, i did like this:
const exampleKey = "_exampleKey"
const INITIAL_STATE = {
example: JSON.parse(localStorage.getItem(exampleKey))
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_TRUE':
localStorage.setItem( exampleKey, JSON.stringify(true));
return { ...state, example: true };
case 'SET_FALSE':
localStorage.setItem( exampleKey, JSON.stringify(false));
return { ...state, example: false};
default:
return state
}
Pointing INITIAL_VALUES.example to localStorage's item example ensure us to keep the correct value when the page reloads.
Hope it helps somebody.