Is there a way to use createAsyncThunk to axios.put using reduxtoolkit? - javascript

Could someone direct me into the right direction to find out how to make a axios.put request using reduxtoolkit?
This is my projectsSlice file. The voteProject is where I am having problems. Im just trying hardcoded data for now to get this to work. When I run it without the arrow function it doesnt accept the data and throws an error saying <payloadtoolargeerror: request entity too large at readstream> when I run it as an arrow function as shown here it accepts the projects data but doesnt send the axios.put to db. Any help would be appreciated.
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from '../axios';
import data from './data'
export const getProjects = createAsyncThunk(
"projects/getProjects",
async () => {
const response = await axios.get('/api/v1/works')
return response.data.data
}
)
export const voteProject = (project) => createAsyncThunk(
"projects/voteProject",
async () => {
const response = await axios.put("/api/v1/works/61fe5ee76b924c82c53b7513", {upvote: [1,2]})
console.log("response: _____________________", response)
}
)
// change the state based on the called functio
export const projectsSlice = createSlice({
name: 'projects',
initialState: {
projects: null,
status: null,
},
reducers: {
setUpvote: (state, action) => {
const index = state.projects.findIndex((obj) => obj._id === action.payload.id);
// console.log("Payload: ", action.payload)
// console.log("Index: ", action.payload)
if (action.payload.isDownvote === false) {
return state.projects[index].upvote += 1;
} else if (action.payload.isDownvote === true) {
state.projects[index].upvote += 1;
state.projects[index].downvote -= 1;
}
},
setDownvote: (state, action) => {
const index = state.projects.findIndex((obj) => obj._id === action.payload.id);
if (action.payload.isUpvote === false) {
state.projects[index].downvote += 1;
} else if (action.payload.isUpvote === true) {
state.projects[index].downvote += 1;
state.projects[index].upvote -= 1;
}
},
setSubractVote: (state, action) => {
const index = state.projects.findIndex((obj) => obj._id === action.payload.id);
if (action.payload.voteType === "upvote") {
state.projects[index].upvote -= 1;
} else if (action.payload.voteType === "downvote") {
state.projects[index].downvote -= 1;
}
},
},
extraReducers: builder => {
builder
.addCase(getProjects.pending, (state, action) => {
state.status = 'loading';
})
.addCase(getProjects.fulfilled, (state, action) => {
state.status = 'success';
state.projects = action.payload;
})
.addCase(getProjects.failed, (state, action) => {
state.status = "failed";
})
}
})
//action creators are generated for each case reducer function
export const { setUpvote, setDownvote, setSubractVote } = projectsSlice.actions;
export default projectsSlice.reducer;

Edit your thunk function to
export const voteProject = createAsyncThunk(
'projects/voteProject',
async (data) => {
try {
const response = await axios.put("/api/v1/works/61fe5ee76b924c82c53b7513", data)
return response.data
} catch (err) {
// custom error
}
}
)
export const projectsSlice = createSlice({
name: 'projects',
initialState: {
projects: null,
status: null,
},
extraReducers: (builder) => {
builder.addCase(voteProject.fulfilled, (state, { payload }) => {
// payload is the data response from thunk above
// update your state
})
// add more case
}
})
In your component:
import { voteProject } from './your-slice'
import { useAppDispatch } from '../store'
const dispatch = useAppDispatch()
const onVoting = () => {
dispatch(voteProject({ // your data }))
.unwrap()
.then((originalPromiseResult) => {
// handle result here
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
Since Redux Toolkit v1.6 has RTK Query integrated in so you can use rtk-query to write date fetching.

Related

React app 'Slowing down' when i try to dispatch action inside firebase.auth().onAuthStateChanged() method

I am trying to implement Firebase Authentication in my React App.
I have buttons LogIn and LogOut, which are working correctly(i can console.log uid)
Redirecting is fine as well.
Then i tried to add new reducer 'user' : {uid: uid} or 'user': {} if logged out.
At this point my App doesn't want to run and browser shows the notification "A web page is slowing your browser".
Thats GitHub link
App.js:
import ReactDOM from "react-dom";
import Navigation from "../routers/Navigation";
import { firebase, database } from "../firebase/firebase";
import { startSetExpenses, setFiltersText } from "../redux/actions";
import { createBrowserHistory } from "history";
import { connect } from "react-redux";
import {logIn, logOut} from '../redux/actions'
let history = createBrowserHistory();
export function App(props) {
firebase.auth().onAuthStateChanged((user) => {
if (!user) {
props.dispatch(logOut())
history.push("/");
} else {
props.dispatch(logIn(user.uid))
if (history.location.pathname === "/") {
history.push("/dashboard");
}
}
});
return (
<div>
<h1>Expenses App</h1>
<Navigation history={history}/>
</div>
);
}
let mapStoreToProps = (dispatch) => {
return {
dispatch
}
}
export default connect(mapStoreToProps)(App)
RootReducer.js :
const initialState = {
filters: {
text: ""
},
expenses: [],
user: {}
};
function userReducer(state = {}, action) {
const {type, uid} = action
switch (type) {
case "LOGIN":
return {
uid: uid
}
case "LOGOUT":
return {}
default :
return state;
}
}
function expensesReducer(state = initialState.expenses, action) {
const { type,id, description, value, updates,expenses } = action;
switch (type) {
case "ADD_EXPENSE":
return [
...state,
{
id,
description,
value,
},
];
case "REMOVE_EXPENSE":
if (state.findIndex(expense => expense.id === id) < 0) throw new Error('index is not found')
return state.filter((expense) => expense.id !== id);
case "UPDATE_EXPENSE":
return state.map((expense) => {
return expense.id === id ? { ...expense, ...updates } : expense;
});
case "SET_EXPENSES":
if (expenses) {
return expenses
}
default:
return state;
}
}
function filtersReducer(state = initialState.filters, action) {
const {type, text} = action;
switch (type) {
case 'SET_FILTERS_TEXT':
return {...state, text}
default:
return state;
}
}
const rootReducer = combineReducers({
filters: filtersReducer,
expenses: expensesReducer,
user: userReducer
})
export default rootReducer
Actions.js:
import {database, firebase, googleProvider} from "../firebase/firebase";
export function logIn(uid) {
return (dispatch) => {
console.log('uid inside actionCreator', uid)
dispatch({
type: "LOGIN",
uid: uid
})
}
}
export function logOut() {
return (dispatch) => {
console.log('logged out')
dispatch({type: 'LOGOUT'})
}
}
export function startLogIn() {
return () => {
return firebase.auth().signInWithPopup(googleProvider)
}
}
export function startLogOut() {
return () => {
return firebase.auth().signOut()
}
}
export function addExpense({ id, description, value } = {}) {
return {
type: "ADD_EXPENSE",
id,
description,
value,
};
}
export const startAddExpense = (expense) => {
return (dispatch) => {
let newId = database.ref("expenses").push().key;
const { description, value } = expense;
return database
.ref("expenses/" + newId)
.set({ description, value })
.then(() => {
dispatch(
addExpense({
type: "ADD_EXPENSE",
id: newId,
description,
value,
})
);
});
};
};
export function removeExpense(id) {
return {
type: "REMOVE_EXPENSE",
id,
};
}
export const startRemoveExpense = (id) => {
return (dispatch) => {
return database
.ref("expenses/" + id)
.remove()
.then(() => {
console.log("removing expense with id : " + id);
dispatch(removeExpense(id));
})
.catch((error) => {
console.log(`Expense with id:${id} was not removed`);
console.log(error)
});
};
};
export function updateExpense(id, updates) {
return {
type: "UPDATE_EXPENSE",
id,
updates,
};
}
export const startUpdateExpense = (id, updates) => {
return (dispatch) => {
return database.ref('expenses/' + id)
.update(updates)
.then(() => {
dispatch(updateExpense(id, updates))
})
.catch((err) => {
console.log('Error with updating an expense from firebase')
console.log(err)
})
}
}
export function setFiltersText(text) {
return {
type: "SET_FILTERS_TEXT",
text,
};
}
export const setExpenses = (expenses) => {
return {
type: "SET_EXPENSES",
expenses: [...expenses],
};
};
export const startSetExpenses = () => {
return (dispatch) => {
//get expenses from database
//.then dispatch expenses to state with setExpenses
return database
.ref("expenses")
.once("value")
.then((snapshot) => {
const expensesObj = snapshot.val();
let expenses = [];
for (let property in expensesObj) {
expenses = [
...expenses,
{
id: property,
description: expensesObj[property].description,
value: expensesObj[property].value,
},
];
}
dispatch(setExpenses(expenses));
});
};
};

React-Redux: How to ensure that multiple fetch requests are all successfully made before an action is dispatched

I am working on a React application and I am using Redux to store the state. I have the following code:
category-arrows.component.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increaseCategoryRank, decreaseCategoryRank, fetchCategoryRanks } from '../../redux/menu/menu.actions';
import './category-arrows.styles.scss';
class CategoryArrows extends Component {
render() {
const { category } = this.props;
const categoryClicked = true;
return (
<div className="arrows-container">
<div className="up-arrow" onClick={
() => {
this.props.increaseCategoryRank(category, categoryClicked)
this.props.fetchCategoryRanks(this.props.menu);
}}></div>
<div className="category-rank">
<p>{category.rank}</p>
</div>
<div className="down-arrow" onClick={
() => {
this.props.decreaseCategoryRank(category, categoryClicked)
this.props.fetchCategoryRanks(this.props.menu);
}}></div>
</div>
)
}
}
const mapStateToProps = state => ({
menu: state.menu
})
export default connect(mapStateToProps, { increaseCategoryRank, decreaseCategoryRank, fetchCategoryRanks } )(CategoryArrows);
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK, FETCH_CATEGORY_RANKS } from './menu.types';
export const decreaseCategoryRank = (category, categoryClicked) => dispatch => {
dispatch({ type: DECREASE_CATEGORY_RANK, category, categoryClicked })
}
export const increaseCategoryRank = (category, categoryClicked) => dispatch => {
dispatch({ type: INCREASE_CATEGORY_RANK, category, categoryClicked })
}
export const fetchCategoryRanks = menu => async dispatch => {
console.log("Printing menu (fetch category ranks)");
console.log(menu);
menu.map(async (category) => {
console.log("PRINTING CATEGORY");
console.log(category.name);
console.log(category.rank);
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
} else {
alert(data.error)
}
});
dispatch({ type: FETCH_CATEGORY_RANKS, menu });
}
menu.types.js:
export const INCREASE_CATEGORY_RANK = "INCREASE_CATEGORY_RANK";
export const DECREASE_CATEGORY_RANK = "DECREASE_CATEGORY_RANK";
export const FETCH_CATEGORY_RANKS = "FETCH_CATEGORY_RANKS";
menu.reducer.js:
// import INITIAL_STATE from './menu.data';
import { INCREASE_CATEGORY_RANK, DECREASE_CATEGORY_RANK, FETCH_CATEGORY_RANKS } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
console.log("Went into increase category rank");
if(action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
var index = menuArray.map(category => category._id).indexOf(action.category._id);
//if it's the first element in array it won't move up
if(index === 0) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index - 1];
menuArray[index - 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return menuArray;
}
}
case DECREASE_CATEGORY_RANK: {
console.log("Went into decrease category rank");
if(action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
console.log(menuArray);
var index = menuArray.map(category => category._id).indexOf(action.category._id);
//if it's the last element in the array, it won't move down
if(index === menuArray.length - 1) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index + 1];
menuArray[index + 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return menuArray;
}
}
case FETCH_CATEGORY_RANKS:
return state;
default:
return state;
}
}
In my CategoryArrows component, clicking on the up-arrow div dispatches the increaseCategoryRank action followed by the fetchCategoryRanks action. Clicking down-arrow div dispatches the decreaseCategoryRank action followed by the fetchCategoryRanks action.
The increaseCategoryRank and decreaseCategoryRank actions changes the ranks of categories that are in the Redux state. The fetchCategoryRanks action, takes the menu array from the state, and sends data about the modified categories to the server through making fetch requests.
One issue that I am facing in my application is that the increaseCategoryRank or decreaseCategoryRank actions can dispatched be again (through clicking on either arrow div) before all of the fetch requests have been sent in fetchCategoryRanks. This results in incorrect information being sent to the server, which is then saved in a database.
I want my code to not allow the increaseCategoryRank/decreaseCategoryRank action to be dispatched again unless all of the fetch requests have successfully been sent to the server in the fetchCategoryRanks action.
However, I am not sure how to check whether all of the requests have been sent before the increaseCategoryRank/decreaseCategoryRank actions are dispatched. Any insights are appreciated.
Adding a "pending requests" state that you can block on could help.
menu.reducer.js - Update state slice to hold menu array and isPending flag, add new fetch category ranks success action type
const INITIAL_STATE = {
menus: [],
isPending: false // <-- add new pending flag to state slice
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case INCREASE_CATEGORY_RANK: {
console.log("Went into increase category rank");
if (action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
var index = menuArray
.map(category => category._id)
.indexOf(action.category._id);
//if it's the first element in array it won't move up
if (index === 0) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index - 1];
menuArray[index - 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return { ...state, menus: menuArray };
}
}
case DECREASE_CATEGORY_RANK: {
console.log("Went into decrease category rank");
if (action.categoryClicked === false) {
return state;
}
const menuArray = [...state];
console.log(menuArray);
var index = menuArray
.map(category => category._id)
.indexOf(action.category._id);
//if it's the last element in the array, it won't move down
if (index === menuArray.length - 1) {
return state;
} else {
const temp = menuArray[index];
menuArray[index] = menuArray[index + 1];
menuArray[index + 1] = temp;
var newrank = 0;
menuArray.forEach(category => {
category.rank = newrank++;
});
return { ...state, menus: menuArray };
}
}
case FETCH_CATEGORY_RANKS:
return { ...state, isPending: true }; // <-- set true when fetching ranks
case FETCH_CATEGORY_RANKS_COMPLETE:
return {
...state,
isPending: false, // <-- clear isPending to unblock UI
menus: [...state.menus, action.menu], // Wasn't sure about this menu the fetchCategoryRanks function dispatches when complete
};
default:
return state;
}
};
menu.actions.js - dispatch an action that the fetch requests are being made, and dispatch the fetch completion action when finished
export const fetchCategoryRanks = menu => async dispatch => {
console.log("Printing menu (fetch category ranks)");
console.log(menu);
dispatch({ type: FETCH_CATEGORY_RANKS }); // <-- trigger isPending to block UI
menu.map(async category => {
console.log("PRINTING CATEGORY");
console.log(category.name);
console.log(category.rank);
const options = {
...apiConfig(),
method: "PUT",
body: JSON.stringify(category)
};
const response = await fetch(`${apiUrl}/category/${category._id}`, options);
let data = await response.json();
if (response.ok) {
console.log("It got sent");
} else {
alert(data.error);
}
});
dispatch({ type: FETCH_CATEGORY_RANKS_COMPLETE, menu });
};
category-arrow.component.jsx - map isPending to props and check before dispatching redux actions
class CategoryArrows extends Component {
render() {
const { category, isPending, menu } = this.props;
const categoryClicked = true;
return (
<div className="arrows-container">
<div
className="up-arrow"
onClick={() => {
if (!isPending) {
this.props.increaseCategoryRank(category, categoryClicked);
this.props.fetchCategoryRanks(menu);
}
}}
/>
<div className="category-rank">
<p>{category.rank}</p>
</div>
<div
className="down-arrow"
onClick={() => {
if (!isPending) {
this.props.decreaseCategoryRank(category, categoryClicked);
this.props.fetchCategoryRanks(menu);
}
}}
/>
</div>
);
}
}
const mapStateToProps = state => ({
isPending: state.menu.isPending,
menu: state.menu.menus,
});

Redux proper send of payload

I'm new to Redux. And I'm trying to create a simple FETCH_ALL_POSTS.
actions
export const fetchPosts = () => async dispatch => {
const response = await jsonPlaceholder.get('/posts');
console.log(response.data)
dispatch({
type: FETCH_ALL_POSTS,
payload: response.data
})
}
posts reducer
export default (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case FETCH_ALL_POSTS:
return {
...state, payload
}
default:
return state
}
}
post list component
const mapStateToProps = state => {
console.log(Object.values(state.posts))
return {
posts: state.posts
}
}
This is working but the data that I'm getting from mapStateToProps is not what I'm expecting.
Result : "array: [ 0:[{},{},{}] ]"
My expected result: "array:[{},{},{}]"
Try this,
const initialState = {
posts: '',
}
export default (state=initialState, action) => {
const { type, payload } = action;
switch (type) {
case FETCH_ALL_POSTS:
return{
posts:state.posts=action.payload.posts
}
default:
return state
}
}

data is fetched but the state is not updated

I'm fetching data from an endpoint. But the state is not updated. it's always undefined.
For some reason this.props.users is undefined. Am I doing something wrong?
After componentDidMount() I trigger the action fetchUsers that send a request to the endpoint. The data is fetched successfully but at the end the state is not updated.
This is my Layout component
class Layout extends Component {
render() {
return (
<div className="container">
{
this.props.users.map((user, key) => {
return <a className="list-group-item list-group-item-action active">User #{user.id}</a>
})
}
</div>
)
}
}
const mapStateToProps = state => {
return {
channels: state.users.data,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(user.fetchUsers()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Layout);
This the action file
export const fetchUsers = () => {
return (dispatch, getState) => {
let headers = { "Content-Type": "application/json" };
return fetch("http://127.0.0.1:3030/api/users/", { headers, })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return { status: res.status, data };
})
} else {
console.log("Server Error!");
throw res;
}
})
.then(res => {
if (res.status === 200) {
dispatch({ type: 'USERS_FETCHED', data: res.data });
return res.data;
}
})
}
}
And this is the reducer
const initialState = {
users: []
};
export default function channels(state = initialState, action) {
switch (action.type) {
case 'USERS_FETCHED':
return { ...state, users: action.data };
default:
return state;
}
}
I think the error comes from your call to the dispatcher in the mapDispatchToProps. Since you are exporting directly the function fetchUsers, you should not be calling user.fetchUsers.
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(fetchUsers()),
}
}

Redux doesn't update state

I have a problem with Redux doesn't update the state. Component gets right initial state. Action is dispatched right, data is fetched right and is accesible in action payload inside reducer. Reducer is executing, right case in switch is picked. Just new state doesn't appear in component. I have three others components where it works just fine, only this one cracks.
component
import fetchLinksPage from '../state/links/actions'
...
let Links = ({linksPageLoaded, linksPage, fetchLinksPage}) => {
useEffect( () => {
if(!linksPageLoaded) {
fetchLinksPage()
console.log(linksPage)
}
},[])
return ( ... )
}
const mapStateToProps = ({linksPageReducer}) => {
return linksPageReducer
}
const mapDispatchToProps = dispatch => {
return {
fetchLinksPage: () => dispatch(fetchLinksPage())
}
}
Links = connect(mapStateToProps, mapDispatchToProps)(Links)
actions
// action types
export const GET_LINKS_PAGE = 'GETLINKSPAGE'
export const LINKS_PAGE_LOADED = 'LINKSPAGELOADED'
export const LINKS_PAGE_ERROR = 'LINKSPAGEERROR'
// action creators
export const getLinksPage = () => {
return {
type: GET_LINKS_PAGE
}
}
export const linksPageLoaded = (data) => {
return {
type: LINKS_PAGE_LOADED,
payload: data
}
}
export const linksPageError = (error) => {
return {
type: LINKS_PAGE_ERROR,
payload: error
}
}
const fetchLinksPage = () => {
return dispatch => {
dispatch(getLinksPage())
fetch('http://portfolio.adamzajac.info/_/items/links?fields=*,logo.*.*')
.then(response => response.json())
.then(data => {
dispatch(linksPageLoaded(data.data))
})
.catch( error => {
dispatch(linksPageError(error))
})
}
}
export default fetchLinksPage
reducer
import * as actions from './actions.js'
const linksPageReducer = (state={}, action) => {
switch (action.type) {
case actions.GET_LINKS_PAGE:
return { ...state, linksPageLoading: true }
case actions.LINKS_PAGE_LOADED:
//console.log('update state')
return { ...state, linksPage: action.payload, linksPageLoading: false, linksPageLoaded: true }
case actions.LINKS_PAGE_ERROR:
return { ...state, linksPageError: action.payload, linksPageLoading: false}
default:
return { ...state, linksPageLoading: false, linksPageLoaded: false, linksPage:[], linksPageError:''}
}
}
export default linksPageReducer
store
import aboutPageReducer from './state/about/reducer'
import projectsPageReducer from './state/projects/reducer'
import skillsPageReducer from './state/skills/reducer'
import linksPageReducer from './state/links/reducer'
const rootReducer = combineReducers({
aboutPageReducer,
projectsPageReducer,
skillsPageReducer,
linksPageReducer
})
const store = createStore(
rootReducer,
applyMiddleware(thunk)
)

Categories