I'm trying to get the store from the reducer.
i saw the redux architecture is not supporting sharing between reducers.
but its really needed in my case.
const initState = {
schemas: [],
};
const myReducer = (state , action) => {
state = state || initState;
switch (action.type) {
case 'redux-form/CHANGE':
const schemaId = getStore().context.schema;
let modifier = state.schemas.get(schemaId);
modifier[action.key] = action.value;
return {
...state
};
}
};
my app reducers:
const iceApp = combineReducers({
form: formReducer,
context,
myReducer,
});
thanks ahead.
You can add reducer functions to any level of your state, consider:
const complexReducer = (state, action) {
const {context, myReducer} = state;
switch (action.type) {
case 'redux-form/CHANGE':
// do something with myReducer
return {context, myReducer};
default:
return state;
}
}
// a simple utility function to call reducers in sequence on the same state
function composeReducers(...reducers) {
return (state, action) => reducers.reduceRight((acc, reducer) => reducer(acc, action), state);
}
const iceApp = composeReducers(complexReducer, combineReducers({
form: formReducer,
context,
myReducer,
}));
This will apply the complexReducer to the whole state coming from the simple reducers.
A different approach is to access context in the action and pass it as payload of the action.
const changeAction = ... // the redux-form/change action
const changeActionWithContext = () => (dispatch, getState) => {
const state = getState();
const schemaId = state.context.schema;
dispatch(changeAction(schemaId));
}
I don't now redux-form so I don't know whether it's possible or not to add custom payload to redux-form actions.
Related
I want to dispatch in changeCategory reducer. how should I do it?
I am using create-react-app tool
Thanks
export const searchParamsSlice = createSlice({
name: 'searchParams',
initialState,
reducers: {
changeLocation: (state, action) => {
state.location = action.payload;
},
changeCategory: (state, action) => {
state.category = action.payload;
const dispatch = useDispatch()
dispatch(fetchResturantsAsync({ city: state.location, category: state.category, searchKey: state.seachText, page: 0, size: 10 }))
},
}
You cannot dispatch in a reducer - it is one of the three Redux core principles that reducers have to be side-effect-free.
If you want to react to another action by dispatching a new one, you could always use the listenerMiddleware provided by RTK Query, or write a thunk action creator that dispatches both of those actions after each other.
Here is how you should do it
create this Middleware
export const asyncDispatchMiddleware = store => next => action => {
let syncActivityFinished = false;
let actionQueue = [];
function flushQueue() {
actionQueue.forEach(a => store.dispatch(a)); // flush queue
actionQueue = [];
}
function asyncDispatch(asyncAction) {
actionQueue = actionQueue.concat([asyncAction]);
if (syncActivityFinished) {
flushQueue();
}
}
const actionWithAsyncDispatch =
Object.assign({}, action, { asyncDispatch });
const res = next(actionWithAsyncDispatch);
syncActivityFinished = true;
flushQueue();
return res;
};
Then add it to your store
export const store = configureStore({
reducer: {
counter: counterReducer,
//....
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(asyncDispatchMiddleware),
});
Then in your reducer do something like this
changeWeekday: (state, action) => {
state.weekName = action.payload;
action.asyncDispatch(fetchSomethingAsync({
weekName: state.weekName
}))
}
I had changeWeekday in my code, in your case it could be any reducer.
populate my initialState with json api call from a component or from here
const initialState = {
myvalues: [] ---->here i want to populate this array
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case "Update":
console.log(newState);
// newState.myvalues = action.key.title.value;
default:
return newState;
}
};
export default reducer;
To populate your initialState with data from an API, you can create e.g. an FETCH_INIT_DATA_ACTION, which get's dispatched right after you initialised your store.
// ...
const store = createStore(/* ... */)
store.dispatch({ type: 'FETCH_INIT_DATA_ACTION' })
// ...
whereas FETCH_INIT_DATA_ACTION triggers a redux-thunk, saga, effect or whatever you want to use.
You can do it like this:
const reducer = (state = initialState, action) => {
switch (action.type) {
case "Update":
return { ...state, myvalues: action.payload }
default:
return state;
}
};
And when you dispatch it you should put your API data in payload.
I am using redux with multiple reducers combined into rootReducer. How is it possible to modify the state of one reducer from another reducer? Ex:
// systemReducer.js
const INITIAL_STATE = { isLoggedIn: true }
function systemReducer(state = INITIAL_STATE, action) {
switch(action.type) { ... }
}
// messagesReducer.js
const INITIAL_STATE = { messages: [] }
function messagesReducer(state = INITIAL_STATE, action) {
switch(action.type) { ... }
}
And then say I have action makers for messagesReducer as under:
// messageActions.js
export const messagesFetchAction = (data) => {
return {
type: MESSAGES_FETCH,
data: data
}
}
Now, how can I modify systemReducer's isLoggedIn to false from messagesFetchAction()? So it looks like as under for example:
// messageActions.js
export const messagesFetchAction = (data) => {
systemState.setState({isLoggedIn: false}); // <=====
return {
type: MESSAGES_FETCH,
data: data
}
}
If you have two reducers does not mean that you have several stores. You still have single store, but combined from two reducers. In general, your store may look like:
{
systemReducer: {
isLoggedIn: true
},
messagesReducer: {
messages: []
}
}
You couldn't dispatch action from reducer. This is prohibited by redux. But you may dispatch several actions from action creator. For example:
export const messagesFetchAction = (data) => (dispatch) => {
dispatch({type: LOGGED_IN, isLoggedIn: false});
dispatch({
type: MESSAGES_FETCH,
data: data
});
}
The action creator above is for Redux Thunk. To be able to use it, apply middleware when creating store like this
const rootReducer = combineReducers({
systemReducer,
messagesReducer
});
const store = createStore(rootReducer, applyMiddleware(
thunkMiddleware
));
I'm using the Redux Thunk example template. When I dispatch an action in getInitialProps, that populates my store, the data is loaded but after the page is rendered, the store is still empty.
static async getInitialProps({ reduxStore }) {
await reduxStore.dispatch(fetchCategories())
const categories = reduxStore.getState().programm.categories;
console.log('STATE!!!', categories)
return { categories }
}
The categories will load correctly but when I inspect my store, the categories state is empty.
Here is my store:
import db from '../../api/db'
// TYPES
export const actionTypes = {
FETCH_PROGRAMMS: 'FETCH_PROGRAMMS',
FETCH_CATEGORIES: 'FETCH_CATEGORIES'
}
// ACTIONS
export const fetchCategories = () => async dispatch => {
const categories = await db.fetchCategories();
console.log('loaded Cate', categories)
return dispatch({
type: actionTypes.FETCH_CATEGORIES,
payload: categories
})
}
// REDUCERS
const initialState = {
programms: [],
categories: []
}
export const programmReducers = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_PROGRAMMS:
return Object.assign({}, state, {
programms: action.payload
})
case actionTypes.FETCH_CATEGORIES:
console.log('Payload!', action);
return Object.assign({}, state, {
categories: action.payload
})
default:
return state
}
}
How can I make the redux state loaded on the server (in getInitialProps) be carried over to the client?
After hours of searching for solution it seems like I found my problem. It seems like I need to pass an initialState when creating the store. So instead of this:
export function initializeStore() {
return createStore(
rootReducers,
composeWithDevTools(applyMiddleware(...middleware))
)
}
I'm doing this and it works now
const exampleInitialState = {}
export function initializeStore(initialState = exampleInitialState) {
return createStore(
rootReducers,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
}
If you do this:
return { categories }
in getInitialProps, categories should be available in component's props in client side.
It should be available in Redux as well, this could cause the problem:
return Object.assign({}, state, {
categories: action.payload
})
Take a look at this Object.assign, the function only takes 2 parameters.
My normal way of doing this:
return {
...state,
categories: action.payload,
};
Let's say there is a tree in the state of my react-redux app and tree belongs to myReducer. In many cases I need this tree flattened, so I've got a selector:
const getTree = state => state.myReducer.tree;
export const getFlatNodes = createSelector(
[getTree],
(tree) => flattenTree(tree)
);
And now I need to access the flattened tree in an action
import { getFlatNodes } from 'path/to/selectors';
function myReducer(state = defaultState, action) {
switch (action.type) {
case SOME_ACTION:
const flattenedTree = getFlatNodes(state);
return {
...state,
smth: getSmthFromFlattenedTree(flattenedTree)
};
}
}
This code will not work as expected since state in the reducer is just a slice of the app state. A simple workaround I came up with is to wrap state to make the passed parameter compatible:
import { getFlatNodes as _getFlatNodes } from 'path/to/selectors';
const getFlatNodes = myReducer => _getFlatNodes({ myReducer });
The code works, although it looks quite hacky and I'm not sure if it won't cause any issues.
Does anybody have any idea why and how it can be done better or my approach is good enough already?
Maybe if you make that in your action
const someAction = () => (dispatch, getState) => ({
type: 'SOME_ACTION',
payload: getFlatNodes(getState())
// payload: getSmthFromFlattenedTree(getFlatNodes(getState()))
});
then in your reducer
function myReducer(state = defaultState, action) {
switch (action.type) {
case SOME_ACTION:
return {
...state,
smth: getSmthFromFlattenedTree(action.payload)
};
}
}