New to unit testing in general and especially in Redux, forgive the dumb question.
I'm trying to test a simple reducer but can't get to make it work.
Reducer:
import { ActionTypes } from "../constants/action-types";
const initialState = {
products: [],
};
export const productReducer = (state = initialState, { type, payload }) => {
switch (type) {
case ActionTypes.FETCH_PRODUCTS:
return { ...state, products: payload };
case ActionTypes.RESET_PRODUCTS:
return {};
default:
return state;
}
};
Test:
import { ActionTypes } from "../../redux/constants/action-types";
import { productReducer } from ".././../redux/reducers/productReducer";
describe("Product reducer", () => {
describe("fetching products", () => {
it("adds products", () => {
const action = {
type: ActionTypes.FETCH_PRODUCTS,
product: [{ name: "test" }],
};
const initialState = undefined;
const nextState = productReducer(initialState, action);
expect(nextState).toEqual([{ name: "test" }]);
});
});
});
That's what I get:
expect(received).toEqual(expected) // deep equality
Expected: [{"name": "test"}]
Received: {"products": undefined}
Just don't understand how to test it.
use payload instead of product
const action = {
type: ActionTypes.FETCH_PRODUCTS,
payload: [{ name: "test" }],
};
Related
I am trying to append / update some data in a state array inside of my slice reducers, but when I try to console.log the .projects array of the state that I am interested in I just get a javascript Proxy. What is going on here (what am I doing wrong)?
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
projects: [],
};
const projectsSlice = createSlice({
name: 'projectsSlice',
initialState: { ...initialState },
reducers: {
addProject(state, action) {
const { project } = action.payload;
const newProjects = [project, ...state.projects];
console.group('add project');
console.log('project: ', project);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
setProjects(state, action) {
const { projects } = action.payload;
state.projects = projects;
},
removeProject(state, action) {
const { projectId } = action.payload;
const newProjects = [...state.projects].filter((project) => project.id !== projectId);
state.projects = newProjects;
},
updateProject(state, action) {
const { project } = action.payload;
const projectIndex = state.projects.findIndex((stateProject) => stateProject.id === project.id);
const newProjects = [...state.projects].splice(projectIndex, 1, project);
console.group('updateProject');
console.log('project: ', project);
console.log('projectIndex: ', projectIndex);
console.log('state projects: ', state.projects);
console.log('newProjects: ', newProjects);
console.groupEnd();
state.projects = newProjects;
},
},
});
export const { addProject, removeProject, updateProject, setProjects } = projectsSlice.actions;
export default projectsSlice.reducer;
The Proxy there is the reason you can just mutate state in that reducer and just get an immutable copy in your state - but browsers are really bad at logging proxies.
per the docs, you can use the current export of RTK&immer:
const slice = createSlice({
name: 'todos',
initialState: [{ id: 1, title: 'Example todo' }],
reducers: {
addTodo: (state, action) => {
console.log('before', current(state))
state.push(action.payload)
console.log('after', current(state))
},
},
})
You need to use current
import { current } from '#reduxjs/toolkit'
and with that, you can reach the current state and work with them and after that, you can send a return in reducer to return new data.
Will looks like this:
const referralSlicer = createSlice({
name: 'referral',
initialState: {
referrals: [],
currentCard: 0,
} as IReferralSlicer,
reducers: {
addReferrals(state, { payload }) {
return {
referrals: [...state.referrals, payload],
}
},
deleteReferral(state, { payload }) {
const currentState = current(state)
return {
...currentState,
referrals: currentState.referrals.splice(payload, 1),
}
},
setCurrentCard(state, { payload }) {
return {
referrals: state.referrals,
currentCard: payload,
}
},
},
})
I am using React+Redux. I am getting an error:
While debugging, data from the server came to the state, and the action was empty:
This is part of my code:
class ProfileContainer extends React.Component {
componentDidMount() {
debugger
axios.get('https://social-network.samuraijs.com/api/1.0/profile/3')
.then(response => {
this.props.setUserProfile(response.data)
})
}
}
export default connect(mapStateToProps, { setUserProfile })(ProfileContainer);
My reducer:
const initialState = {
posts: [
{ id: 1, message: "Hey, how are you?", likesCount: 3 },
{ id: 2, message: "This is my first post", likesCount: 15 },
{ id: 3, message: "This is my first post", likesCount: 17 },
],
newPostText: "",
profile: null
};
const profileReducer = (state = initialState, action) => {
debugger
switch (action.type) {
case SET_USER_PROFILE:
return { ...state, profile: action.profile };
default:
return state;
}
};
export const setUserProfile = (profile) => ({ type: SET_USER_PROFILE, profile })
Initiate a dispatch, pass the result to the dispatch() function:
export const setUserProfile = (profile) => (dispatch({ type: SET_USER_PROFILE, profile }))
Your dispatch flow is wrong you have to do like this:
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
setUserProfile: (profile) => dispatch({ type: SET_USER_PROFILE, profile }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProfileContainer);
I don't understand why this error appears. I am trying to create profile feature that will get data with axios and will show it by userid. I tried a lot of things and also i did the same thing with other component and everything worked
ProfileContainer.js
import React from 'react';
import {connect} from 'react-redux';
import Profile from './Profile';
import setUserProfile from '../../redux/profile-reducer'
import * as axios from 'axios';
class ProfileContainer extends React.Component {
componentDidMount() {
axios.get(`https://social-network.samuraijs.com/api/1.0/profile/2`).then(response => {
this.props.setUserProfile(response.data);
});
};
render() {
return (
<Profile {...this.props} profile={this.props.profile} />
)
}
}
let mapStateToProps = (state) => ({
profile: state.profilePage.profile
});
export default connect(mapStateToProps,{setUserProfile})(ProfileContainer);
profile-reducer.js
const ADD_POST = 'ADD-POST'
const UPDATE_NEW_POST_TEXT = 'UPDATE-NEW-POST-TEXT'
const SET_USER_PROFILE = 'SET_USER_PROFILE';
let initialState = {
posts: [
{ id: 1, message: 'How are you bro?)', likesCount: 21312 },
{ id: 2, message: 'Have you ever been to Georgia?', likesCount: 31312312 },
],
newPostText: 'q',
profile: null
};
const profileReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_POST:
let newPost = {
id: 124331,
message: state.newPostText,
likesCount: 0
}
return { ...state, posts: [...state.posts, newPost], newPostText: '' }
case UPDATE_NEW_POST_TEXT:
return { ...state, newPostText: action.newText };
case SET_USER_PROFILE:
return { ...state, profile: action.profile };
default:
return state;
}
}
export const addPostActionCreator = () => ({ type: ADD_POST });
export const updateNewPostTextActionCreator = (text) => ({ type: UPDATE_NEW_POST_TEXT, newText: text });
export const setUserProfile = (profile) => ({ type: SET_USER_PROFILE, profile });
export default profileReducer;
You must use async/await in your code.
take a look at this article: https://medium.com/#matt.readout/using-javascripts-async-await-syntax-to-fetch-data-in-a-react-app-878b930cdc6f
you will find the same code there.
I am working on a React application and I am using Redux to store the state. I have the following the code:
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { ADD_CATEGORY, GET_MENU } from './menu.types';
export const getMenu = () => async dispatch => {
const response = await fetch(`${apiUrl}/menu`);
if (response.ok) {
const menuData = await response.json();
dispatch({ type: GET_MENU, payload: menuData })
}
}
export const addCategory = category => async dispatch => {
const options = {
...apiConfig(),
method: 'POST',
body: JSON.stringify(category)
};
const response = await fetch(apiUrl + '/category/', options)
let data = await response.json()
if (response.ok) {
dispatch({ type: ADD_CATEGORY, payload: { ...data } })
} else {
alert(data.error)
}
}
menu.reducer.js:
import { ADD_CATEGORY, GET_MENU } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return [...action.payload];
case ADD_CATEGORY:
return [ ...state, { ...action.payload, menuItem: [] } ]
default:
return state;
}
}
In the above Reducer, the initial state is an empty array. Dispatching the GET_MENU action, changes the initial state so that it contains an array of menu items instead.
The array that is fetched in the GET_MENU action is of the following:
I have modified the code in the Reducer function so that the initial state is now the following:
menu.reducernew.js:
import { ADD_CATEGORY, GET_MENU } from './menu.types';
const INITIAL_STATE = {
menuArray: [],
isSending: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_MENU:
return {
...state,
menuArray: action.payload
};
case ADD_CATEGORY:
return {
...state,
menuArray: [ ...menuArray, { ...action.payload, menuItem: [] } ]
};
default:
return state;
}
}
For the case ADD_CATEGORY in the Reducer, I am not sure what the correct syntax is for reassigning the menuArray property in the state to the modified array. I want the array to have the new object that is fetched within the addCategory action creator added to it.
When I run my application, I am getting the following error:
I am not sure why I am getting this error or how to resolve it. Any insights are appreciated.
Here menuArray: [ ...menuArray, { ...action.payload, menuItem: [] } menuArray variable is not defined which you are trying to spread. Use state.menuArray to access current menuArray.
Change:
menuArray: [ ...menuArray, { ...action.payload, menuItem: [] }
To:
menuArray: [ ...state.menuArray, { ...action.payload, menuItem: [] }
case ADD_CATEGORY:
return {
...state,
menuArray: [ ...state.menuArray, { ...action.payload, menuItem: [] } ]
};
So Im having an issue with a react/redux project. The program stops outputting information when the first onSetTechnicianXXX call is made. The interesting thing is I successfully perform multiple onSetGetData actions as well as an onSetTechnicianId (inside the parent file). I get no errors. Below is the debug code:
console.log("1");
const technician = fetchedTechnicians[0];
console.log("Tech.Intake.Name: " + technician.intakeData.name);
console.log("2");
const name = technician.intakeData.name;
console.log("Name: " + name);
this.onSetTechnicianName(name);
console.log("3");
this.onSetTechnicianName(technician.intakeData.name);
console.log("4");
this.onSetTechnicianCerts(technician.intakeData.certifications);
console.log("5");
this.onSetTechnicianPhone(technician.intakeData.phone);
console.log("6");
this.onSetTechnicianEmail(technician.intakeData.email);
console.log("7");
this.onSetTechnicianServicebay(technician.intakeData.servicebay);
console.log("8");
========Output from the Chrome DevTools Console========
1 TechnicianEditForm.js:118
Tech.Intake.Name: Joe Technician TechnicianEditForm.js:120
2 TechnicianEditForm.js:121
Name: Joe Technician TechnicianEditForm.js:123
========Output from the Chrome Redux DevTool=========
selectedTechnicianId(pin): "-LnECtv2Ms40OaFBu2t0"
getdata(pin): false
name(pin): ""
certifications(pin): ""
phone(pin): ""
email(pin): ""
servicebay(pin): ""
These are the React/Redux files for processing the state
===========actionTypes.js:==========================
export const SET_TECHNICIANID = 'SET_TECHNICIANID';
export const SET_GETDATA = 'SET_GETDATA';
export const SET_TECHNICIAN_NAME = 'SET_TECHNICIAN_NAME';
export const SET_TECHNICIAN_CERTS = 'SET_TECHNICIAN_CERTS';
export const SET_TECHNICIAN_PHONE = 'SET_TECHNICIAN_PHONE';
export const SET_TECHNICIAN_EMAIL = 'SET_TECHNICIAN_EMAIL';
export const SET_TECHNICIAN_SERVICEBAY = 'SET_TECHNICIAN_SERVICEBAY';
=================actions.js:===========================
import * as actionTypes from './actionTypes';
export const setTechnicianId = (selectedTechnicianId) => {
return {
type: actionTypes.SET_TECHNICIANID,
selectedTechnicianId: selectedTechnicianId
};
};
export const setGetData = (getdata) => {
return {
type: actionTypes.SET_GETDATA,
getdata: getdata
};
};
export const setTechnicianName = (name) => {
return {
type: actionTypes.SET_TECHNICIAN_NAME,
name: name
};
};
##### Others Not Shown But Simular #####
=================reducer.js:================
import * as actionTypes from './actionTypes';
import { updateObject } from './utility';
const initialState = {
selectedTechnicianId: '',
getdata: false,
name: '',
certifications: '',
phone: '',
email: '',
servicebay: ''
}
const baseReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SET_TECHNICIANID:
return updateObject(state, { selectedTechnicianId: action.selectedTechnicianId });
case actionTypes.SET_GETDATA:
return updateObject(state, { getdata: action.getdata });
case actionTypes.SET_TECHNICIAN_NAME:
return updateObject(state, { name: action.name });
##### Others Not Shown But Simular #####
=======================index.js:=====================
export {
setTechnicianId,
setGetData,
setTechnicianName,
setTechnicianCerts,
setTechnicianPhone,
setTechnicianEmail,
setTechnicianServicebay,
} from './actions';
===========Part of the TechnicianEditForm.js============
const mapStateToProps = state => {
return {
selectedTechnicianId: state.selectedTechnicianId,
getdata: state.getdata,
name: state.name,
certifications: state.certifications,
phone: state.phone,
email: state.email,
servicebay: state.servicebay
};
};
const mapDispatchToProps = dispatch => {
return {
onSetTechnicianId: (selectedTechnicianId) => dispatch(actionTypes.setTechnicianId(selectedTechnicianId)),
onSetGetData: (getdata) => dispatch(actionTypes.setGetData(getdata)),
onSetTechnicianName: (name) => dispatch(actionTypes.setTechnicianName(name)),
onSetTechnicianCerts: (certifications) => dispatch(actionTypes.setTechnicianCerts(certifications)),
onSetTechnicianPhone: (phone) => dispatch(actionTypes.setTechnicianPhone(phone)),
onSetTechnicianEmail: (email) => dispatch(actionTypes.setTechnicianEmail(email)),
onSetTechnicianServicebay: (servicebay) => dispatch(actionTypes.setTechnicianServicebay(servicebay)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(TechnicianEditForm), axios);