State does not change in my reducer when action is dispatched - redux

I am not able to retrieve the state in the reducer
MyComponent looks like this
const MyComponent = ({name, features, onClick}) => {
return (
<div>
Hello! {name}
<Button onClick={() => { onClick(features); }}> Weight</Button>
</div>
);
const mapDispatchToProps = (dispatch: any) => {
return {
onClick: (features) => {
dispatch(weightSort(features));
}
};
};
const mapStateToProps = (state: any, ownProps: any) => {
console.log(state); //Displays the state
return {
name: "John Doe",
features: ownProps.features,
};
};
export const FeatureBlock = connect(mapStateToProps, mapDispatchToProps)(MyComponent);
My actions and reducers looks like below:
// Action Creator
export const weightSort = (features) => {
console.log("inside the weight sort action creator!!!");
return {
type: "SET_WEIGHT_FILTER",
filter: "DESC",
features,
};
};
// Reducer
export const weightFilter = (state = [], action) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
console.log(state); // Gives me empty state
console.log("++inside weight filter+++++", action); //Displays action
return state;
default:
return state;
}
};
export const FeatureBlock = connect(
mapStateToProps,
mapDispatchToProps,
)(MyComponent);
What am I missing here? Any help will be appreciated!

In your reducer, when you console.log(state), it is correct in returning an empty array because you haven't done anything to modify it.
// Reducer
export const weightFilter = (state = [1,2,3], action) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
console.log(state); // This will show [1,2,3] because [1,2,3] is the initial state.
console.log("++inside weight filter+++++", action); //Displays action
return state;
default:
return state;
}
};
My guess is that you want something like this for your reducer:
// Action Creator
export const weightSort = (name, features) => {
console.log("inside the weight sort action creator!!!");
return {
type: "SET_WEIGHT_FILTER",
name,
features,
};
};
// Reducer
export const weightFilter = (
state = {
name: '',
features: [],
},
action
) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
return {...state, name: action.name, features: action.features}
default:
return state;
}
};
and then in your mapStateToProps you would map out the attributes like so:
const mapStateToProps = (state: any, ownProps: any) => {
console.log(state); //Displays the state
return {
name: state.weightFilter.name,
features: state.weightFilter.features,
};
};
and your button would have a name prop passed into the function like so:
<Button onClick={() => { onClick(name, features); }}> Weight</Button>
If you would like to sort your data, you can do so either in the reducer or inside the container. I prefer to do it in the container and like to use the lodash sortBy function. It works like this:
import { sortBy } from 'lodash' //be sure to npm install lodash if you use this utility
...
...
function mapStateToProps(state) {
return {
name: state.weightFilter.name,
features: sortBy(features, ['nameOfPropertyToSortBy'])
};
}
Here is the lodash documentation on sortBy: https://lodash.com/docs/4.17.4#sortBy
Hope that helps!

Related

TypeError: dispatch is not a function when clicking the toggle button

I am using react redux-thunk. I have a set of users data that I get from an API and this is the schema:
.
I've connected the "active" property with the checked attribute of a Switch MUI button, so naturally when calling the API I have some users with their switch button already on "true". What I am trying to do is to just make the switch functional, and just be able to click it and change its state, not necessarily doing anything with that.
Here's my toggleType.js:
export const TOGGLE = "TOGGLE";
Here's my toggleAction.js:
import { TOGGLE } from "./toggleType";
const statusToggleAction = () => {
return {
type: TOGGLE,
};
};
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction);
};
};
Here's my toggleReducer.js:
import { TOGGLE } from "./toggleType";
const initialState = {
status: false,
};
const toggleReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE:
status: true;
default:
return state;
}
};
export default toggleReducer;
Everything is under my userContainer.js, like that:
function UserContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);
return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<Container maxWidth="lg" style={{ flexGrow: 1, height: "100%" }}>
<h2>User List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => (
<div key={user.id}>
<p>{user.name}</p>
<Switch checked={user.active} onChange={statusToggle()} />
</div>
))}
</div>
</Container>
);
}
const mapStateToProps = (state) => {
return { userData: state.user, statusToggle: state.status };
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
statusToggle: () => dispatch(statusToggle()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
This is the error I am getting whenever I am clicking one of those switches:
Any ideas are welcome, I "learned" redux like 3 days ago!
toggleReducer function in toggleReducer.js, replace status: true; with return { status: true }.
Just return action in statusToggle function in toggleAction.js without dispatch as following.
export const statusToggle = () => {
return statusToggleAction();
};
Or just call statusToggleAction directly in userContainer.js as following.
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction());
};
};

Dispatch an action in redux

I created an app which should simulate the buying of products. So, when i will click on the BUY button, the number of fruits should decrement.
I suppose that the problem is in my reducer, but i can' t understand where.
/////
const Fruits = (props) => {
console.log(props);
return (
<div>
<h1>Number of fruits: {props.numFruits} </h1>
<button onClick={buyFruitsAction}>BUY</button>
</div>
);
};
const mapStateToProps=(state)=> {
return {
numFruits: state.numFruits
}
};
const mapDispatchToProps=(dispatch)=> {
return {
buyFruitsAction: ()=> dispatch(buyFruitsAction())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Fruits)
//// Reducer
import {BUY_FRUITS} from "../types";
const initialState = {
numFruits : 10
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case BUY_FRUITS : return {
...state,
numFruits: state.numFruits - 1
};
default: return state;
}
};
export default reducer;
Where is the issue of my code?
This line is the problem:
<button onClick={buyFruitsAction}>BUY</button>
There is no variable buyFruitsAction, it is a property. So that should read props.buyFruitsAction.
Your reducer is fine, can you post your action creator?
buyFruitsAction is undefined in your component do you meant props.buyFruitsAction?

I was trying to get input with redux,all is fine but I can't figure out how to get input values

Trying to get user input with action,all is working i get my console.logs about how inputVal changes,but when I try to print this in i get undefined in console
Should I use like mapDispatchToProps or I don't need this,since I'm passing actions as second param into mapStateToProps
actions:
export const inputChange = val => {
return {
type: INPUT_CHANGE,
payload: val
};
};
reducer:
import { INPUT_CHANGE } from './actionTypes';
const initialState = {
inputVal: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case INPUT_CHANGE:
return {
...state,
inputVal: action.payload
};
default:
return state;
}
};
mainPage:
const mapStateToProps = state => {
console.log(state);
return state;
};
class MainPage extends Component {
onInput = e => {
this.props.inputChange(e.target.value);
console.log(this.props.inputChange(e.target.value));
};
render() {
console.log(this.props.inputVal);
return (
<div>
<input onChange={this.onInput}></input>
<p>{this.props.}</p>
</div>
);
}
}
export default connect(
mapStateToProps,
{
addToCart,
removeFromCart,
selectItem,
inputChange
}
)(MainPage);
combinedReducers:
import { combineReducers } from 'redux';
import AddItem from './addItem/reducer';
import InputReducer from './reducerInput';
export default combineReducers({
AddItem,
InputReducer
});
I've tried to this.props.inputVal.
Since you have combineReducers, you should use these keys to access in mapStateToProps.
From the redux docs:
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
You can control state key names by using different keys for the
reducers in the passed object. For example, you may call
combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
for the state shape to be { todos, counter }.
So your mapStateToProps must be like:
const mapStateToProps = state => {
console.log(state);
return {
inputVal: state.InputReducer.inputVal
}
};
A minimal working code sandbox:
https://codesandbox.io/s/cold-meadow-pxtu3

Redux action not dispatched. TypeError: Invalid attempt to spread non-iterable instance

In my application I want to add a 'ticket' to an array in the 'event' object. In the action I post the new ticket to the database, and after that I dispatch the action to the reducer. By using the Redux logger, I am able to retrieve the error:
The action of 'createTicket' is this:
// actions/tickets.js
export const TICKET_CREATE_SUCCESS = 'TICKET_CREATE_SUCCESS';
const ticketCreateSuccess = tickets => ({
type: TICKET_CREATE_SUCCESS,
tickets
});
export const createTicket = (eventId, data) => (dispatch, getState) => {
const jwt = getState().currentUser.token;
const id = getState().currentUser.userId;
const email = getState().currentUser.email;
const name = getState().currentUser.name;
request
.post(`${baseUrl}/events/${eventId}/tickets`)
.set('Authorization', `Bearer ${jwt}`)
.send(data)
.then(response => {
dispatch(ticketCreateSuccess({ ...response.body, user: { id, email, name } }));
})
.catch(error => error);
};
The reducer
// reducers/events.js
import { EVENT_FETCHED } from '../actions/events';
import { TICKET_EDIT_SUCCESS, TICKET_CREATE_SUCCESS } from '../actions/tickets';
export default (state = null, action = {}) => {
switch (action.type) {
case EVENT_FETCHED:
return action.event;
case TICKET_EDIT_SUCCESS:
return {
...state,
tickets: state.tickets.map(ticket => {
if (ticket.id === action.ticket.id) {
return action.ticket;
}
return ticket;
})
};
case TICKET_CREATE_SUCCESS:
console.log({ ...state, tickets: [...state.tickets, action.tickets] });
return { ...state, tickets: [...state.tickets, action.tickets] };
default:
return state;
}
};
The reducers are combined into :
import { combineReducers } from 'redux';
import currentUser from './currentUser';
import events from './events';
import event from './event';
import ticket from './ticket';
import tickets from './tickets';
import numberOfTickets from './numberOfTickets';
export default combineReducers({ currentUser, events, event, ticket, tickets, numberOfTickets });
Could it be that you're trying to spread your reducer state when its value is null:
export default (state = null, action = {}) => {
return {
...state, // Here
// rest
}
Your default state should probably be an object, e.g.:
const InitialState = {
tickets: []
};
export default (state = InitialState, action) => {
// Some code
case TICKET_CREATE_SUCCESS:
return {
...state,
tickets: [
...state.tickets,
action.tickets
]
}
}
just add this ...state || []
and you are good to go.
the problem is value of ...state equals null with empty array and when you try to iterate over null it creates an error.
so use and "OR" operator and it will work fine.

Action creators handling axios get.request with state access for param

I'm trying to pass some value from a component to a action creators which is doing a get request with axios. I'm trying to follow this pattern from Dan Abramov :
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
However I can't make it work. I think I have trouble on two level : my component and my action creator. Would be great to have some helps.
my component :
const timeR = ({
selectedTimeRange,
timeRange = [],
onTimeChange }) => {
return (
<div>
<div>
Filters:
<div>
Year:
<select
defaultValue={selectedTimeRange}
onChange={onTimeChange}>
<option value="all" >All</option>
{timeRange.map((y, i) =>
<option key={i} value={y}>{y}</option>
)}
</select>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
var range = ['30daysAgo', '15daysAgo', '7daysAgo'];
return {
selectedTimeRange: state.timeReducer.timerange[0],
timeRange: range
};
};
const mapDispachToProps = (dispatch) => {
return {
onTimeChange: (e) => {dispatch (onSetTimeRange(e.target.value));},
};
};
const TimeRange = connect(mapStateToProps, mapDispachToProps)(timeR);
export default TimeRange;
This component give me a dropdown menu. When selecting a timerange, for example '30daysAgo', it update my redux store state so I can access the value from my reducer.
Here is the action associated to my dropdown menu :
export function onSetTimeRange(timerange) {
return {
type: 'SET_TIME_RANGE',
timerange
}
}
and here is the action dealing with axios.get :
export const fetchgadata = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
var VIEW_ID = "ga:80820965";
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
type: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
My question :
How do I bring these two actions together. At the end I would like to be able, when doing onChange on my drop-down menu, to call a action with the value selected from my menu as a param for my axios.get request.
I feel like I need to nest two actions creators. I've tried this but doesn't work ("fetchgadata" is read-only error in my terminal)
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export function onSetTimeRange() {
return (dispatch, getState) => {
const {VIEW_ID} = getState().timerange;
dispatch(fetchgadata = (VIEW_ID) => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null,
id:VIEW_ID,
});
});
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
}
}
Edit:
reducers for API call :
const initialState = {data:null,isFetching: false,error:null};
export const gaData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
reducers for Drop-down :
const items = [{timerange: '30daysAgo'},{timerange: '15daysAgo'},{timerange: '7daysAgo'}]
const timeReducer = (state = {
timerange: items
}, action) => {
switch (action.type) {
case 'SET_TIME_RANGE':
console.log(state,action);
return {
...state,
timerange: action.timerange,
};
default:
return state;
}
}
I see a little typo in the catch of your axios.get request, it reads ype: FETCH_DATA_FAILURE. Otherwise, can you add in your reducer for me, I don't see it up there? If I understand correctly, you want one action to update two different pieces of state, in which case you would simply dispatch an action and add it to both reducers. Really it's best to just demonstrate:
//axios call
axios.get("some route", { some params } )
.then(response => {
dispatch({
type: UPDATE_TWO_THINGS,
payload: some_value
})
}) .... catch, etc
//reducer 1
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { userInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, userInfo: payload };
}
return state;
}
//reducer 2
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { businessInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, businessInfo: payload };
}
return state;
}
Hopefully this helps, but let me know if not, I'll do my best to get this working with you! Thanks for asking!

Resources