I am new in react and redux and I would like to know if it is possible to use one store for multiple actions.
I am trying this but the first action ges overwritten with the last action, why?
I am calling the action in two separate component and I call those two component in my app component.
reducer.js
const dataReducer = (state = {
fetching: false,
fetched: false,
data: {},
error: null
}, action) => {
switch (action.type) {
case 'FETCH_DATA_PENDING':
return {...state, fetching: true}
break;
case 'FETCH_DATA_FULFILLED':
return {...state, fetching: false, fetched: true, data: action.payload.data }
break;
case 'FETCH_DATA_REJECTED':
return {...state, fetching: false, error: action.payload }
break;
}
return state;
}
module.exports = dataReducer;
action.js
import axios from 'axios';
const apiUrl = 'https://swapi.co/api/';
//fetch categories
export function fetchCategories() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl)
}
}
//fetch films
export function fetchFilms() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl + 'films')
}
}
You should be able to do it this way, but the fact that both of your actions have the same type might be confusing in your reducers. It might be more helpful to have a type FETCH_FILMS and FETCH_CATEGORIES. That way the reducer can do separate things with them, unless of course, you always want every reducer to do the exact same thing with them.
Related
In redux actions, when we want to set a value, we use a type to dispatch like this :
dispatch({
type: SET_LOADER,
payload: true
})
Where the type: SET_LOADER stored in a different file and export it like below.
export const SET_LOADER = 'SET_LOADER'
And in reducer we will do it like this :
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
So in my application, I have this SET_LOADER type used in different actions and reducers. For example, in authentication, in profile update, when I want to load, I will use this type. So I have this type imported in various places.
I'm not sure if it's okay to use a single type for multipurpose because I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
But it's working for the first time dispatch. The next update, it's updating the incorrect redux state. After I refresh the page and try to update again, then it work.
first of all you need to separate your reducer into multiple reducers and then combine them in the store , then you can probably get away by using that same action in multiple cases for but then it'll be only a per reeducer solution meaning that let's say you have and Auth reducer this reducer will have its isLoading , and it may interfere with other actions within that reducer , fore example FetchAllProducts will use isLoading but also FetchByIdProduct is using isLoading and same for other actions that will trigger a loading state .
let's consider these reducers which use the same initial state
function initialState() {
return {
formErr: {},
isLoading: false
}
}
export const authReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const productsReducer=(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
export const cartReducer =(state = initialState(), action)=> {
const { type, payload } = action;
switch (type) {
case SET_LOADER:
return {
...state,
isLoading: payload
}
default:
return state
}
}
//this is the store
import {createStore,applyMiddleware,compose,combineReducers} from 'redux'
import thunk from 'redux-thunk'
import {productsReducer} from './reducers/ProductReducer'
import {cartReducer} from './reducers/CartReducer'
import {authReducer } from './reducers/AuthReducer'
const initialState={
products: {
formErr: {},
isLoading: false
},
cart: {
formErr: {},
isLoading: false
},
auth: {
formErr: {},
isLoading: false
}
}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(combineReducers({
products: productsReducer,
cart : cartReducer ,
auth : authReducer,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store
even though their using the same initial state you , when you will connect a component to the redux store you have access to three different isLoading :
export default connect((state)=>({
isLoading : state.products.isLoading,
isLoading2: state.authReducer.isLoading,
isLoading3: state.cart.isLoading,
}))(Products)
but to be honest I'd rather have make my actions more explicit and case specific something like productsFetchIsLoading , this gives you more control and prevents bugs
I noticed now that when I do dispatch, the redux state that get updated is not belonged to the target reducer. The state update is happening at different reducer.
Every action gets dispatched to every reducer. When you call dispatch({ type: SET_LOADER, payload: true }), the expected behavior is that the isLoading state will get set to true in every reducer which has a case SET_LOADER.
If you want the loading states to be independent then each reducer needs a unique string action type.
If you have multiple similar reducers then you can use a factory function to generate the type names, action creator functions, and reducer cases. Here we are extending the createSlice utility from Redux Toolkit.
We pass in the name which is the prefix for the auto-generated action types, the initialState of just the unique properties for this reducer state, and any unique reducer cases. This will get merged with the standard base state.
Helper:
const createCustomSlice = ({name, initialState = {}, reducers = {}}) => {
return createSlice({
name,
initialState: {
formErr: {},
isLoading: false
...initialState,
},
reducers: {
setLoader: (state, action) => {
state.isLoading = action.payload;
},
setFormErr: (state, action) => {
state.formErr = action.payload;
}
...reducers,
}
});
}
Usage:
const profileSlice = createCustomSlice({
name: "profile",
initialState: {
username: ""
},
reducers: {
setUsername: (state, action) => {
state.username = action.payload;
}
}
});
// reducer function
const profileReducer = profileSlice.reducer;
// action creator functions
export const { setFormErr, setLoader, setUsername } = profileSlice.actions;
These action creators will create actions with a prefixed type like 'profile/setLoader'.
This is probably a mistake on my part but I've noticed that in my Redux store, for every entity, there seems to be an outer object named x which in turn has a child with the same name, which then contains the data.
This is better explained with an example:
user reducer
const initialState = {
user: {},
}
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_USER:
return {
...state,
user: action.payload,
loggedIn: true,
}
case REGISTER_USER:
return {
...state,
user: action.payload,
registered: true,
}
default:
return state
}
}
user object in state
As you can see, user has a user nested inside of it.. can this pattern be avoided somehow in the reducer?
You can change your script on
case LOGIN_USER:
return {
...state,
...action.payload,
loggedIn: true,
}
etc..
it's should help you
I'm a little new to react, redux, and sagas, but I'm getting the hang of things.
I have a component (Results.jsx) that displays results of a particular real-world event, through a saga calling an external API:
componentDidMount() {
if (this.props.thing_id) {
this.props.getResults(this.props.thing_id);
}
}
...
const mapStateToProps = (state) => {
return {
prop1: state.apiReducer.thing_results.data1,
prop2: state.apiReducer.thing_results.data2,
fetching: state.apiReducer.fetching,
error: state.apiReducer.error,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getResults: (thing_id) => dispatch({type: "RESULTS_DATA_REFRESH", payload: thing_id})
};
};
This all works great. Until... Well, I'm using a tabbed interface that lets me dynamically add a bunch of additional instances of Results.jsx so I can see a bunch of different results sets all on the same screen.
The problem is that when a new instance of the Results.jsx component loads, and gets data from the RESULTS_DATA_REFRESH dispatch, all of the instances of the Results.jsx component update with the data that comes back. They all show the same data.
For the life of me, I can't figure out how to have a particular instance of a component only listen to results from what it itself dispatched. I thought that's the way sagas were supposed to work?
Any help is appreciated!
Edits/Answers:
Reducer function is pretty textbook, looks like:
const initialState = {
fetching: false,
error: null,
thing_results: {
data1: null,
data2: null,
},
};
export default (state = initialState, action) => {
switch (action.type) {
//...
case "RESULTS_DATA_REFRESH":
return {...state, fetching: true};
case "RESULTS_DATA_SUCCESS":
return {...state, fetching: false, thing_results: action.results.data, error: null};
case "RESULTS_DATA_FAILURE":
return {...state, fetching: false, thing_results: null, error: action.error};
default:
return state;
}
};
Sagas are nothing but a middleware to offload your async tasks and store writes out of the View layer. Ultimately the prop that comes to your component depends on how you store it. Specifically in this case if prop1 and prop2 are picked up from the same place in the store, it'll come as the same value in all instances of Results.
If you require different data for different instances, section it based on some unique id mapped to the instance. You reducer would look like :
const apiReducer = (state = {}, action) => {
switch (action.type) {
case "RESULTS_DATA_REFRESH":
return {
...state,
[action.payload]: { data: null, fetching: true }
};
case "RESULTS_DATA_SUCCESS":
return {
...state,
/** You should be getting back the id from the api response.
* Else append the id in the success action from your api saga.
*/
[action.payload.id]: { data: action.results.data, fetching: false }
};
case "RESULTS_DATA_FAILURE":
return {
...state,
[action.payload.id]: {
data: null,
fetching: false,
error: action.error
}
};
default:
return state;
}
};
/** Other reducers */
const otherReducerA = function() {};
const otherReducerB = function() {};
export default combineReducers({ apiReducer, otherReducerA, otherReducerB });
And access it like :
const mapStateToProps = state => {
return {
data: state.apiReducer
};
};
function Results({ data, thing_id }) {
return <div>{data[thing_id].data}</div>;
}
I'm trying to delete an element from dom by clicking on it. I did it without the problem without redux thunk but now I have a problem. My reducer doesn't know about the state. How do let him know what items are?
Action:
export function deleteItem(index) {
return {
type: 'DELETE_ITEM',
index
};
}
My reducer that shows undefined.
export function deleteItem(state = [], action) {
switch (action.type) {
case 'DELETE_ITEM':
const copy = state.items.slice()
console.log(copy)
default:
return state;
}
}
Heres my actual code https://github.com/KamilStaszewski/flashcards/tree/develop/src
I saw your code and you are defining a new reducer for each of the operations you want to get done to your items (e.i itemsHaveError, deleteItem, ...) but the correct way of doing this is to store all of the relevant functions for the items to a single reducer which holds the data needed to change whenever some action to the items happens, but in the way you did it, any time any action happens because your reducers are separated the initial state gets empty as you have passed to the functions and the reducers do not know about their related data so they overwrite them with the empty initial state, the correct way would be like this to write a single reducer for items:
const initialState = {
isLoading: false,
hasError: false,
items: [],
};
export default function(state = initialState, action) {
switch (action.type) {
case ITEMS_HAVE_ERROR:
return {
...state,
hasError: action.hasError,
};
case ITEMS_ARE_LOADING:
return {
...state,
isLoading: action.isLoading,
};
case ITEMS_FETCH_DATA_SUCCESS:
return {
...state,
items: action.items,
};
case DELETE_ITEM:
const copy = state.items.slice()
return {
...state,
items: copy,
};
default:
return state;
}
}
so this would be your item.js and your item reducer and the only one that should get to combineReducer function.
Indicate the initial State of the reducer by default , the state is an empty array and you can't access the state.items , cause it is undefined. Assume this:
const x = [];
x.foo.slice();
that would return an error . Thus from :
state = []
change it to :
state = {
items:[]
}
applying it to your code:
export function deleteItem(
state = {
items:[]
},
action) {
switch (action.type) {
case 'DELETE_ITEM':
const copy = state.items.slice()
console.log(copy)
default:
return state;
}
}
So I have this simple async request action creator using axios for requests and redux-thunk to dispatch actions. I want to re-use the same action creator for different requests - so each request would have its own state. I'll try to show some code and then explain more. Here is my action creator
function responseOK(response){
return {
type: RESPONSE_OK,
payload: response,
}
}
function loadingChangedStatus(isLoading) {
return {
type: IS_LOADING,
isLoading: isLoading,
}
}
function handleError(errorID){
return {
type: HANDLE_ERROR,
errorID: errorID,
}
}
export function makeRequest( URL){
return function(dispatch, getSate){
dispatch( loadingChangedStatus(true) );
axios.get( URL )
.then(function(response){
dispatch(loadingChangedStatus(false));
dispatch(responseOK(response));
})
.catch(function (response) {
dispatch(loadingChangedStatus(false));
dispatch(handleError('connection_error'));
});
}
}
And my reducer reducerRequest:
export default function(state = {isLoading:false, payload:false,errorID:false}, action){
switch (action.type) {
case IS_LOADING:
return Object.assign({}, state, {
isLoading: action.isLoading,
});
break;
case RESPONSE_OK:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
});
break;
case HANDLE_ERROR:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
errorID:action.errorID,
});
break;
default:
return state;
}
}
HERE STARTS MY PROBLEM
I combine reducers like so:
import { combineReducers } from 'redux';
import Request from "./reducerRequest";
const rootReducer = combineReducers({
// I WANT EACH OF THESE TO BE SEPARATE INSTANCE BUT USE THE SAME ACTION CREATOR / REDUCER
defaults: Request,
updates: Request,
});
export default rootReducer;
In my component:
function mapStateToProps( {defaults, updates} ){
return {defaults, updates}
}
function mapDispatchToProps( dispatch ){
return bindActionCreators({ makeRequest}, dispatch);
}
PROBLEM: I want to re-use my action creator for different requests. How can I
call makeRequest('www.defaults.com') and it ends up in defaults
call makeRequest('www.updates.com') and it ends up in updates
Now the only way I can image to solve this would be to write for every request its own action creator and own reducer - just lots of copy paste - that doesn't feel right.
How can I reuse my action creator and reducer to create 2 separate instances of defaults and updates in my component?
You can prefix your reducer actions per action:
export default function(namespace)
return function(state = {isLoading:false, payload:false,errorID:false}, action){
switch (action.type) {
case namespace + IS_LOADING:
return Object.assign({}, state, {
isLoading: action.isLoading,
});
break;
case namespace + RESPONSE_OK:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
});
break;
case namespace + HANDLE_ERROR:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
errorID:action.errorID,
});
break;
default:
return state;
}
}
}
and then add the namespace
function responseOK(namespace, response){
return {
type: namespace + RESPONSE_OK,
payload: response,
}
}
const rootReducer = combineReducers({
// I WANT EACH OF THESE TO BE SEPARATE INSTANCE BUT USE THE SAME ACTION CREATOR / REDUCER
defaults: Request("DEFAULTS_"),
updates: Request("UPDATES_"),
});
and then use the namespace when you call the make requests
call makeRequest('DEFAULTS_', 'www.defaults.com') and it ends up in defaults
call makeRequest('UPDATES_', 'www.updates.com') and it ends up in updates
HTH