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?
Related
I am using the useEffect hook in a react a component to fire a redux action. This action makes a call to my api, but before this call happens I update the redux state, after the api call I again update the redux state. With the api call removed it works fine but with it included it causes an infinite loop
React component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { allEntries } from "../../redux/actions/allEntries";
export const EmployeeProfileTime = () => {
const dispatch = useDispatch();
const userId = useSelector((state) => state.userInfoReducer.userId);
const isAdmin = useSelector((state) => state.userInfoReducer.isAdmin);
useEffect(() => {
const userInfo = {
userId,
typeOfId: isAdmin ? "admin_id" : "user_id",
};
dispatch(allEntries(userInfo));
}, [userId, isAdmin, dispatch]);
return <div></div>;
};
import axios from "axios";
export const allEntries = (userInfo) => async (dispatch) => {
try {
dispatch({
type: "SET_USER_INFO_REQUEST",
});
await axios({
method: "get",
url: `userInfo is passed as param`,
}).then((response) => {
console.log(response);
});
dispatch({
type: "SET_USER_INFO_SUCCESS",
});
} catch (error) {
console.log(error);
}
};
Redux reducer
const intitialState = {
firstName: "",
lastName: "",
email: "",
image: "",
userId: "",
companyNumber: "",
companyName: "",
isAdmin: "",
date: "",
weeklyTimeEntries: [],
entriesToDisplay: [],
allEntries: [],
weeklyTime: "",
jobs: [],
isLoading: false,
error: null,
};
const userInfoReducer = (state = intitialState, action) => {
switch (action.type) {
case "SET_USER_INFO":
return {
...state,
firstName: action.payload.firstName,
lastName: action.payload.lastName,
email: action.payload.email,
image: action.payload.image,
userId: action.payload.userId,
companyNumber: action.payload.companyNumber,
isAdmin: action.payload.isAdmin,
date: action.payload.date,
};
case "SET_USER_INFO_REQUEST":
return {
...state,
isLoading: true,
};
case "SET_USER_INFO_SUCCESS":
return {
...state,
isLoading: false,
};
case "SET_USER_INFO_ERROR":
return {
...state,
error: action.error,
};
case "SET_USER_COMPANY_NUMBER":
return {
...state,
companyNumber: action.payload,
};
case "SET_USER_COMPANY_NAME":
return {
...state,
companyName: action.payload,
};
case "SET_USER_IS_ADMIN":
return {
...state,
isAdmin: 1,
};
case "SET_USER_IS_NOT_ADMIN":
return {
...state,
isAdmin: 0,
};
case "SET_USER_IMAGE":
return {
...state,
image: action.payload,
};
case "SET_USER_WEEKLY_TIME":
return {
...state,
weeklyTimeEntries: action.payload,
};
case "SET_USER_TOTAL_TIME":
return {
...state,
weeklyTime: action.payload,
};
case "SET_USER_DISPLAY_ENTRIES":
return {
...state,
entriesToDisplay: action.payload,
};
case "SET_USER_JOBS":
return {
...state,
jobs: action.payload,
};
case "SET_USER_ALL_ENTRIES":
return {
...state,
allEntries: action.payload,
};
default:
return state;
}
};
export default userInfoReducer;
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 } ] }
I have a user store that handles all the user actions, which include fetching user data and updating user information. I've also got a post store that fetches posts users have written and also, posts, updates, and deletes them.
When I update my user information and call my function:
export const putUserDataPending = () => {
return {
type: "PUT_USER_DATA_PENDING"
}
}
export const putUserDataFulfilled = user => {
return {
type: "PUT_USER_DATA_FULFILLED",
user: user
}
}
export const putUserDataRejected = error => {
return {
type: "PUT_USER_DATA_REJECTED",
error: error
}
}
export const putUserData = (username, email, countries, home) => {
const token = localStorage.getItem('token');
return dispatch => {
dispatch(putUserDataPending());
axios.put(
'http://localhost:8000/api/v1/rest-auth/user/',
{
username: username,
email: email,
countries: countries,
home: home
},
{headers: { 'Authorization': `Token ${token}`}}
)
.then(response => {
const user = response.data;
dispatch(putUserDataFulfilled(user));
})
.catch(err => {
dispatch(putUserDataRejected(err));
})
}
}
It rerenders the user profile page, but it doesn't rerender the author information because the state for the posts was never updated. What's the proper way to update any of the author objects in the posts state that may have been updated by updating the user?
Or is it just better to manually rerender the homepage?
My home page MapState is this:
const mapState = state => {
return {
fetched: state.tripReport.fetched,
fetching: state.tripReport.fetching,
tripReports: state.tripReport.tripReports,
showCountryModal: state.modal.showCountryModal,
modalCountry: state.modal.modalCountry
};
}
where tripReports is an array of objects, and some of the authors I want to update.
My editProfile MapState looks like this:
const mapState = state => {
return {
user: state.user.user,
userCountries: state.user.user.countries,
searchedCountry: state.country.country,
showProfileModal: state.modal.showProfileModal,
modalProfile: state.modal.modalProfile
};
}
Where the user object is the object getting updated.
Here is the post reducer:
const initialState = {
fetching: false,
fetched: false,
fetchingTripReports: false,
fetchedTripReports: false,
tripReports: [],
userTripReports: [],
error: null,
}
export default function (state = initialState, action) {
switch (action.type) {
case "FETCH_TRIP_REPORTS_PENDING": {
return {
...state,
fetching: true
}
}
case "FETCH_TRIP_REPORTS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
tripReports: action.tripReports
}
}
case "FETCH_TRIP_REPORTS_REJECTED": {
return {
...state,
fetching: false,
fetched: false,
error: action.error
}
}
case "FETCH_USER_TRIP_REPORTS_PENDING": {
return {
...state,
fetchingTripReports: true
}
}
case "FETCH_USER_TRIP_REPORTS_FULFILLED": {
return {
...state,
fetchingTripReports: false,
fetchedTripReports: true,
userTripReports: action.tripReports
}
}
case "FETCH_USER_TRIP_REPORTS_REJECTED": {
return {
...state,
fetchingTripReports: false,
fetchedTripReports: false,
error: action.error
}
}
case "POST_TRIP_REPORTS_PENDING": {
return {
...state,
}
}
case "POST_TRIP_REPORTS_FULFILLED": {
return {
...state,
// The new trip report must be added onto the array, then the array must be sorted by id.
userTripReports: [...state.userTripReports].concat(action.response).sort((a, b) => a.id < b.id),
tripReports: [...state.tripReports].concat(action.response).sort((a, b) => a.id < b.id)
}
}
case "POST_TRIP_REPORTS_REJECTED": {
return {
...state,
error: action.error
}
}
case "DELETE_TRIP_REPORTS_PENDING": {
return {
...state,
}
}
case "DELETE_TRIP_REPORTS_FULFILLED": {
return {
...state,
// The deleted post must be filtered out of the lists.
userTripReports: state.userTripReports.filter(tripReport => tripReport.id !== action.response.id),
tripReports: state.tripReports.filter(tripReport => tripReport.id !== action.response.id)
}
}
case "DELETE_TRIP_REPORTS_REJECTED": {
return {
...state,
error: action.error
}
}
case "UPDATE_TRIP_REPORTS_PENDING": {
return {
...state,
}
}
case "UPDATE_TRIP_REPORTS_FULFILLED": {
return {
...state,
// The old post must be filtered out, the updated post must be added, then the array must be sorted.
userTripReports: [...state.userTripReports].filter(tripReport => tripReport.id !== action.response.id).concat(action.response).sort((a, b) => a.id < b.id),
tripReports: [...state.tripReports].filter(tripReport => tripReport.id !== action.response.id).concat(action.response).sort((a, b) => a.id < b.id),
response: action.response
}
}
case "UPDATE_TRIP_REPORTS_REJECTED": {
return {
...state,
error: action.error
}
}
default:
return state
}
}
and here is the user reducer:
const initialState = {
fetching: false,
fetched: false,
adding: false,
added: false,
user: {},
error: null,
}
/* Reducer Function*/
export default function (state = initialState, action) {
switch (action.type) {
case "FETCH_USER_PENDING": {
return {
...state,
fetching: true
}
}
case "FETCH_USER_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
user: action.user
}
}
case "FETCH_USER_REJECTED": {
return {
...state,
fetching: false,
fetched: false,
error: action.error
}
}
case "PUT_USER_DATA_PENDING": {
return {
...state,
adding: true,
added: false,
}
}
case "PUT_USER_DATA_FULFILLED": {
return {
...state,
adding: false,
added: true,
user: action.user
}
}
case "PUT_USER_DATA_REJECTED": {
return {
...state,
adding: false,
added: false,
error: action.error
}
}
default:
return state
}
}
Where on the action 'PUT_USER_DATA_FULFILLED' I would like to update the tripReport state to rerender the homepage.
When I try to call any other action in redux it is setting one part of the state to it's initialState.
My root reducer looks like this
const rootReducer = combineReducers({
credentials: combineReducers({
cred,
user,
partner,
merchant,
bank,
error,
auth,
}),
preCredentials,
theme,
});
the part of the state that is being cleared is theme.
action dispatched
state diff
Why this actions that have anything to do with theme reducer can change its state.
Theme reducer
function theme(state = { ...INITIAL_THEME }, action) {
switch (action.type) {
case LOADING_THEME:
return {
...state,
isLoading: true,
};
case SAVE_THEME:
return {
...action.theme,
error: {
status: null,
message: '',
},
isLoading: false,
};
case CLEAR_THEME:
return INITIAL_THEME;
default:
return INITIAL_THEME;
}
}
reducer of dispatched action
function preCredentials(state = { ...INITIAL_STATE }, action) {
switch (action.type) {
case SAVE_USERNAME:
return { ...state,
user: { ...state.user,
fullName: action.fullName,
},
};
default:
return state;
}
}
function theme(state = { ...INITIAL_THEME }, action) {
switch (action.type) {
case LOADING_THEME:
return {
...state,
isLoading: true,
};
case SAVE_THEME:
return {
...state,
...action.theme,
error: {
status: null,
message: '',
},
isLoading: false,
};
case CLEAR_THEME:
return INITIAL_THEME;
default:
return state;
}
}
return state instead of initial state
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;
}
}