How can I access state inside of my Redux cartReucer? - reactjs

I need to access my current cart state which is just a list of products that have
been added to the cart, so that I check ids in order to calculate quantity for duplicate products. I realize that one of my issues here is that i've initialized itemsInCart with an empty array but i'm not sure what else to do here since, state can't be destructured without it.
cartReducer.js
const itemsInCart = []
export const cartReducer = (state = itemsInCart, action) => {
const { type, payload } = action;
switch (type) {
case "ADD_TO_CART":
return [
...state,
{
imgUrl: payload.imgUrl,
name: payload.name,
price: payload.price,
quantity: payload.quantity
},
];
default:
}
return state;
};
Product.js
Clicking the button dispatches the 'ADD_TO_CART' action, adds new products to our cart in state.
import React from 'react';
import {useDispatch} from 'react-redux';
const Product = ({imgUrl, name, price, id }) => {
const dispatch = useDispatch()
const addToCart = () => {
dispatch({
type: "ADD_TO_CART",
payload: {
imgUrl: imgUrl,
name: name,
price: price,
id: id
}
})
}
return (
<div
key={id}
style={{
textAlign: "center",
display: "flex",
border: "1px solid",
marginBottom: "2rem",
flexDirection: 'column'
}}
>
<img
src={imgUrl}
style={{ height: "5rem", width: "5rem" }}
alt="The product"
/>
<h3>{name}</h3>
<p>${price}.00</p>
{id}
<button onClick={()=>addToCart()}>Add To Cart</button>
</div>
);
}
export default Product;
InCartList.js
Renders list of items in my cart inside CartContainer
import React from "react";
import { useSelector } from "react-redux";
import CartItem from "./CartItem";
const ProductList = () => {
const allState = useSelector((state) => state.cart);
const renderProducts = () => {
return allState.map((product) => {
return (
<CartItem id={product.id} quantity={product.quantity}key={product.id} name={product.name} price={product.price}/>
);
});
};
return <>{renderProducts()}</>;
};
export default ProductList;

You shouldn't place any logic inside reducer (reducer should only pure function)
You can try to get state you want before dispatch action ADD_TO_CART
use getStateToProps function
use store which should be exported when initialized inside App component (I guess)
export const store = configureAppStore(history);

Related

Codepen.io won't render React component

I've been driving myself insane trying to figure out why this React component won't render. After checking in a local environment it works fine, but I need this to work on CodePen.io to turn it in for a FreeCodeCamp Challenge. Any help would be greatly appreciated.
I've tried putting in an 'unpkg' source, adding it to the html in script tags (the React packages) and using the integrated npm tool in the codepen.io js tab in the settings. None of this worked and I think the code should work.
import React from 'http://esm.sh/react#18.2.0'
import ReactDOM from 'http://esm.sh/react-dom#18.2.0'
import * as redux from "http://cdn.skypack.dev/redux#4.2.0";
import reduxThunk from "http://cdn.skypack.dev/redux-thunk#2.4.2";
import { Provider, connect } from "http://cdn.skypack.dev/react-redux#8.0.5";
// Initial State
const initialState = {
quote: "Let's go",
author: "Bob J. Baker",
backgroundColor: "#f5f5f5"
};
// Actions: Types
const CHANGE_ALL = "CHANGE_ALL";
// Action: Functions
const changeAll = () => {
return async (dispatch) => {
try {
dispatch({ type: CHANGE_ALL });
const response = await fetch("https://quotes.rest/quote/random");
const data = await response.json();
const color = getRandomColor();
dispatch({
type: CHANGE_ALL,
quote: data.contents.quotes[0].quote,
author: data.contents.quotes[0].author,
backgroundColor: color
});
} catch (error) {
console.log(error);
}
};
};
const getRandomColor = () => {
const randomColor = "#" + Math.random().toString(16).substr(-6);
return randomColor;
};
// Reducer
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_ALL:
return {
...state,
quoteAndAuthor: { quote: action.quote, author: action.author },
backgroundColor: action.backgroundColor
};
default:
return state;
}
};
// Store
const store = redux.createStore(rootReducer, redux.applyMiddleware(reduxThunk));
//**********Component://**********
class RandomQuoteComponent extends React.Component {
// state & props
constructor(props) {
super(props);
}
componentDidMount() {
// running the get all action
this.props.changeAll();
}
render() {
return (
<div
id="main-wrapper"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
backgroundColor: this.props.backgroundColor
}}
>
<div id="quote-box">
<h2 id="text">{this.props.quote}</h2>
<h6 id="author">{this.props.author}</h6>
<button id="new-quote">New Quote</button>
<a href="#" id="tweet-quote">
<i className="fab fa-twitter"></i>
</a>
</div>
</div>
);
}
}
////**********END OF COMPONENT//**********
// Mapping for state and dispatch actions to props
const mapStateToProps = (state) => {
return {
backgroundColor: state.backgroundColor,
quote: state.quoteAndAuthor.quote,
author: state.quoteAndAuthor.author
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeAll: () => dispatch(changeAll())
};
};
const ConnectedRandomQuoteComponent = connect(
mapStateToProps,
mapDispatchToProps
)(RandomQuoteComponent);
ReactDOM.render(
<Provider store={store}>
<ConnectedRandomQuoteComponent />
</Provider>,
document.getElementById("root")
);

How to query data using 'useQueries' when it is dependent on previous query?

I am creating a webapp where I can show a list of Pokemons on initial page load using PokéAPI. The list should show all the images of the Pokemons and its name, inside of an MUI Card, something like this: App UI example.
I am using two APIs. One for getting the list of all the Pokemon names and their details. Another for getting the specific Pokemon's image.
In order to create the UI, I am querying the first API to get the list of all the Pokemons. And then I am using the result of the first API in the second query to get the image of that Pokemon.
Here are both the components that I have created:
PokemonList.jsx
import Box from "#mui/material/Box";
import { useQueries, useQuery } from "#tanstack/react-query";
import axios from "axios";
import PokemonCard from "./PokemonCard";
const getPokemonList = async () => {
const { data } = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=12&offset=0"
);
console.log(data);
return data;
};
const usePokemonList = () => {
return useQuery(["pokemonList"], () => getPokemonList());
};
const getPokemons = async (url) => {
const { data } = await axios.get(url);
return data;
};
const usePokemons = (pokemons) => {
return useQueries({
queries: pokemons?.results.map((pokemon) => {
return {
queryKey: ["pokemons", pokemon.name],
queryFn: getPokemons.bind(this, pokemon.url),
staleTime: Infinity,
enabled: !!pokemons.count,
};
}),
});
};
function PokemonList() {
const { data: pokemonList } = usePokemonList();
const { data: pokemons, isLoading } = usePokemons(pokemonList);
return (
<Box
sx={{
display: "flex",
justifyContent: "space-evenly",
flexFlow: "row wrap",
gap: "2em",
}}
>
{!isLoading &&
pokemons.map((pokemon) => (
<PokemonCard
key={pokemon.species.name}
name={pokemon.species.name}
image={pokemon.sprites.front_default}
/>
))}
</Box>
);
}
export default PokemonList;
PokemonCard.jsx
import { Card, CardContent, CardMedia, Typography } from "#mui/material";
import PropTypes from "prop-types";
PokemonCard.propTypes = {
image: PropTypes.string,
name: PropTypes.string,
};
function PokemonCard(props) {
return (
<Card sx={{ width: 225, flexWrap: "wrap" }}>
<CardMedia component="img" height="140" image={props.image} />
<CardContent>
<Typography variant="caption">{props.name}</Typography>
</CardContent>
</Card>
);
}
export default PokemonCard;
This is the error that I am getting, because the first API has not resolved yet. What should be the proper way to use useQueries here?
The error is telling you that the thing you're trying to map for your useQueries call is null or undefined. Which makes sense if the previous result has not yet returned. Additionally your hooks are a little too brittle in how their integrated. I would suggest refactoring your usePokemons hook to just take a list of pokemons (not an object with results) and default it to being empty. e.g.
const usePokemons = (pokemons) => {
pokemons = pokemons || [];
return useQueries({
queries: pokemons.map((pokemon) => {
return {
queryKey: ["pokemons", pokemon.name],
queryFn: getPokemons.bind(this, pokemon.url),
staleTime: Infinity,
};
}),
});
};
Then you just change how you integrate the two:
function PokemonList() {
const { data: pokemonList, isLoading: isListLoading } = usePokemonList();
const { data: pokemons, isLoading: isImagesLoading } = usePokemons(pokemonList?.results);
// ... etc

How can I change my data fetching strategy to avoid stale data on initial dynamic route renders?

I'm building a Next.js app which allows users to create and view multiple Kanban boards. There are a couple of different ways that a user can view their different boards:
On the Home page of the app, users see a list of boards that they can click on.
The main navigation menu has a list of boards users can click on.
Both use Next.js Link components.
Clicking the links loads the following dynamic page: src/pages/board/[boardId].js The [boardId].js page fetches the board data using getServerSideProps(). An effect fires on route changes, which updates the redux store. Finally, the Board component uses a useSelector() hook to pull the data out of Redux and render it.
The problem I'm experiencing is that if I click back and forth between different boards, I see a brief flash of the previous board's data before the current board data loads. I am hoping someone can suggest a change I could make to my approach to alleviate this issue.
Source code:
// src/pages/board/[boardId].js
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Board from 'Components/Board/Board'
import { useRouter } from 'next/router'
import { hydrateTasks } from 'Redux/Reducers/TaskSlice'
import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'
import prisma from 'Utilities/PrismaClient'
const BoardPage = ({ board, tasks }) => {
const router = useRouter()
const dispatch = useDispatch()
useEffect(() => {
dispatch(hydrateTasks({ board, tasks }))
}, [router])
return (
<Board />
)
}
export async function getServerSideProps ({ query, req, res }) {
const session = await unstable_getServerSession(req, res, authOptions)
if (!session) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
const { boardId } = query
const boardQuery = prisma.board.findUnique({
where: {
id: boardId
},
select: {
name: true,
description: true,
id: true,
TO_DO: true,
IN_PROGRESS: true,
DONE: true
}
})
const taskQuery = prisma.task.findMany({
where: {
board: boardId
},
select: {
id: true,
title: true,
description: true,
status: true,
board: true
}
})
try {
const [board, tasks] = await prisma.$transaction([boardQuery, taskQuery])
return { props: { board, tasks } }
} catch (error) {
console.log(error)
return { props: { board: {}, tasks: [] } }
}
}
export default BoardPage
// src/Components/Board/Board.js
import { useEffect } from 'react'
import { useStyletron } from 'baseui'
import Column from 'Components/Column/Column'
import ErrorBoundary from 'Components/ErrorBoundary/ErrorBoundary'
import useExpiringBoolean from 'Hooks/useExpiringBoolean'
import { DragDropContext } from 'react-beautiful-dnd'
import Confetti from 'react-confetti'
import { useDispatch, useSelector } from 'react-redux'
import useWindowSize from 'react-use/lib/useWindowSize'
import { moveTask } from 'Redux/Reducers/TaskSlice'
import { handleDragEnd } from './BoardUtilities'
import { StyledBoardMain } from './style'
const Board = () => {
const [css, theme] = useStyletron()
const dispatch = useDispatch()
useEffect(() => {
document.querySelector('body').style.background = theme.colors.backgroundPrimary
}, [theme])
// get data from Redux
const { boardDescription, boardName, columnOrder, columns, tasks } = useSelector(state => state?.task)
// set up a boolean and a trigger to control "done"" animation
const { boolean: showDone, useTrigger: doneUseTrigger } = useExpiringBoolean({ defaultState: false })
const doneTrigger = doneUseTrigger({ duration: 4000 })
// get width and height for confetti animation
const { width, height } = useWindowSize()
// curry the drag end handler for the drag and drop UI
const curriedDragEnd = handleDragEnd({ dispatch, action: moveTask, handleOnDone: doneTrigger })
return (
<ErrorBoundary>
<DragDropContext onDragEnd={curriedDragEnd}>
<div className={css({
marginLeft: '46px',
marginTop: '16px',
fontFamily: 'Roboto',
display: 'flex',
alignItems: 'baseline'
})}>
<h1 className={css({ fontSize: '22px', color: theme.colors.primary })}>{boardName}</h1>
{boardDescription &&
<p className={css({ marginLeft: '10px', color: theme.colors.primary })}>{boardDescription}</p>
}
</div>
<StyledBoardMain>
{columnOrder.map(columnKey => {
const column = columns[columnKey]
const tasksArray = column.taskIds.map(taskId => tasks[taskId])
return (
<Column
column={columnKey}
key={`COLUMN_${columnKey}`}
tasks={tasksArray}
title={column.title}
status={column.status}
/>
)
})}
</StyledBoardMain>
</DragDropContext>
{showDone && <Confetti
width={width}
height={height}
/>}
</ErrorBoundary>
)
}
export default Board
// src/pages/index.tsx
import React, {PropsWithChildren} from 'react'
import {useSelector} from "react-redux";
import {authOptions} from 'pages/api/auth/[...nextauth]'
import {unstable_getServerSession} from "next-auth/next"
import CreateBoardModal from 'Components/Modals/CreateBoard/CreateBoard'
import Link from 'next/link'
import {useStyletron} from "baseui";
const Index: React.FC = (props: PropsWithChildren<any>) => {
const {board: boards} = useSelector(state => state)
const [css, theme] = useStyletron()
return boards ? (
<>
<div style={{marginLeft: '46px', fontFamily: 'Roboto', width: '600px'}}>
<h1 className={css({fontSize: '22px'})}>Boards</h1>
{boards.map(({name, description, id}) => (
<Link href="/board/[boardId]" as={`/board/${id}`} key={id}>
<div className={css({
padding: '20px',
marginBottom: '20px',
borderRadius: '6px',
background: theme.colors.postItYellow,
cursor: 'pointer'
})}>
<h2 className={css({fontSize: '20px'})}>
<a className={css({color: theme.colors.primary, width: '100%', display: 'block'})}>
{name}
</a>
</h2>
<p>{description}</p>
</div>
</Link>
))}
</div>
</>
) : (
<>
<h1>Let's get started</h1>
<button>Create a board</button>
</>
)
}
export async function getServerSideProps(context) {
const session = await unstable_getServerSession(context.req, context.res, authOptions)
if (!session) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
return {props: {session}}
}
export default Index
It looks like there's only ever one board in the redux. You could instead use a namespace so that you don't have to keep swapping different data in and out of the store.
type StoreSlice = {
[boardId: string]: Board;
}
Then the "brief flash" that you will see will either be the previous data for the correct board, or nothing if it has not yet been fetched.

Redux with React Native state not updating

I tried to add a recipe to favourites using Redux with React Native.
If I use the state in the favicon.js this works fine but does not work with Redux.
This is my favicon.js
import React, {useState} from 'react'
import { FontAwesome } from '#expo/vector-icons'
import { View, TouchableOpacity,StyleSheet, Text, Alert } from 'react-native'
import { connect } from 'react-redux'
import { toggleFavId, isFavourite } from '../actions'
const FavIcon = ({recipeid, toggle, isFav, text}) => {
// const [isFavNew, setFavNew] = useState(false)
// const toggleFav = (recipeid) =>{
// setFavNew(!isFavNew)
// console.log(`Entered toggle ${recipeid} isfavnew = ${isFavNew}`)
// }
return (
<View>
<TouchableOpacity style={styles.favItem} onPress={() => {toggle(recipeid)}}>
<FontAwesome name={isFav == true ? 'heart' : 'heart-o'} size={40} color='red' />
<Text> {text}</Text>
</TouchableOpacity>
</View>
)
}
const mapStateToProps = (state) =>({
favids: state.favids,
})
const mapDispatchToProps = (dispatch) => ({
toggle: (recipeid) => dispatch(toggleFavId(recipeid)),
isFav: (recipeid) => dispatch(isFavourite(recipeid)),
})
export default connect(mapStateToProps,mapDispatchToProps)(FavIcon)
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
alignItems: 'flex-start',
justifyContent: 'center',
},
favItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
marginTop:10,
paddingTop:15,
paddingBottom:15,
marginLeft:30,
marginRight:30,
backgroundColor:'#00BCD4',
borderRadius:10,
borderWidth: 1,
borderColor: '#fff',
height: 75,
width: 300,
}
});
This if the reducer....the code enters the TOGGLE switch but the state is not updated with the new values.
import {TOGGLE, IS_FAVOURITE} from '../actions'
export const favids = (state=[], action) => {
const {type, recipeid} = action
const newRecipe = {
recipeid: recipeid,
is_fav: true,
}
switch (type) {
case TOGGLE: {
console.log(`Toggle State = ${JSON.stringify(state)}`)
console.log(`NEW REcipe ${JSON.stringify(newRecipe)} `)
// console.log(`Toggle State after adding = ${JSON.stringify(state)}`)
// return state.map(item=>{
// state.recipeid === recipeid ?
// {...item, newRecipe} : {...item,newRecipe}
// })
return {...state, newRecipe}
}
case IS_FAVOURITE: {
console.log(`Is Favourite state =${JSON.stringify(state)}`)
return state.map(item=>{
item.recipeid === recipeid ?
{...item, is_fav: !item.is_fav} : item
})
}
default:
return state
}
}
I've tried different methods of updating the state (see below) but the state is not updated.
I've commented out the older code which was not working
// return state.map(item=>{
// state.recipeid === recipeid ?
// {...item, newRecipe} : {...item,newRecipe}
// })
return {...state, newRecipe}
Any help would be appreciated.
This is the index.js file from the '../actions' folder
export const TOGGLE = 'TOGGLE'
export const toggleFavId = id => ({
type: 'TOGGLE',
recipeid: id
})
export const IS_FAVOURITE = 'IS_FAVOURITE'
export const isFavourite = id => (
{
type: 'IS_FAVOURITE',
recipeid: id
}
)
export const TOGGLE_FAV = 'TOGGLE_FAV'
export const toggleFav = id => ({
type: 'TOGGLE_FAV',
recipeid: id,
})
this is the VisibleFavicon.js file
import {connect} from 'react-redux'
import Favicon from '../components/favicon'
const mapStateToProps = state =>({
favids: state
})
const mapDispatchToProps = dispatch => ({
toggleFavIds: id => dispatch ({
type: 'TOGGLE',
id
}),
isFavourite : id => dispatch ({
type: 'IS_FAVOURITE',
id
}),
})
export default connect(mapStateToProps, mapDispatchToProps)(Favicon)
Try to get whole state of redux instead of state.favids and wherever you want to use that state you can access it like props.state.favids :
const mapStateToProps = (state) =>({
favids: state,
})
Heres the solution to the issue, this is the code for the reducer.
import { TOGGLE, IS_FAVOURITE } from '../actions'
export const favids = (state = [], action) => {
const { type, recipeid } = action
const newRecipe = {
recipeid,
is_fav: true,
}
switch (type) {
case TOGGLE: {
return state.findIndex(recipe => recipe.recipeid === recipeid) === -1 ?
[...state, newRecipe] :
state.map(recipe =>
recipe.recipeid === recipeid ?
{ ...recipe, is_fav: !recipe.is_fav } : recipe
)
}
default:
return state
}
}

How to integrate React MD autocomplete with redux?

I want to integrate react-md with redux, but I don't understand how to trigger the onAutocomplete function. For now I only want to get some hard coded data from the Action, later on I'll add an api call and the search text as parameter.
Here is my action with the hard coded data that I want to dispatch:
export const searchCityAutoComplete = () => {
// no need for text parameter to search at this point
const users = [{
id: '1',
name: 'Robin',
}, {
id: '2',
name: 'Yan',
}]
return {
type: "AUTOCOMPLETE_SEARCH",
payload: users
};
}
Here is the reducer:
const initState = {
searchResults: [],
}
const sitesReducer = (state = initState, action) => {
switch (action.type) {
case "AUTOCOMPLETE_SEARCH":
state = {
...state,
searchResults: action.payload
}
break;
default:
return state;
}
return state;
}
export default sitesReducer;
And here is the component
import React from 'react';
import { connect } from 'react-redux';
import { searchCityAutoComplete } from '../actions/sitesActions';
import Autocomplete from 'react-md/lib/Autocompletes';
const SearchAutocomplete = ({ searchResults, onAutocomplete }) => (
<div >
<div className="md-text-container" style={{ marginTop: "10em" }}>
<Autocomplete
id="test-autocomplete"
label="Autocomplete"
dataLabel="name"
autocompleteWithLabel
placeholder="Search Users"
data={searchResults}
onAutocomplete={(...args) => {
searchCityAutoComplete(args)
console.log(args);
}}
deleteKeys={[
"id",
]}
simplifiedMenu={false}
anchor={{
x: Autocomplete.HorizontalAnchors.CENTER,
y: Autocomplete.VerticalAnchors.TOP
}}
position={Autocomplete.Positions.BOTTOM}
/>
</div>
</div>
);
const mapStateToProps = state => {
console.log(state)
return {
searchResults: state.sitesReducer.searchResults,
}
}
const mapDispatchToProps = dispatch => ({
onAutocomplete: () => { dispatch(searchCityAutoComplete()) }
})
export default connect(mapStateToProps, mapDispatchToProps)(SearchAutocomplete);
As you probably notice, the onAutocomplete function isn't in the same scope as the component... so I guess that's why it's not triggered. For a starting point I just need to get the data from the action - once I type in the autocomplete text box...thanks.
From react-md docs :
onAutocomplete : An optional function to call when an autocomplete suggestion is clicked either by using the mouse, the enter/space key,
or touch.
And so onAutocomplete is only called when you select a suggestion. And it's not what you're looking for. What you're looking for is the onChange prop :
onChange : An optional function to call when the Autocomplete's text field value changes.
Here you can find a simple example code : https://codesandbox.io/s/muddy-cdn-l85sp
You can just pass your onAutocomplete action straight into Autocomplete component:
const SearchAutocomplete = ({ searchResults, onAutocomplete }) => (
<div>
<div className="md-text-container" style={{ marginTop: "10em" }}>
<Autocomplete
id="test-autocomplete"
label="Autocomplete"
dataLabel="name"
autocompleteWithLabel
placeholder="Search Users"
data={searchResults}
onAutocomplete={onAutocomplete} // Pass the action from props here
deleteKeys={[
"id",
]}
simplifiedMenu={false}
anchor={{
x: Autocomplete.HorizontalAnchors.CENTER,
y: Autocomplete.VerticalAnchors.TOP
}}
position={Autocomplete.Positions.BOTTOM}
/>
</div>
</div>
);
Then in mapDispatchToProps you'll need to accept autocomplete value and do a search on it or set it to reducer:
const mapDispatchToProps = dispatch => ({
onAutocomplete: (value) => dispatch(searchCityAutoComplete(value))
})
export const searchCityAutoComplete = (value) => {
// do smth with the value
const users = [{
id: '1',
name: 'Robin',
}, {
id: '2',
name: 'Yan',
}]
return {
type: "AUTOCOMPLETE_SEARCH",
payload: users
};
}

Resources