I am trying to update a react context with an array
this is the cartcontext
import React, { useState } from "react";
import UniqueString from "../lib/unique-string";
import suspender from "../lib/suspender";
import db from '../db';
const uid = new UniqueString();
// set up the database when this function is called
// const CartDBContext = React.createContext(setUpDatabase())
const emptycart = {"454":{"test"}}
const CartContext = React.createContext([emptycart]);
// function to get all meals in the DB
async function getAllItemsFromDB() {
return new Promise((resolve, reject) => {
var allItems = db.table("cartitems").toArray().then((itemArry) => {
console.log("items in cart ",itemArry)
if (!itemArry || !itemArry.length) {
// array does not exist, is not an array, or is empty
// ⇒ do not attempt to process array
resolve([])
} else {
resolve(itemArry)
}
})
})
}
// using our suspender here
const resource = suspender(getAllItemsFromDB());
// The component itself
function CartContextProvider({ children }) {
// reading data from suspender
const items = resource.data.read() || [];
console.log("all items from suspender", items, resource)
// state to store list of items in the object Store
const [itemsList, setItemsList] = useState(items);
// if the itemList is null, umnount the component
// else return the appropriate JSX
return (
// pass the mealsList state and its setter as context values
<CartContext.Provider value={{ itemsList, setItemsList }}>
{children}
</CartContext.Provider>
);
}
export default CartContextProvider;
export { CartContext };
this is index.jsx where the context is used
import React, { useContext,useState,useEffect,useRef ,Suspense } from "react";
import CartContextProvider, { CartContext } from "../components/CartContextProvider";
function Index(){
...
const [itemsList, setItemsList] = useContext(CartContext);
useEffect(() => {
runIndexDb(data).then(async result => {
console.log("ItemList is ", itemsList)
console.log(result)
setItemsList(itemsList => [...itemsList, result]);
})
})
...
}
The print out of console.log
for itemsList
ItemList is {454: "test"}
result
0: {id: "00010164533955", name: "Hilsinger Company Sport Band - Black", productimg: "http://i5.walmartimages.com/asr/44e902de-30f8-40f3…9fdb.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: 2, …}
1: {id: "00014381104394", name: "A House Divided: Season 1 (DVD)", productimg: "http://i5.walmartimages.com/asr/99cfec5c-634e-4e26…4465.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
2: {id: "00016500590958", name: "One A Day Men's 50+ Mini Gels, Multivitamins for Men, 80 Ct", productimg: "http://i5.walmartimages.com/asr/7d44d419-bd6f-4808…b7ea.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
3: {id: "00022141041599", name: "Mepps Dressed Aglia Inline Spinner, Silver & Gray, 1/4 oz", productimg: "http://i5.walmartimages.com/asr/a0ce2579-300a-4536…0d63.jpeg?odnHeight=180&odnWidth=180&odnBg=ffffff", unitofissue: "each", quantity: "1", …}
the code is throwing an error at setItemsList
Unhandled Rejection (TypeError): setItemsList is not a function
I tried many different methods but no solution
what am I doing wrong and how can I updated the itemsList with the array from above?
useContext returns an object and not a list.
If you do it like so it should work.
Try to replace:
const [itemsList, setItemsList] = useContext(CartContext);
With:
const { itemsList, setItemsList } = useContext(CartContext);
Maybe you used useContext in a component that isn't wrapped by the context provider (using useContext in the same component with return context provider).
Related
I am trying redux-thunk for the first time Hence working on a simple project the thunk uses the API and displays the data on the screen but the API is returning a JSON object ,to display the titles on the screen I need to use the .map() function to map through the object, but the object doesn't allow us to use map() function so I need to convert the JSON data to an array and the use .map() function to achieve the desired result but I don't know how to convert the JSON data to an array
I tried different approaches to deal with this but nothing seems to work for me Here is what I need
const codeReturnedFromJSONRequest ={data:{0:somedata}} //example JOSN data
what I want my code to look like :
const requiredData=[{data:{0:somedata}}] //I want the required data to be an array so that I can use .map()
If you want my actual code here it is
//ApiSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const getPosts = createAsyncThunk("posts/getPosts", async () => {
return fetch("https://api.jikan.moe/v4/anime?q=naruto&sfw").then((response) =>
response.json()
);
});
const postSlice = createSlice({
name: "posts",
initialState: {
posts: [],
loading: false,
},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.loading = true;
},
[getPosts.fulfilled]: (state, action) => {
state.loading = false;
state.posts = action.payload;
},
[getPosts.rejected]: (state, action) => {
state.loading = false;
},
},
});
export default postSlice.reducer
//store
import { configureStore } from "#reduxjs/toolkit";
import postReducer from "./anime";
export const store =configureStore({
reducer: {
post:postReducer
}
})
//Api data
import React from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../store/anime";
function Apidata() {
const { posts, loading } = useSelector((state) => state.post);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPosts());
}, []);
console.log(posts.data)
return (
<div>
{posts.map((item) => (
<h2>{item.data}</h2>
))}
</div>
);
}
export default Apidata;
// App.js
import { Fragment, useState } from "react";
import Apidata from "./components/Apidata";
function App() {
return (
<Fragment>
<Apidata/>
</Fragment>
)
}
export default App;
if you want create an array just wrap the response.json() in an array like that:
export const getPosts = createAsyncThunk("posts/getPosts", async () => {
return fetch("https://api.jikan.moe/v4/anime?q=naruto&sfw")
.then(response=>response.json())
.then((response) =>[response]
);
});
BUT I don't think it is a best practice. Ask to whom create the backend and get explanations!.
Hope the best for you,
Mauro
This peace of code resolved my issue
const myObj = {0 : {mal_id: 20, url: 'https://myanimelist.net/anime/20/Naruto', images: 'test', trailer: 'test', approved: true}, 1: {mal_id: 20, url: 'https://myanimelist.net/anime/20/Naruto', images: 'test', trailer: 'test', approved: true}};
const myArr = [];
for (const key in myObj) {
const arrObj = myObj[key];
arrObj['id'] = key;
myArr.push(arrObj)
}
console.log(myArr);
Click here to see the reddit post:
Solution reddit link
SOLVED
I'm trying to use the newer version of react redux v8.0.2 within my web-based game to keep state within my application without having to pass states between navigation calls.
Unfortunately I'm running into an issue where the player state gets initialized for the first time, but the final state is not being updated after calling the useDispatch() method. I've tried looking everywhere online, but none of the solutions that are currently out there have actually solved my issue.
I even force my functional component to rerender, and that still just returns the initial state of my player instead of the updated one that I'm expecting. Can anyone help me figure out what I'm missing here. It's got to be something small that isn't talked about on the redux forms/docs. TIA!
playerSlice.js
import {createSlice} from '#reduxjs/toolkit';
export const playerSlice = createSlice({
name: 'player',
initialState: {
address: '',
cp: 0,
created: '',
faction: 0,
faction_selected: false,
games_lost: 0,
games_won: 0,
online: false,
selected_char: 0,
selected: {
combatType: '',
lvl: 0,
mgc: 0,
str: 0,
rng: 0,
def: 0
},
time_played: 0,
tokens: 10000,
total_cp: 0,
total_earned: 0,
user_name: ""
},
reducers: {
setInit: (state,action) => {
state = action.payload;
},
setCP: (state,action) => {
state.cp += action.payload;
},
setFaction: (state,action) => {
state.faction = action.payload;
},
setGamesLost: (state,action) => {
state.games_lost = action.payload;
},
setGamesWon: (state,action) => {
state.games_won = action.payload;
},
setPlayerState: (state,action) => {
state = {
...state,
...action.payload
}
}
}
});
export const {setInit, setCP, setFaction, setGamesLost, setGamesWon, setPlayerState} = playerSlice.actions;
export const selectPlayer = (state) => state.player;
export default playerSlice.reducer;
index.js
import {configureStore} from '#reduxjs/toolkit';
import playerReducer from '../store/playerSlice';
export default configureStore({
reducer: {
player: playerReducer,
},
})
Selection.js
import Card from './Card';
import React, {useState, useEffect} from 'react';
import '../stylesheet/Selection.css';
import Logo from '../assets/degen age title GNW skull.png';
import KnightTitle from '../assets/knights title.png';
import GoblinTitle from '../assets/goblins title.png';
import WizardTitle from '../assets/wizards title.png';
import ElfTitle from '../assets/elves title.png';
import SorcererShield from '../assets/sorcerers shield item.jpg';
import Weaken from '../assets/weaken item img.jpg';
import Barrage from '../assets/barrage item img.jpg';
import Berserk from '../assets/berserk item img.jpg';
import {db} from '../firebase/firestore';
import {addDoc,collection, serverTimestamp} from 'firebase/firestore';
import { useNavigate, useLocation } from 'react-router-dom';
import {CHAR_RACES} from '../constants';
import {useSelector, useDispatch} from 'react-redux';
import {setInit, selectPlayer} from '../store/playerSlice';
const SCREEN_DELAY = 4000; // delay in ms
const Selection = () => {
const [initScreen, setInitScreen] = useState(true);
const player = useSelector(selectPlayer);
const dispatch = useDispatch();
const navigate = useNavigate();
const [ready,setReady] = useState(false);
const {state} = useLocation();
useEffect(() => {
let mounted = true;
if(mounted){
setTimeout(() => {
setInitScreen(false);
},SCREEN_DELAY);
}
return () => {
mounted = false;
}
},[]);
const handleFactionSelect = async (_faction) => {
// add new player to db
const ref = collection(db, 'players');
const playerData = {
address: state.address,
cp: 0,
created: serverTimestamp(),
faction: _faction,
faction_selected: true,
games_lost: 0,
games_won: 0,
online: true,
selected_char: 0,
selected: {
combatType: 'MELEE',
lvl: 200,
mgc: 10,
str: 59,
rng: 30,
def: 101
},
time_played: 0,
tokens: 10000,
total_cp: 0,
total_earned: 0,
user_name: "someUser393900"
}
dispatch(setInit({
...playerData,
created: new Date().getTime(),
faction: _faction
}))
console.log({player});
// set in redux as well****
// addDoc(ref,playerData).then(res => {
// if(res.id){
// const _faction = CHAR_RACES[playerData.faction];
// dispatch(setInit({
// ...playerData,
// created: new Date().getTime(),
// faction: _faction
// }))
// // navigate('/play',{
// // state: {
// // player: {
// // ...playerData,
// // faction: _faction
// // }
// // }
// // });
// navigate('/play');
// }
// }).catch(error => {
// console.error(error);
// })
}
return (
<div className='select-main'>
{!initScreen ? <div id="main-select" className='fade-in-slow2 select-wrapper'>
<h1 className='text-center'>CHOOSE YOUR SIDE</h1>
<div className='select-cards'>
<Card cardStyle="f1" ability={SorcererShield} desc="Sorcerers Shield" title={WizardTitle} name={1} onClick={handleFactionSelect} />
<Card cardStyle="f3" ability={Berserk} desc="Berserk" title={KnightTitle} name={2} onClick={handleFactionSelect} />
<Card cardStyle="f2" ability={Barrage} desc="Barrage" title={ElfTitle} name={0} onClick={handleFactionSelect} />
<Card cardStyle="f4" ability={Weaken} desc="Weaken" title={GoblinTitle} name={3} onClick={handleFactionSelect} />
</div>
</div> :
<div className='fade-in-slow sub-select-wrapper flex-just-center'>
<div className='cracked'></div>
</div>
}
</div>
)
}
export default Selection;
As you can see I'm attempting to call the setInit reducer from my redux store and then logging the new state after that. I also know that trying to log the state directly after doesn't always reflect the most recent data, but I've tried by adding a state change to my component and log the player state afterwards and I just get the same data back again. Nothing changes.
setInit: (state,action) => {
state = action.payload;
},
This doesn't do anything, it simply replaces the local variable state but Redux cannot possibly see that. You should either mutate the state, or return a new state. In your case, returning action.payload should work.
See https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state
I have three different components that within them are using a component called StripeCheckout and one of the properties of StripeCheckout is description which I currently have as a string:
import React, { Component } from "react";
import StripeCheckout from "react-stripe-checkout";
import { connect } from "react-redux";
import * as actions from "../actions";
class SunnySampler extends Component {
render() {
return (
<div>
<StripeCheckout
name='Microurb Farms'
amount={this.props.amount}
description='Sunny Sampler Box'
shippingAddress
billingAddress={false}
zipCode={true}
token={(token, amount) =>
this.props.handleToken(token, this.props.amount)
}
stripeKey={process.env.REACT_APP_STRIPE_KEY}
/>
</div>
);
}
}
export default connect(null, actions)(SunnySampler);
SunnySampler is just one of the three components making use of StripeCheckout. Each has its own amount property dynamically coded and passed down to the express api and yet I cannot seem to pass down the description property successfully.
The challenge also is that each description property is different depending on which component was selected.
So I was able to pass in the amount dynamically here:
const tiers = [
{
title: "Half pound boxes",
price: "10",
description: [
"Sunflower Shoots",
"Pea Shoots",
"Radish Shoots",
"Broccoli Shoots",
],
buttonText: <HalfPound amount={1000} />,
buttonVariant: "outlined",
},
{
title: "Grasses",
subheader: "Tray",
price: "15",
description: ["Wheatgrass", "Barleygrass"],
buttonText: <Grasses amount={1500} />,
buttonVariant: "contained",
},
{
title: "Sunny Sampler Box",
price: "20",
description: [
"6oz Sunflower",
"2oz Broccoli",
"3oz Sweet Pea",
"2oz Radish",
],
buttonText: <SunnySampler amount={2000} />,
buttonVariant: "outlined",
},
];
this is inside of Dashboard.js, then in my action creator I pass it in like so:
export const handleToken = (token, amount) => async (dispatch) => {
const res = await axios.post("/api/stripe", { token, amount });
dispatch({ type: FETCH_USER, payload: res.data });
};
Inside each of those payment type of components it looks like so:
import React, { Component } from "react";
import StripeCheckout from "react-stripe-checkout";
import { connect } from "react-redux";
import * as actions from "../actions";
class SunnySampler extends Component {
render() {
return (
<div>
<StripeCheckout
name='Microurb Farms'
amount={this.props.amount}
description='Sunny Sampler Box'
shippingAddress
billingAddress={false}
zipCode={true}
token={(token, amount) =>
this.props.handleToken(token, this.props.amount)
}
stripeKey={process.env.REACT_APP_STRIPE_KEY}
/>
</div>
);
}
}
export default connect(null, actions)(SunnySampler);
and finally my backend api:
const keys = require("../config/keys");
const stripe = require("stripe")(keys.stripeSecretKey);
module.exports = (app) => {
app.post("/api/stripe", async (req, res) => {
const { amount, token } = req.body;
// const description = req.body.data.description;
const charge = await stripe.charges.create({
amount: amount,
currency: "usd",
source: token.id,
});
console.log(charge);
});
};
I tried taking the same approach I took to the amount property with the description property and variations of it and I am still getting undefined.
Originally, inside the action creator I had passed in description to it and then inside the handleToken I had passed in this.props.description and then inside the api route on the backend I had req.body.description which should have worked, but I got undefined.
When I console log req.body I see in the data structure description: null, despite having passed a string into the description property inside of StripeCheckout component. I cannot explain why that is.
Im new to frontend testing in general and i have a CartScreen in react with a calculateTotalQuantity function which im trying to test with jest. Since hours i try to change the value of cartItems (create a new cartItems in testclass and asign some dummy qty data). I tried out mocking, spies but without success. I simply cant change the value of cartItems. My attempt is to put in some valuable data and expect the correct output of calculateTotalQuantity function.
This is how my CartScreen.js looks like:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { addToCart, removeFromCart } from "../actions/cartActions";
function CartScreen(props){
const cart = useSelector(state => state.cart);
const {cartItems} = cart;
console.log(JSON.stringify(cartItems));
const productId = props.match.params.id;
const qty = props.location.search ? Number(props.location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
const removeFromCartHandler = (productId) => {
dispatch(removeFromCart(productId));
}
function calculateTotalQuantity() {
let totalQty = cartItems.reduce((a, c) => a + parseInt(c.qty), 0);
console.log("QUantity");
console.log(totalQty);
return totalQty
}
My testclass looks like this:
import { JsonWebTokenError } from "jsonwebtoken";
import CartScreen from "./CartScreen";
it("calculates total quantity",() => {
const cartItems = [
{
id: 1,
"qty": "3",
},
{
id: 2,
"qty": "4",
},
{
id: 3,
"qty": "6",
},
];
const mockFn = jest.fn(CartScreen);
mockFn();
mockFn.cartItems = cartItems;
console.log("Mockfunktion: ")
console.log(mockFn());
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toEquals(13)
})
lets say this is my code
const donation = useStoreState(
state => state.user.initialState.donationData,
)
const setDonation = useStoreActions(
actions => actions.donation.setDonation,
)
setDonation({
amount: 1000000,
message: 'donation from easy peasy',
payment_method_id: '1',
receiver_id: '1',
})
console.log('donation', donation)
when i tried to console.log it not showing new donation data
In easy-peasy initialState is an immutable value used to initialise your store. So your setDonation function wont be able to change this value.
A full (though contrived!) example of what you want to do is shown here, with comments which should explain whats going on:
import React, { Component } from "react";
import { render } from "react-dom";
import {
useStoreState,
action,
createStore,
StoreProvider,
useStoreActions
} from "easy-peasy";
// Define your model
const donationModel = {
donation: {},
setDonation: action((state, payload) => {
state.donation = payload;
})
};
// Define you application store
const storeModel = {
donations: donationModel
};
// Create an instance of the store
const store = createStore(storeModel);
const App = () => (
// Wrap the Donation component with the StoreProvider so that it can access the store
<StoreProvider store={store}>
<Donation />
</StoreProvider>
);
const Donation = () => {
// Dispatch a setDonation action to add donation data to the store
useStoreActions(actions =>
actions.donations.setDonation({
amount: 1000000,
message: "donation from easy peasy",
payment_method_id: "1",
receiver_id: "1"
})
);
// Retrieve data from the store using useStoreState
const donationMessage = useStoreState(
state => state.donations.donation.message
);
// Display the donation message returned from the store!
return <>{donationMessage}</>;
};
render(<App />, document.getElementById("root"));
You can find this working here.