I want to add a loader for each action, like the buttons will display loading when dispatch (see demo)
export default class Items extends Component {
render() {
return (
<div>
<div>status: {this.props.item.status}</div>
<button onClick={() => this.props.resetItem()}>
{this.props.loading ? "loading..." : "Reset"}
</button>
<button onClick={() => this.props.approveItem()}>
{this.props.loading ? "loading..." : "Approve"}
</button>
</div>
);
}
}
The problem is all button will show loading because my reducer has a global loading state only
export function items(state = initState, action) {
switch (action.type) {
case "APPROVE":
return {
...state,
loading: true
};
case "APPROVED":
return {
...state,
loading: false,
item: {
status: "approved"
}
};
case "RESET":
return {
...state,
loading: true
};
case "DONE_RESET":
return {
...state,
loading: false,
item: {
status: "pending"
}
};
default:
return state;
}
}
I can hardcode approve_loading, reset_loading and so on but that's redundancy, any technique to do namespacing in reducer?
Neat question - have never run into this myself but I'm wondering if something like this could work. You can use combineReducers() to namespace, so a perhaps not entirely elegant approach could be:
export function itemsReducer(index) {
return function items(state = {}, action) {
switch (action.type) {
case `APPROVE_${index}`:
return {
...state,
loading: true,
};
case `APPROVED_${index}`:
return {
...state,
loading: false,
item: {
status: 'approved',
},
};
default:
return state;
}
};
}
const reducers = {};
//could add more indexes here for more items;
[0, 1].forEach(i => {
reducers[`item${i}`] = itemsReducer(i);
});
export default combineReducers(reducers);
//state = {
// item0: {...},
// item1: {...}
//}
Your actions would then need to include the appropriate index (0 or 1) when dispatching (e.g. APPROVED_1) so the correct item state will get set.
Equivalent syntax:
export default combineReducers({
item0: itemsReducer(0),
item1: itemsReducer(1)
});
Related
As an initial state I use array of objects:
export default{
items: [
{
Date: 1,
Operation: 'revenue',
}
]
}
In component I dispatch the action, which must update one element in object of array: "Operation"
const mapDispatchToProps = (dispatch) => {
return {
selectOperation: (input) => dispatch({type: app.SELECT, payload: input})
}
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: 'Operation',
value: event.target.value
});
};
// render() logic
}
export default connect(null, mapDispatchToProps)(OperationSelect)
Reducer:
import initialState from '../constants/initialState';
import { app } from '../constants/types';
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
[action.payload.key]: state.items.map(
(item, i)=> i===0 ? {...item, Operation: action.payload.value}
: item
)
};
default:
return state;
}
}
But, when I select new value and application run handleChange, dispatch the action, run reducer, the state in store keeps the old value of "Operation".
What am I doing wrong?
This is I think what you need to do:
first add an id property to your items and then do something like this:
export default {
items: [
{
id: 0,
Date: 1,
Operation: "revenue",
},
],
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: "Operation", // I think you need to check this and try to findout that you need this or not
value: event.target.value,
id: 0 // 0 is just an example you need to decide how you would implement the id
});
}
// render() logic
}
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map((item, i) =>
i === action.payload.id ? { ...item, Operation: action.payload.value } : item
),
};
default:
return state;
}
}
The problem was in that I did not update in reducer the state of array.
This is a working code:
export default function update(state = initialState, action) {
switch (action.type) {
case app.SELECT:
return {
...state,
items: state.items.map(
(item, i)=> i===0 ? {...item, [action.payload.key]: action.payload.value}
: item
)
};
default:
return state;
}
}
Earlier in return-block I used [action.payload.key] in place, where "items" should have used. So I updated "Operation" in place, where "items" updated.
I wanted to know how I can pass the state into an action so that it can use the state to make an API call. the state I want to pass is the input because it's the image URL that gets sent to Clarifai's servers to predict the celebrity. The handle search is responsible for updating state to the URL.
I've tried to use get state with no luck
This is my action
export const requestPrediction = () => {
return function (dispatch, getState) {
dispatch(fetchCelebrequest)
let input = getState().input
app.models.predict(Clarifai.CELEBRITY_MODEL,
input)
.then(res => {
const data = res.outputs[0]['data']['regions'][0]['data'].concepts[0]
dispatch(fetchCelebSuccess(data))
})
.catch(err => {
const error = err.message
dispatch(fetchCelebFailed(error))
})
}
}
This is my reducer.js
import {
CHANGE_SEARCHFIELD,
REQUEST_PREDICTION_PENDING,
REQUEST_PREDICTION_SUCESS,
REQUEST_PREDICTION_FAILED
} from './constants'
const initialState = {
input: '',
imageUrl: '',
box: {},
isSignedIn: false,
isPending: false,
celeb: {},
error: '',
celebConfidence: [],
user: {
id: '',
name: '',
email: '',
entries: 0,
joined: ''
}
}
export const handleSearch = (state=initialState, action={}) => {
switch (action.type) {
case CHANGE_SEARCHFIELD:
return { ...state, input: action.payload }
default:
return state
}
}
export const requestPrediction = (state=initialState, action={}) => {
switch(action.type) {
case REQUEST_PREDICTION_PENDING:
return {...state, isPending: true}
case REQUEST_PREDICTION_SUCESS:
return {...state, celebName: action.payload, isPending: false}
case REQUEST_PREDICTION_FAILED:
return {...state, error: action.payload, isPending: false}
default:
return state
}
}
The proper way to do it is via setState since the updates my be asynchronous.
You may check out this link. How can I pass state to an action in React.js?
Im new to react, now Im creating a simple app using redux and redux-thunk which calls an API asynchronously. Here is my game gameAction:
export const fetchGamesStartAsync = () => {
return dispatch => {
dispatch(fetchGamesStart());
axiosGenCfg.post('/game/get',{
"page" : 1,
"size" : 10
})
.then(({ res }) => {
dispatch(fetchGamesSuccess(res));
})
.catch(err => {
dispatch(fetchGamesFailure(err.message));
});
}
};
const fetchGamesStart = () => ({
type: gameActionTypes.FETCH_GAMES_START,
});
const fetchGamesFailure = () => ({
type: gameActionTypes.FETCH_GAMES_FAILURE,
});
const fetchGamesSuccess = (games) => ({
type: gameActionTypes.FETCH_GAMES_SUCCESS,
payload:{
...games
}
});
and this is my gameReducer:
const INITIAL_STATE= {
gamesList : null,
isFetching: false,
errorMessage : undefined
};
const gameReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case gameActionTypes.FETCH_GAMES_START:
return{
...state,
isFetching: true
};
case gameActionTypes.FETCH_GAMES_SUCCESS:
return{
...state,
isFetching: false,
gamesList: action.payload
};
case gameActionTypes.FETCH_GAMES_FAILURE:
return{
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return {
state
};
}
};
and in rootReducer
export default combineReducers({
admin : adminReducer,
game: gameReducer,
})
I also added redux-logger to check state and this is what i get in console
So why there are 2 levels of state in my game object? and also the same with admin object. before i add redux-thunk to project, I didn't have this problem. before adding redux-thunk currentAdmin was direct child of admin. But now there is a state object between.
default:
return {
state
};
should just be
default:
return state
Right now any time you hit the default, state.whatever is becoming state.state.whatever
I have a state object that contains an array inside names rows. This array contains a list of objects:
{_id: "5e88ad4c5f6f7388d50b9480",
stampa: "Confezione",
S: 0,
M: 0,
L: 0,
XL: 0,
coloured: "",
modello: "",
SKU: ""}
Now, in a form I dispatch an action that the payload contains exactly the same object, the only differences are the keys S, M, L, XL that can change.
Then, in my reducer, I want to find in my original state the same object by matching with _id, and then update it with the object that comes with the payload.
This is my attempt, however I am getting the following error:
TypeError: state.rows.map is not a function
case "UPDATE_ROW":
return state.rows.map((row) =>
row._id === action.payload._id
? {
...row,
_id: action.payload,
}
: row
);
How can I tackle this in a better way?
EDIT:
This is my reducer:
export default (state, action) => {
switch (action.type) {
case "UPDATE_STATE":
return {
...state,
rows: action.payload,
};
case "SET_ERROR":
return {
...state,
error: action.payload,
};
case "UPDATE_ROW":
console.log("updating", action.payload);
return state.rows.map((row) =>
row._id === action.payload._id
? {
...row,
_id: action.payload,
}
: row
);
default:
return state;
}
};
And this is my state:
import React, { createContext, useReducer } from "react";
import AppReducer from "./AppReducer";
// Initial State
const initialState = {
rows: [],
};
export const GlobalContext = createContext(initialState);
// Provider component
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
console.log(state);
return (
<GlobalContext.Provider value={[state, dispatch]}>
{children}
</GlobalContext.Provider>
);
};
The main issue you are having there is that you should default state.rows to be an array in order for the type error to stop.
Other than that, from what I see there, your logic seems fine.
EDIT:
Okay, looks like your issue was that you weren't returning the full state when you were running the "UPDATE_ROW" case. If you use this, I believe the issue should be fixed.
I also fixed the logic in the reducer case as well. It looked like you were adding the entirety of the payload object in the _id property while keeping the rest of the row the same. Rather than update the row with the payload property.
export default (state, action) => {
switch (action.type) {
case "UPDATE_STATE":
return {
...state,
rows: action.payload,
};
case "SET_ERROR":
return {
...state,
error: action.payload,
};
case "UPDATE_ROW":
console.log("updating", action.payload);
return {...state, rows:state.rows.map((row) =>
row._id === action.payload._id
? {
...action.payload
}
: row
)};
default:
return state;
}
};
This is the reducer state. I need to add, update, remove the object in cartData. At the first time, cartData is empty.
const initialState = {
fetchData: {},
cartData: {}
}
Example:
fetchData: {
"React":{'name': 'React'},
"Node":{'name': 'Node'},
}
If user ADD_ITEM react book, new item is adding in the cart here.
cartData:{
"React":{'name': 'React', 'quantity': 1},
}
If user Edit_ITEM react book, existing item is updating here.
cartData:{
"React":{'name': 'React', 'quantity': 4},
}
If user REMOVE_ITEM react book, removing when its come to zero here.
cartData:{
}
How can we modify redux state for these actions?
Tried this: using lodash. But did't worked out correctly.
case types.ADD_ITEM:
return { ...state, cartData: // add new item }
case types.EDIT_ITEM:
return { ...state, [state.cartData.name]: action.payload }
case types.REMOVE_ITEM:
return _.omit(state, [state.cartData.name]: action.payload)
You can use spread syntax for add and edit items and Object.keys() and reduce() for remove item.
const initialState = {
fetchData: {},
cartData: {}
}
function cartReducer(state = initialState, action) {
switch(action.type) {
case 'ADD_ITEM':
return {...state, cartData: {...state.cartData, ...action.payload}}
case 'EDIT_ITEM':
return {...state, cartData: {...state.cartData, ...action.payload}}
case 'REMOVE_ITEM':
let newState = Object.keys(state.cartData).reduce((r, e) => {
if(!action.payload[e]) r[e] = state.cartData[e];
return r
}, {})
return {...state, cartData: newState}
default:
return state;
}
}
var state = {}
state = cartReducer(undefined, {
type: 'ADD_ITEM',
payload: {"React":{'name': 'React', 'quantity': 1}}
})
console.log(state)
state = cartReducer(state, {
type: 'ADD_ITEM',
payload: {"Node":{'name': 'Node', 'quantity': 2}}
})
console.log(state)
state = cartReducer(state, {
type: 'EDIT_ITEM',
payload: {"React":{'name': 'React', 'quantity': 4}}
})
console.log(state)
state = cartReducer(state, {
type: 'REMOVE_ITEM',
payload: {"React":{'name': 'React', 'quantity': 1}}
})
console.log(state)
It's hard to know exactly what you are trying. Below is an example of a reducer function with an add to cart method. You'll need to add a similar method for each of your scenarios.
export function reducer(state = initialState, action: any): State {
switch(action.type) {
case "ADD_TO_CART": {
return {
fetchData: state.fetchData,
cartData: Object.assign({}, state.cartData, action.payload}
};
}
}
default: {
return state;
}
}
You will then dispatch the action by calling the dispatch function:
dispatch({
type: "ADD_TO_CART",
payload: "React":{'name': 'React', 'quantity': 1}
})
In actions:
const editData = (items) => (dispatch) => {
dispatch({type: 'EDIT_ITEMS', payload: items});
}
In reducer:
const reducer = (state = INITIAL_STATE, action){
case 'EDIT_ITEMS': {
if(_.isEmpty(action.payload)){
return {
...state,
cartData: {},
};
} else {
return {
...state,
cellData: action.payload,
};
}
}
}
This should be the way to do it. payload should be all the items you've in the cart at any point of time.
[EDIT:]
As the question has been edited, You can also do that using deleting a key, using
// Ref: https://github.com/erikras/react-redux-universal-hot-example/issues/962#issuecomment-219354496
export const removeByKey = (object, deleteKey) => {
return Object.keys(object)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = object[current];
return result;
}, {});
};
case types.REMOVE_ITEM:
return { ...state, cartData: deleteKey(cartData, action.payload)) }