I am using react and redux to write a todolist app.
Here is my initial state:
export const initialState = {
todoList:[
{
taskId:1
task: 'gym'
completed: true
},
{
taskId:2
task: 'buy dinner'
completed: false
}
]
}
const todoReducer = (state=initialState,action) => {
switch(action.type){
case UPDATE_TODO:
return Object.assign({}, state, {
todoList: state.todoList.map(todo =>
{
return todo.taskId === action.payload.taskId ?
Object.assign({},todo, {
task: action.payload.task,
completed: action.payload.completed
}) : todo
}
)
})
default:
return state;
}
}
export default todoReducer;
If I want to update the second task to 'buy book' and change completed to true, how can I add code in my reducer? The code above is not working now. Not sure the reason. Could anyone help?
Try this:
reducer
const initialState = {
todoList: [
{
taskId: 1,
task: "gym",
completed: true
},
{
taskId: 2,
task: "buy dinner",
completed: false
}
]
};
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_TODO':
const { payload } = action;
return {
...state,
todoList: state.todoList.map(item => {
if (item.taskId === payload.taskId) {
return {
...item,
task: payload.task,
completed: payload.completed
};
} else {
return item;
}
})
};
default:
return state;
}
};
Unit test:
const output = todoReducer(initialState, {type: 'UPDATE_TODO', payload: {
taskId: 1,
task: "newTask",
completed: true
}});
console.log('output', output);
test output:
output { todoList:
[ { taskId: 1, task: 'newTask', completed: true },
{ taskId: 2, task: 'buy dinner', completed: false } ] }
Related
This is a Redux issue.
Take a look at the logic that dispatched this action: {type: 'users/handleverification/fulfilled', payload: {…}, meta: {…}}
export const UserValidation = createAsyncThunk( //This Function should move to another folder
'users/handleverification',
async(thunkAPI) => {
try{
//Logic
if(Authenticated.status === 201){
localStorage.setItem('token', Authenticated.data.token);
delete Authenticated.token;
return Authenticated
console.log("Test me",Authenticated.data);
}
}
catch(error){
window.alert("error");
if(NewUser){
//delete User????????
}
}
}
)
Here is the Create Slice function:
export const userSlice = createSlice({
name: 'user',
initialState: {
UserData: {},
isFetching: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
reducers: {
clearState: (state) => {
state.isError = false;
state.isSuccess = false;
state.isFetching = false;
return state;
},
},
extraReducers: {
[UserValidation.fulfilled]:(state,{payload}) => {
// console.log("Fullfilled:")
},
[UserValidation.pending]:(state) => {
// console.log("Pending");
},
[UserValidation.rejected]:(state,{payload}) => {
// console.log("Rejected");
},
}
})
Why do I get the error:
A non-serializable value was detected in an action, in the path: payload.config.adapter. Take a Look at the action 'users/handleverification'
I am working on unit test for my reducer and I don't know what I am doing wrong here. The error I am getting in the console is that Matcher error: received value must be a non-null object and received value is undefined.
Reducer
import uuid from "uuid/v4";
import {
ADD_TODO,
REMOVE_TODO,
TOGGLE_TODO,
EDIT_TODO
} from "../constants/Actions";
const reducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return [...state, { id: uuid(), task: action.task, completed: false }];
case REMOVE_TODO:
return state.filter(todo => todo.id !== action.id);
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ? { ...todo, task: action.task } : todo
)
default:
return state;
}
};
export default reducer;
And my test is.
import { useReducer } from "react";
import { act, renderHook } from "#testing-library/react-hooks";
import { reducer } from "../src/reducers/Todo";
import { ADD_TODO } from '../src/constants/Actions'
describe("Todo reducer tests", () => {
it("Add Todo", () => {
const { result } = renderHook(() => useReducer(reducer, []));
const [state, dispatch] = result.current;
act(() => {
dispatch({ type: ADD_TODO })
});
expect(state).toMatchObject({
id: 1,
task: 'Hello',
completed: true,
});
});
});
I recommend you to check redux offical how to write test correctly
this help me a lot and they explain for every stuff you need
here is my code where I use like redux show.
import reducer from './reducer';
import * as actionTypes from '../Actions/actionTypes';
describe('reducer CheclUp', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({
favorite: [],
SearchPlaces: [],
});
});
it('should remove favorite and move it to SearchPlaces', () => {
expect(reducer({
favorite: [{Key: "1"}],
SearchPlaces: [],
}, {
type: actionTypes.REMOVE_FAVORITE,
payload: "1"
})).toEqual({
favorite: [],
SearchPlaces: [{Key: "1"}],
});
});
it('should remove SearchPlaces and move it to favorite', () => {
expect(reducer({
favorite: [],
SearchPlaces: [{Key: "1"}],
}, {
type: actionTypes.ADD_EXISTED_TO_FAV,
payload: "1"
})).toEqual({
favorite: [{Key: "1"}],
SearchPlaces: [],
});
});
it('should remove from SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [{Key: "1"}],
}, {
type: actionTypes.REMOVE_SEARCHED_PLACE,
payload: "1"
})).toEqual({
favorite: [],
SearchPlaces: [],
});
});
it('should add to SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [],
}, {
type: actionTypes.ADD_SEARCHED_PLACE,
payload: [{Key: "1"}]
})).toEqual({
favorite: [],
SearchPlaces: [{Key: "1"}],
});
});
it('should remove from SearchPlaces', () => {
expect(reducer({
favorite: [],
SearchPlaces: [],
}, {
type: actionTypes.ADD_FAVORITE,
payload: [{Key: "1"}]
})).toEqual({
favorite: [{Key: "1"}],
SearchPlaces: [],
});
});
});
Your state starts off as undefined because you don't have a default state set. Your reducer hits the default case and returns state, which isn't set in your first reducer run. I recommend setting a default state in your reducer:
const reducer = (state = [], action) => {
You can also pass in an initial state instead:
const { result } = renderHook(() => useReducer(reducer, []));
I have started to create my application using #reduxjs/toolkit and kind of got stuck. I find no resource anywhere which can guide me with how to unit tests the logic in extraReducers. Any help would be appreciable.
Example:
Example:
const fetchList = createAsyncThunk('example/fetchList', async ({skip, reset, ...rest}) => {
const request = {
skip: reset ? initialState.skip : skip,
...rest,
};
return await getList(request);
});
const exampleSlice = createSlice({
name: 'example',
initialState: {id: '', list: []},
reducers: {
resetParams() {
return {id: '', list: []}
},
setId(state, {payload}) {
state.id = payload.id
}
},
extraReducers: {
[fetchList.pending]: (state) => {
state.fetching = true;
},
[fetchList.fulfilled]: (state, {payload = []}) => {
return {
fetching: false,
id: state.id + 1,
list: payload
}
},
[fetchList.rejected]: (state, {error}) => {
state.fetching = false;
},
},
});
//Tests .. for setId()
const initialState = {
id: 1,
list : []
}
const result = exampleSlice.reducer(initialState, exampleSlice.actions.setId({id: 10}))
expect(result.id).toEqual(10)
How can I test logic in extraReducers for fetchList.fulfilled and fetchList.rejected!
You can test it the same way as what you've shown.
Here's a simple way to just test the logic of the reducer based on the action types that createAsyncThunk outputs.
import reducer, {
fetchList
} from './exampleSlice';
describe('exampleSlice', () => {
describe('reducers', () => {
const initialState = { id: '', list: [], fetching: false }
it('sets fetching true when fetchList is pending', () => {
const action = { type: fetchList.pending.type };
const state = reducer(initialState, action);
expect(state).toEqual({ id: '', list: [], fetching: true });
});
it('sets the id and list when fetchList is fulfilled', () => {
const action = { type: fetchList.fulfilled.type, payload: { id: 1, list: [2, 3]} };
const state = reducer(initialState, action);
expect(state).toEqual({ id: 1, list: [2, 3], fetching: false });
});
it('sets fetching false when fetchList is rejected', () => {
const action = { type: fetchList.rejected.type, payload: { error: 'some error' } };
const state = reducer(initialState, action);
expect(state).toEqual({ id: '', list: [], fetching: false });
});
});
});
I also threw up a simple example of the same concept on a demo CSB: https://codesandbox.io/s/rtk-14-addmatcher-counter-test-l11mt?file=/src/features/counter/counterSlice.test.ts
In a reducer
The incoming action.payload.balances shows the correct information, however it doesn't update the physical balances piece of state
I'd also like to note that when I'm trying to send a transaction it will call getBalances again & know that I have less than what the UI displays, which is confusing as to why the information isn't updating directly to the balances variable
// Sequence of events (all of these are in different files of course)
// Action Call
export const getBalances = exchange =>
action(actionTypes.GET_BALANCES.REQUEST, { exchange })
// Request
case GET_BALANCES.REQUEST:
return { ...state, status: 'loading' }
// Saga
export function* getBalances(action) {
getBalanceCount++
const state: storeType = yield select()
yield fork(async, action, API.getBalances, {
exchange: state.exchanges.selectedExchange._id,
userId: state.auth.userId,
})
if (getBalanceCount > 1) {
getBalanceCount--
return
}
yield delay(10000)
if (state.auth.token && state.auth.status === 'success')
yield put({ type: action.type, payload: {} })
}
// API Call
export const getBalances = ({ userId, exchange }) =>
API.request(`/wallets/${userId}/${exchange}`, 'GET')
// Sets balances to the payload.balances
case GET_BALANCES.SUCCESS:
return {
...state,
balances: action.payload.balances,
status: 'success',
}
Entire reducer file
import { mergeDeepRight } from 'ramda'
import {
GET_BALANCES,
GET_EXCHANGES,
SELECT_EXCHANGE,
GET_SYMBOL_PRICE_TICKER,
GET_DAY_CHANGE_TICKER,
GET_FRIEND_EXCHANGES,
ADD_EXCHANGE,
} from '../action-types/exchanges.action-types'
import { LOG_OUT, VALIDATE_TOKEN } from '../action-types/login.action-types'
import { ExchangeService } from '../constants/types'
// Exchanges Reducer
export type exchangeState = {
status: string
_id: string
label: string
displayName: string
dayChangeTicker: any
symbolPriceTicker: any
balances: any,
}
export type exchangesState = {
status: string
selectedExchange: exchangeState
addExchange: {
status: string,
}
exchanges: Array<ExchangeService>
friendExchanges: Array<ExchangeService>,
}
const initialExchangeState: exchangeState = {
status: 'pending',
_id: '',
label: '',
displayName: null,
dayChangeTicker: {},
symbolPriceTicker: {},
balances: {},
}
const initialState: exchangesState = {
status: 'pending',
selectedExchange: {
status: 'pending',
_id: '',
label: '',
displayName: null,
dayChangeTicker: {},
symbolPriceTicker: {},
balances: {},
},
addExchange: {
status: 'pending',
},
exchanges: [],
friendExchanges: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_EXCHANGE:
case GET_SYMBOL_PRICE_TICKER.SUCCESS:
case GET_DAY_CHANGE_TICKER.SUCCESS:
case GET_BALANCES.REQUEST:
case GET_BALANCES.SUCCESS:
case GET_BALANCES.FAILURE:
return { ...state, selectedExchange: selectedExchangeReducer(state.selectedExchange, action) }
case GET_EXCHANGES.REQUEST:
case GET_FRIEND_EXCHANGES.REQUEST:
return { ...state, status: 'loading' }
case GET_EXCHANGES.SUCCESS:
if (action.payload.exchanges.length > 0) {
return mergeDeepRight(state, {
exchanges: action.payload.exchanges,
selectedExchange: { ...action.payload.exchanges[0] },
status: 'success',
})
}
return { ...state, status: 'success' }
case GET_FRIEND_EXCHANGES.SUCCESS:
return { ...state, friendExchanges: action.payload.exchanges, status: 'success' }
case GET_EXCHANGES.FAILURE:
case GET_FRIEND_EXCHANGES.FAILURE:
return { ...state, message: action.payload.message, status: 'failure' }
case LOG_OUT.SUCCESS:
case VALIDATE_TOKEN.FAILURE:
return initialState
case ADD_EXCHANGE.REQUEST:
return { ...state, addExchange: { status: 'loading' } }
case ADD_EXCHANGE.SUCCESS:
return { ...state, addExchange: { status: 'success' } }
case ADD_EXCHANGE.FAILURE:
return { ...state, addExchange: { status: 'failure' } }
default:
return state
}
}
const selectedExchangeReducer = (state = initialExchangeState, action) => {
switch (action.type) {
case SELECT_EXCHANGE:
if (action.payload.exchange) {
return { ...state, ...action.payload.exchange }
}
return initialExchangeState
case GET_SYMBOL_PRICE_TICKER.SUCCESS:
const symbolPriceTicker = action.payload.data.data.reduce((result, ticker) => {
result[ticker.symbol] = ticker.price
return result
}, {})
return { ...state, symbolPriceTicker }
case GET_DAY_CHANGE_TICKER.SUCCESS:
const dayChangeTicker = action.payload.data.data.reduce((result, ticker) => {
result[ticker.symbol] = ticker.priceChangePercent
return result
}, {})
return { ...state, dayChangeTicker }
// Get selected exchange's balances
case GET_BALANCES.REQUEST:
return { ...state, status: 'loading' }
case GET_BALANCES.SUCCESS:
debugger
return {
...state,
balances: action.payload.balances,
status: 'success',
}
case GET_BALANCES.FAILURE:
return { ...state, balances: [], message: action.payload.message, status: 'failure' }
default:
return state
}
}
action.balances.payload: https://imgur.com/a/HuIKPT3
state.balances: https://imgur.com/a/GlzHg7v
I'd expect the payload to update the balances, however it simply isn't doing that
Is everything formatted in a manner that would do this correctly? Or is likely a JSON formatting issue?
In my component I want to check when the parameter has changed and update accordingly. However when I do this, I am seeing weird behaviour and multiple requests been made to my api.
my component:
componentWillMount() {
this.state = {
path: this.props.match.params.categoryName,
};
}
componentDidUpdate(prevProps) {
if (prevProps === undefined) {
return false;
}
if (this.state.path !== this.props.match.params.categoryName) {
this.getCategory()
}
}
getCategory() {
if (this.props.allPosts && this.props.allPosts.length) {
const categoryId = _.result(_.find(this.props.allPosts, v => (
v.name === this.props.match.params.categoryName ? v.id : null
)), 'id');
this.props.dispatch(Actions.fetchCategory(categoryId));
}
}
my action:
import Request from 'superagent';
import Constants from '../constants';
const Actions = {
fetchCategory: categoryId => (dispatch) => {
dispatch({ type: Constants.CATEGORY_FETCHING });
Request.get(`/api/v1/categories/${categoryId}`)
.then((data) => {
dispatch({
type: Constants.CATEGORY_RECEIVED,
category: { id: data.body.id, name: data.body.name },
category_posts: data.body.posts,
});
});
},
};
export default Actions;
my reducer:
import Constants from '../constants';
const initialState = {
posts: [],
category: [],
fetching: true,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.CATEGORY_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.CATEGORY_RECEIVED:
return Object.assign({}, state, { category: action.category,
posts: action.category_posts,
fetching: false });
default:
return state;
}
}