Redux toolkit alert removal setTimeout - javascript

I'm totally new to the redux-toolkit and still learning it, I'm kind of blocked at this step as I don't know how to implement it with redux-toolkit.
I have a system of toasts build in my redux store and this was my action.
MY Action
const setAlert = (msg, alertType, timeout = 5000) => (dispatch) => {
const id = uuidv4();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id },
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};
My Old reducer
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}
I was thinking about creating a react component to render when an alert array is longer than 0 with useEffect but I think it would be overkill.
I was thinking also about creating createAsyncThunk action but I need to return the value of the alert so I can't set setTimeout as the function would return.
Is there a way to get the dispatch function in the reducer so it would dispatch removeAlert after timeout?
const initialState: [] = [];
const alertSlice = createSlice({
name: 'alert',
initialState,
reducers: {
setAlert(state, action) {
const id = uuidv4();
[...state, action.payload];
toast[action.payload.alertType](msg);
setTimeout(() => dispatch(removeAlert(id)), timeout);
},
removeAlert(state, action) {
return state.filter((alert) => alert.id !== action.payload);
},
},
});

I resolved it with hand-written thunk
https://redux.js.org/usage/writing-logic-thunks#async-logic-and-side-effects
const initialState: [] = [];
const alertSlice = createSlice({
name: 'alert',
initialState,
reducers: {
sA(state, action) {
[...state, action.payload];
toast[action.payload.alertType](action.payload.msg);
},
rA(state, action) {
return state.filter((a) => a.id !== action.payload.id);
},
},
});
export const { sA, rA } = alertSlice.actions;
export function alert(msg, alertType, timeout = 5000) {
return async (dispatch, getState) => {
const id = uuidv4();
const obj = {
msg,
alertType,
timeout,
id,
};
dispatch(sA(obj));
setTimeout(() => dispatch(rA(id)), timeout);
};
}
export default alertSlice;

Related

How to reload data from api in Redux

when I lose in a hangman I want to reload my data in the API so that a new password appears. Unfortunately I have no idea how to reload it without reloading the whole page, is it even possible to run the api again on a button click for example?
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
const url = 'https://random-word-api.herokuapp.com/word?number=1';
const initialState = {
password: '1',
misstake: 0,
usedChart: [],
};
export const getPassword = createAsyncThunk(
'hangman/getPassword',
async (thunkAPI) => {
console.log(1)
try {
const resp = await axios(url)
return resp.data
} catch(error){
return thunkAPI.rejectWithValue('api not working');
}
}
);
const HangManSlice = createSlice({
name: 'hangman',
initialState,
reducers: {
increaseError: (state) => {
state.misstake += 1
},
usedCharts: (state, action) => {
state.usedChart.push(action.payload)
},
restart: (state) => {
state.misstake = 0
state.usedChart = []
getPassword()
}
},
extraReducers: (builder) => {
builder
.addCase(getPassword.fulfilled, (state, action) => {
state.password = action.payload;
})
}
})
export const { increaseError, usedCharts, restart } = HangManSlice.actions
export default HangManSlice.reducer
You should dispatch the getPassword() from the "restart" button handler instead of calling it your reducer.
const RestartButton = () => {
const dispatch = useDispatch();
const restartHandler = () => {
dispatch(getPassword());
dispatch(restart())
};
return (
<button onClick={restartHandler}>Restart</button>
);
}
Another option is to use the getPassword thunk to initialize a new game like so:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
const url = 'https://random-word-api.herokuapp.com/word?number=1';
const initialState = {
loading: false,
password: '1',
misstake: 0,
usedChart: [],
};
export const startGame = createAsyncThunk(
'hangman/getPassword',
async (thunkAPI) => {
console.log(1)
try {
const resp = await axios(url)
return resp.data
} catch(error){
return thunkAPI.rejectWithValue('api not working');
}
}
);
const HangManSlice = createSlice({
name: 'hangman',
initialState,
reducers: {
increaseError: (state) => {
state.misstake += 1
},
usedCharts: (state, action) => {
state.usedChart.push(action.payload)
},
},
extraReducers: (builder) => {
builder
.addCase(startGame.pending, (state, action) => {
state.loading = true;
})
.addCase(startGame.rejected, (state, action) => {
state.loading = false;
})
.addCase(startGame.fulfilled, (state, action) => {
state.loading = false;
// store new password
state.password = action.payload;
// reset state
state.misstake = 0
state.usedChart = []
})
}
})
export const { increaseError, usedCharts, restart } = HangManSlice.actions
export default HangManSlice.reducer
Now you can dispatch startGame(), and the state will be reset with a new password.

Update client state received from api without refreshing in redux

I have two functions, one where I am able to send an order that updates the users balance amongst some other things, and another which retrieves the users balance for the user to see. Before any orders happen I still need to retrieve the balance for the user to see, thus I have broken my getBalance func from MarketLongFunc.
Using redux-toolkit and redux-thunk I have an ordersSlice.js that looks like this:
export const MarketLongFunc = createAsyncThunk(
"/order/marketlong",
async (value, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
const newObj = {
value: value,
token: token,
};
let url = `http://localhost:3001/api/orders/marketlong`;
const response = await axios.post(url, newObj);
//getBalance()
return;
}
);
export const getBalance = createAsyncThunk(
"/order/getBalance",
async (value, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
const newObj = {
token: token,
};
let url = `http://localhost:3001/api/orders/getBalance`;
const response = await axios.post(url, newObj);
return response.data;
}
);
const initialState = {
value: null,
error: null,
balance: null,
status: "idle",
orderStatus: "idle",
};
export const ordersSlice = createSlice({
name: "orders",
initialState,
reducers: {
reset: (state) => initialState,
resetStatus: (state) => {
state.orderStatus = "idle";
},
},
extraReducers(builder) {
builder
.addCase(MarketLongFunc.pending, (state, action) => {
state.orderStatus = "loading";
})
.addCase(MarketLongFunc.fulfilled, (state, action) => {
state.orderStatus = "success";
// getBalance();
// state.balance = action.payload;
})
.addCase(MarketLongFunc.rejected, (state, action) => {
state.orderStatus = "failed";
state.error = action.error.message;
})
.addCase(getBalance.pending, (state, action) => {
state.status = "loading";
})
.addCase(getBalance.fulfilled, (state, action) => {
// state.status = "success";
state.balance = action.payload;
state.status = "success";
})
.addCase(getBalance.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const { reset } = ordersSlice.actions;
export default ordersSlice.reducer;
Now in my next component the useEffect will call if there is no balance yet and the user is logged in. The way in which I was trying to solve my issue was to use the state.orderStatus = "success" under MarketLongFunc.fulfilled, this way hypothetically I can dispatch getbalance under the useEffect if a MarketLong is placed and then change the status with reset like the following:
export const Orderform = () => {
const user = useSelector((state) => state.auth.user);
const balance = useSelector((state) => state.orders.balance);
const status = useSelector((state) => state.orders.orderStatus);
const dispatch = useDispatch();
useEffect(() => {
if (!balance && user) {
dispatch(getBalance());
}
if (status == "success") {
dispatch(getBalance());
dispatch(resetStatus());
}
}, [balance]);
if (user) {
return (
<div>
<h1>
cash balance: ${balance ? Math.round(balance.balance) : "error"}
</h1>
<MarketLong />
</div>
);
}
return (
<div>
Login
</div>
);
};
The above code does not work currently as when I console.log(status) on refresh is is idle and when I use marketLong it is loading but it never makes it to fulfilled so still the only way to update the balance that is displayed after an order is to refresh the page. I want to update the displayed balance without refreshing the page as refreshing the page will have to make two other API calls on top of the getBalance. I have left some comments in where I tried things like just putting the getBalance function inside the MarketLongFunc in the ordersSlice, I also tried returning it etc but that did nothing and I figured fixing this issue in the useEffect with the status' would be the best way to fix this but I am open to hearing other solutions besides creating redundant code where I just basically type out the getBalance func inside marketLongFunc.
Another way that almost works is just adding dispatch(getBalance()) after dispatch(MarketLongFunc(longItem)); in my MarketLong react component like the following:
const addNewLong = async (e) => {
e.preventDefault();
const longItem = {
userInput: req.ticker,
quotePrice: req.quotePrice,
quantity: Item.quantity,
};
dispatch(MarketLongFunc(longItem));
dispatch(getBalance());
};
The problem with this is the first order never gets updated but after that it updates incorrectly as the balance will be off by one buy order. I imagine this is due to getBalance gettting called before MarketLongFunc but without setting a manual setTimeout func which seems like a clunky solution, I am not sure how to fix that with redux, you would think something like : if (dispatch(MarketLongFunc(longItem))) {dispatch(getBalance())}, but maybe this way needs to be changed in the ordersSlice (which I had tried and was not able to get it to work).
There are many ways to solve this problem - I will describe an approximate solution:
export const MarketLongFunc = createAsyncThunk(
"/order/marketlong",
async (value, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
const newObj = {
value: value,
token: token,
};
let url = `http://localhost:3001/api/orders/marketlong`;
const response = await axios.post(url, newObj);
//getBalance()
return;
}
);
export const getBalance = createAsyncThunk(
"/order/getBalance",
async (value, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
const newObj = {
token: token,
};
let url = `http://localhost:3001/api/orders/getBalance`;
const response = await axios.post(url, newObj);
return response.data;
}
);
const initialState = {
value: null,
error: null,
balance: null,
status: "idle",
orderStatus: "idle",
balanceNeedsToBeUpdated: true // <--- HERE
};
export const ordersSlice = createSlice({
name: "orders",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers(builder) {
builder
.addCase(MarketLongFunc.pending, (state, action) => {
state.orderStatus = "loading";
})
.addCase(MarketLongFunc.fulfilled, (state, action) => {
state.orderStatus = "idle";
state.balanceNeedsToBeUpdated = true; // < ----- HERE
// getBalance();
// state.balance = action.payload;
})
.addCase(MarketLongFunc.rejected, (state, action) => {
state.orderStatus = "failed";
state.error = action.error.message;
})
.addCase(getBalance.pending, (state, action) => {
state.status = "loading";
})
.addCase(getBalance.fulfilled, (state, action) => {
// state.status = "success";
state.balance = action.payload;
state.status = "idle";
state.balanceNeedsToBeUpdated = false; // <---- HERE
})
.addCase(getBalance.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const { reset } = ordersSlice.actions;
export default ordersSlice.reducer;
export const Orderform = () => {
const user = useSelector((state) => state.auth.user);
const balance = useSelector((state) => state.orders.balance);
const status = useSelector((state) => state.orders.status);
const orderStatus = useSelector((state) => state.orders.orderStatus);
const balanceNeedsToBeUpdated = useSelector((state) => state.orders.balanceNeedsToBeUpdated);
const dispatch = useDispatch();
useEffect(() => {
if (user && balanceNeedsToBeUpdated) { //< ----- HERE
dispatch(getBalance());
}
}, [user, balanceNeedsToBeUpdated]); // < ---- HERE
if (user) {
if (status == 'loading' || orderStatus == 'loading') {
return <div>loading</div>;
}
return (
<div>
<h1>
cash balance: ${balance ? Math.round(balance.balance) : "error"}
</h1>
<MarketLong />
</div>
);
}
return (
<div>
Login
</div>
);
};
//....
const addNewLong = async (e) => {
e.preventDefault();
const longItem = {
userInput: req.ticker,
quotePrice: req.quotePrice,
quantity: Item.quantity,
};
dispatch(MarketLongFunc(longItem)); // < --- HERE
};

Firebase - return the value from the onSnapshot event in function

I am trying to return the value from function that has the onSnapshot() event but keep getting this weird error. Basically, I call this action and return the data from it like I would in any other function. But I keep getting this error and I do not know how to fix it.
This is the error
Uncaught TypeError: Cannot add property 0, object is not extensible
at Array.push (<anonymous>)
This the function
export const getQuestions = () => {
var questions = [];
onSnapshot(collection(firebaseDatabase, "questions"), (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.data() !== null) {
questions.push(doc.data());
}
});
});
return questions;
};
Also this function is used with Redux Thunk and Redux Toolkit.
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { getQuestions } from "../../utils/firebase-functions/firebase-functions";
export const getAllQuestions = createAsyncThunk(
"allQuestions/getAllQuestions",
async () => {
const response = getQuestions();
return response;
}
);
export const allQuestionsSlice = createSlice({
name: "allQuestions",
initialState: {
allQuestions: [],
loading: false,
error: null,
},
extraReducers: {
[getAllQuestions.pending]: (state) => {
state.loading = true;
state.error = null;
},
[getAllQuestions.fulfilled]: (state, action) => {
state.allQuestions = action.payload;
state.loading = false;
state.error = null;
},
[getAllQuestions.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
export default allQuestionsSlice.reducer;
Where it is dispatched
const dispatch = useDispatch();
const tabContentData = useSelector(
(state) => state.allQuestions.allQuestions
);
useEffect(() => {
dispatch(getAllQuestions());
}, [dispatch]);
console.log(tabContentData);
You can try returning a promise when the data is being fetch for first time as shown below:
let dataFetched = false;
export const getQuestions = () => {
return new Promise((resolve, reject) => {
onSnapshot(collection(firebaseDatabase, "questions"), (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.data() !== null) {
questions.push(doc.data());
}
});
if (!dataFetched) {
// data was fetched first time, return all questions
const questions = querySnapshot.docs.map(q => ({ id: q.id, ...q.data()}))
resolve(questions)
dataFetched = true;
} else {
// Questions already fetched,
// TODO: Update state with updates received
}
});
})
};
getQuestions() now returns a Promise so add an await here:
const response = await getQuestions();
For updates received later, you'll have to update them directly in your state.

REDUX: Error: Actions may not have an undefined "type" property. Have you misspelled a constant?

I'm learning Redux, and I am very confused about what is going on here. I am using thunk and GET_ITEMS is in my reducer so I'm not sure what I have done wrong? The error is in the dispatch(getItemsAction());
Redux.js
function reducer(state, action) {
switch (action.type) {
case 'GET_ITEMS':
return {
...state,
items: action.payload,
loading: false,
};
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload],
};
case 'DELETE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload),
};
case 'ITEMS_LOADING':
return {
...this.state,
loading: true,
};
default:
return state;
}
}
export const getItemsAction = () => ({
return(dispatch) {
axios.get('api/items').then(response => {
console.log(response);
dispatch({ type: 'GET_ITEMS', payload: response.data });
});
},
});
ShoppingList.js
import { addItemAction, deleteItemAction, getItemsAction } from '../redux';
export default function ShoppingList() {
const items = useSelector(state => state.items);
const dispatch = useDispatch();
const addItem = name => dispatch(addItemAction(name));
const deleteItem = id => dispatch(deleteItemAction(id));
useEffect(() => {
dispatch(getItemsAction());
}, []);
in the top code you returned the dispatch in incorrect way
but actually you need to call dispatch like cb
for example in javascript we do somthing like this
const myfunc = () => cb => {
cb('OK')
};
its callback in javascript and you have to return dispatch like callback to work correct
export const getItemsAction = () => dispatch => {
axios.get('api/items').then(response => {
dispatch({
type: 'GET_ITEMS',
payload: response.data
})
});
};
at the end dont forgot to get axios response data with response.data
the correct syntax for the action is
export const getItemsAction = () => dispatch => {
axios.get('/api/items').then(res =>
dispatch({
type: 'GET_ITEMS',
payload: res.data,
})
);
};

how to use state in actions when we use useReducer

I am using hooks and context api.I have multiple actions that write them into seperate file.my problem this:in another file how can I access state?
I use this file for create my contexts:
createContext.js
import React, { useReducer } from "react";
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
and when I want to create context I pass actions ,reducer and default values to createContext file and get Context and Provider from that.like this:
productContext.js
import createDataContext from "./createDataContext";
import {storeProducts, detailProduct} from "../data";
const productReducer = (state, action) => {
switch (action.type) {
case "GET_ITEM":
return {...state, productDetail: action.productDetail};
case "ADD_TOTALS":
return {
...state,
cartSubTotal: action.cartSubTotal,
cartTotal: action.cartTotal,
cartTax: action.cartTax
};
case "ADD_TO_CART":
return {
...state,
products: action.tempProducts,
cart: [...state.cart, action.product]
};
default:
return state;
}
};
const getItem = (id) => {
const product = **products**.find(item => item.id === id);
return product;
}
const handleDetail = dispatch => (id) => {
const productDetail = getItem(id);
dispatch({type: "GET_ITEM", productDetail})
};
const addToCart = dispatch => (id) => {
let tempProducts = [...storeProducts];
const index = tempProducts.indexOf(getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
dispatch({
type: "ADD_TO_CART",
tempProducts,
product
});
const data = addTotals();
dispatch({
type: "ADD_TOTALS",
cartSubTotal: data.cartSubTotal,
cartTotal: data.cartTotal,
cartTax: data.cartTax
});
};
const addTotals = () => {
let subTotal = 0;
**cart**.map(item =>{ (subTotal += item.total)});
const tempTax = subTotal * 0.1;
const tax = parseFloat(tempTax.toFixed(2));
const total = subTotal + tax;
return {cartSubTotal: subTotal, cartTax: tax, cartTotal: total};
};
export const {Provider, Context} = createDataContext(
productReducer,
{
handleDetail,
},
{
products: storeProducts,
productDetail: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
cartSubTotal: 0,
cartTax: 0,
cartTotal: 0
);
I can not access cart and products that are bold.how can I use them?
It looks like you're doing a lot of work in the action creator function that would make more sense as part of the reducer. For example, instead of this:
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
return { ...state, productDetail: action.productDetail };
default:
return state;
}
};
const getItem = (id) => {
// no access to state!
const product = products.find((item) => item.id === id);
return product;
};
const handleDetail = (dispatch) => (id) => {
const productDetail = getItem(id);
dispatch({ type: 'GET_ITEM', productDetail });
};
You can do this:
// action value
{ type: 'GET_ITEM', id: 1234 }
// reducer
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
const productDetail = state.products.find(
(item) => item.id === action.id
);
return { ...state, productDetail };
default:
return state;
}
};
Inside the reducer is where you have access to both the action and the state. Try to design your actions so that they contain the smallest amount of information possible to achieve your intention.

Categories