Dispatching Redux Thunks to re-render React Component - react-redux

I'm trying to implement a sports betting filter, but am getting stuck when trying to dispatch a thunk to re-render the page based on a sport-specific key that I pass into the thunk. I have a dropdown list that has click listeners that dispatch a handler in the parent component (GamesList). I'm noticing my action creator (gotKey) keeps returning the default key('americanfootball_ncaaf') even when clicking on the NFL list item in the DropdownList component. Any help or direction is much appreciated. Thanks!
Redux Store
import axios from 'axios'
require('../../secrets')
const GOT_GAMES = 'GOT_GAMES'
const GOT_MONEYLINES = 'GOT_MONEYLINES'
const GOT_KEY = 'GOT_KEY'
const gotGames = games => ({type: GOT_GAMES, games})
const gotMoneyLines = games => ({type: GOT_MONEYLINES, games})
const gotKey = key => ({type: GOT_KEY, key})
const defaultSportKey = 'americanfootball_ncaaf'
const apiKey = process.env.API_KEY
export const getGames = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}&region=us&mkt=spreads`
)
dispatch(gotGames(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
export const getMoneyLines = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}&region=us&mkt=h2h`
)
dispatch(gotMoneyLines(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
const gamesState = {
spreadGames: [],
moneylineGames: [],
sportKey: ''
}
const gamesReducer = (state = gamesState, action) => {
// console.log('action' + action)
switch (action.type) {
case GOT_GAMES:
return {...state, spreadGames: action.games}
case GOT_MONEYLINES:
return {...state, moneylineGames: action.games}
case GOT_KEY:
return {...state, sportKey: action.key}
default:
return state
}
}
export default gamesReducer
Main GamesList component
import React from 'react'
import {connect} from 'react-redux'
import {getGames, getMoneyLines} from '../store/games'
import SingleGame from './SingleGame'
import DropdownList from './DropdownList.js'
class GamesList extends React.Component {
constructor() {
super()
this.handler = this.handler.bind(this)
}
componentDidMount() {
this.props.getGames()
this.props.getMoneyLines()
}
handler(key) {
this.props.getGames(key)
this.props.getMoneyLines(key)
}
render() {
// console.log(this.state)
const spreadList = this.props.spreadGames.data
const moneylineList = this.props.moneylineGames.data
if (!spreadList || !moneylineList) {
return <div>loading</div>
} else {
return (
<div>
<DropdownList handler={this.handler} />
{spreadList.map((game, index) => (
(Date.now().toString().slice(0, -3) < game.commence_time) ?
<div key={index}>
<SingleGame
spreads={game.sites[0]}
homeTeam={game.home_team}
teams={game.teams}
timeStart={game.commence_time}
moneylineGame={moneylineList[index]}
/>
</div> : null
))}
</div>
)
}
}
}
const mapStateToProps = state => ({
spreadGames: state.games.spreadGames,
moneylineGames: state.games.moneylineGames,
sportKey: state.games.sportKey
})
const mapDispatchToProps = dispatch => ({
getGames: () => dispatch(getGames()),
getMoneyLines: () => dispatch(getMoneyLines())
})
export default connect(mapStateToProps, mapDispatchToProps)(GamesList)
DropdownList Component
import React from 'react'
export default class DropdownList extends React.Component {
constructor(props) {
super()
this.state = {
listOpen: false
}
this.showList = this.showList.bind(this)
this.hideList = this.showList.bind(this)
}
showList(event) {
event.preventDefault()
this.setState({listOpen: true}, () => {
document.addEventListener('click', this.hideList)
})
}
hideList() {
this.setState({listOpen: false}, () => {
document.addEventListener('click', this.hideList)
})
}
render() {
return (
<div>
<div type="button" onClick={this.showList}>
Select A Sport
</div>
{this.state.listOpen ? (
<ul>
<li
onClick={() => {
this.props.handler('americanfootball_nfl')
}}
>
NFL
</li>
<li
onClick={() => {
this.props.handler('americanfootball_ncaaf')
}}
>
NCAA
</li>
</ul>
) : (
<li>test</li>
)}
</div>
)
}
}

You're not passing your key in mapDispatchToProps
const mapDispatchToProps = dispatch => ({
getGames: key => dispatch(getGames(key)), // <-- forward key
getMoneyLines: key => dispatch(getMoneyLines(key)) // <-- forward key
})
react-redux has a nicer api for this, you just pass your action creators directly
export default connect(mapStateToProps, { getGames, getMoneyLines })(GamesList)
https://react-redux.js.org/api/connect#example-usage

Related

Getting an error "A non-serializable value was detected in an action, in the path: `payload`. Value: " in react-redux

I wrote a function in add user and remove user on react-redux. But i getting error on this function
user Slice.js in redux
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: {
id: "",
name: "",
email: "",
},
},
reducers: {
addUser: (state, action) => {
state.users = action.payload;
},
removeUser: (state) => {
state.users = "";
},
},
});
export const { addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
The remove user funcion is returning a empty string. on the screen value is empty.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
import {} from "react-redux";
const User = () => {
const users = useSelector((state) => state.users.users);
const dispatch = useDispatch();
console.log(users);
const removeuser = (state) => {
dispatch(removeUser(state));
};
return (
<div>
<h1>{users.id}</h1>
<h2>{users.name}</h2>
<h3>{users.email}</h3>
<button type="" onClick={removeuser}>
Remove User
</button>
</div>
);
};
export default User;
what the solution of this function
What I am seeing in your error message is that you are passing a React event in your action payload.
Why is this happening? An onClick prop is a function which gets called with the click event as its argument.
<button type="" onClick={removeuser}>
So the argument of removeuser needs to be (event) => {. Or if you don't need to access the event then you can use () => {. There is no scenario in which a state variable makes sense as an argument.
When you call a Redux Toolkit action creator function, the argument that you pass to the function will become the action.payload.
With the way that your slice is set up now, the removeUser reducer does not use the action.payload so it does not need an argument. You just call dispatch(removerUser()).
const onClickRemove = () => {
dispatch(removeUser());
};
/* ... */
<button onClick={onClickRemove}>
or
<button onClick={() => dispatch(removeUser())}>
Does you app have more than one user? As you begin to understand things more, your userSlice will probably evolve. You may end up with an array of users, in which case your removeUser action will need to know which user to remove.
Something like:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: []
},
reducers: {
// Payload is an array of user objects.
setAllUsers: (state, action) => {
state.users = action.payload;
},
// Payload is a single user object.
addUser: (state, action) => {
state.users.push(action.payload);
},
// Payload is the id of the user to remove.
removeUser: (state, action) => {
state.users = state.users.filter(user => user.id !== action.payload);
},
},
});
export const { setAllUsers, addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
const UsersList = () => {
const users = useSelector((state) => state.users.users);
console.log(users);
return (
<div>
{users.map(user => (
<UserListItem key={user.id} user={user}/>
))}
</div>
);
}
const UserListItem = ({ user }) => {
const dispatch = useDispatch();
return (
<div>
<h1>{user.id}</h1>
<h2>{user.name}</h2>
<h3>{user.email}</h3>
<button onClick={() => dispatch(removeUser(user.id))}>
Remove User
</button>
</div>
);
};
export default UsersList;

Lost with useEffect Hooks - data now undefined

First time posting and have my Learner plates on.
Using axios with a json mock-server with gets through a ContextProvider to trying to map my data to present with Bootstrap cards.
(then) also click on the card to present on another page (not attempted yet)
My data was presenting fine in list form and in a card through Outlet - but I'm trying instead to present the whole array through cards.
I'd appreciate any help I can get. Apologies in advance for the lengthy code (I'm not sure where the problem is)
ProductList.js
import React from 'react'
import { ProductContext } from './ProductContext'
import ProductDetail from './ProductDetail'
function ProductList(props) {
function productList(products) {
if (products === null) return
return (
<div className='card-container'>
{
products.map(product => (<ProductDetail />))
}
</div>
)
}
return (
<div direction="horizontal" style={{ textAlign: 'center' }}>
<h1>Artworks</h1>
<ProductContext.Consumer>
{({ products }) => (
productList(products)
)}
</ProductContext.Consumer>
</div>
)
}
export default ProductList
ProductDetail.js
function ProductDetail(props) {
const hasFetchedData = useRef(false)
let params = useParams()
let navigate = useNavigate()
let { getProduct, deleteProduct } = useContext(ProductContext)
let [product, setProduct] = useState()
useEffect(() => {
if (!hasFetchedData.current) {
const res = axios.get("http://localhost:3002/products");
setProduct(res);
hasFetchedData.current = true;
}
}, [])
useEffect(() => {
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
}
fetch()
}, [params.productId]); // eslint-disable-line react-hooks/exhaustive-deps
let [error, setError] = useState()
useEffect(() => {
setError(null)
async function fetch() {
await getProduct(params.productId)
.then((product) => setProduct(product))
.catch((message) => setError(message))
}
fetch()
}, [params.productId, getProduct])
function errorMessage() {
return <Alert variant="danger">Stockroom is empty: {error}</Alert>
}
function handleDeleteProduct(id) {
deleteProduct(id)
navigate('/products')
}
function loading() {
return <div className="w-25 text-center"><Spinner animation="border" /></div>
}
function productCard() {
let { id, artistname, born, piecename, painted, imgurl, price } = product
return (
<Card className="w-25" key={product.id}>
<Card.Img variant="top" src={imgurl} />
<Card.Body>
<Card.Title>{artistname} {born}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">{piecename}</Card.Subtitle>
<Card.Subtitle className="mb-2 text-muted">{painted}</Card.Subtitle>
<Card.Text>
<strong>Price:</strong> <span>${price}</span>
</Card.Text>
<Link to={`/products/${id}/edit`} className="btn btn-primary mx-3">Edit</Link>
<Button variant="danger" onClick={handleDeleteProduct.bind(this, id)}>Delete</Button>
</Card.Body>
</Card>
)
}
if (error) return errorMessage()
if (product === undefined) return loading()
return product.id !== parseInt(params.productId) ? loading() : productCard()
}
export default ProductDetail
ProductContext.js
export const ProductContext = createContext()
export const ProductProvider = (props) => {
const [products, setProducts] = useState([])
useEffect(() => {
async function getProducts() {
await refreshProducts()
}
getProducts()
}, []);
function refreshProducts() {
return axios.get("http://localhost:3002/products")
.then(response => {
setProducts(response.data)
})
}
function getProduct(id) {
return axios.get(`http://localhost:3002/products/${id}`)
.then(response =>
new Promise((resolve) => resolve(response.data))
)
.catch((error) =>
new Promise((_, reject) => reject(error.response.statusText))
)
}
function deleteProduct(id) {
axios.delete(`http://localhost:3002/products/${id}`).then(refreshProducts)
}
function addProduct(product) {
return axios.post("http://localhost:3002/products", product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
function updateProduct(product) {
return axios.put(`http://localhost:3002/products/${product.id}`, product)
.then(response => {
refreshProducts()
return new Promise((resolve) => resolve(response.data))
})
}
return (
<ProductContext.Provider
value={{
products,
refreshProducts,
getProduct,
deleteProduct,
addProduct,
updateProduct
}}
>
{props.children}
</ProductContext.Provider>
)
}

React-Redux Maximum call stack size exceeded when adding object to list

I am creating a simple game react app and when I try to add a player to my players list it seems to be creating an infinite loop and I'm not sure why. I tried to use useEffect to render the player list on initial load but that didn't help so I removed it for now to simplify. Any ideas what I could be doing differently?
App.js
import React, { useEffect } from 'react'
import {useDispatch, useSelector} from 'react-redux';
import './App.css';
import {setPlayerName, increaseCurrentPlayerId, decreaseCurrentPlayerId, addPlayerToList} from './redux/reducers/playerReducer';
function App() {
const dispatch = useDispatch()
const playerName = useSelector(state => state.playerName);
const playerList = useSelector(state => state.playerList);
const currentPlayerId = useSelector(state => state.currentPlayerId)
// dispatch(addPlayerToList('Test'))
const addPlayer = (player) => {
dispatch(addPlayer(player))
dispatch(setPlayerName(''))
}
const renderPlayerList = () => {
if (playerList.length < 1) {
return (
<div>
No Players
</div>
)
} else {
return (
playerList.map(p =>
<p>p.name</p>
)
)
}
}
return (
<div className="App">
<input
type='text'
name='playerName'
onChange={({ target }) => dispatch(setPlayerName(target.value))}
required
/>
Name<br/>
<button type='button'
onClick={() => addPlayer(playerName)}
>
Add Player</button> <br />
<br />
</div>
);
}
export default App;
playerReducer.js
export const playerNameReducer = (state = '', action) => {
switch (action.type) {
case 'SET_PLAYER_NAME':
return action.data;
default:
return state;
}
};
export const playerListReducer = (state = null, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state, action.data];
default:
return state;
}
};
Action Creators
export const setPlayerName = playerName => {
return {
type: 'SET_PLAYER_NAME',
data: playerName,
};
};
export const addPlayerToList = player => {
return {
type: 'ADD_PLAYER',
data: player,
};
};
addPlayer calls itself
const addPlayer = (player) => {
dispatch(addPlayer(player))
}

mapDispatchToProps not updating store

I'm working on a personal project with redux. My mapStateToProps function seems to me properly written. but when I try to use it to send an object to my store nothing works.
Here's my function:
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
And my reducer:
const initialState = {
orderList : []
}
console.log(initialState);
export default function rootReducer ( state= initialState, action){
const orderList = [...state.orderList];
let position
switch (action.type){
case ADD_ORDER:
return {
orderList : [...state.orderList, action.payload]
};
case DELETE_ORDER:
position = orderList.indexOf(action.payload)
orderList.splice(position, 1)
return {
orderList
}
default:
return state;
}
console.log(state)
}
My entire component as requested:
import React, { Component } from 'react';
import { NavItem } from 'react-bootstrap';
import menu from './menu';
import { connect } from 'react-redux';
import { addOrder} from '../action'
class getOrder extends Component {
state = {
number: `CMD-${Date.now()}`,
order:[],
total: 0 ,
menu:menu,
isPaid: false
}
addItem = (index) => {
const order = [...this.state.order];
const menu = [...this.state.menu];
let total = this.state.total;
const pizza = menu[index];
console.log(pizza);
let ind = order.findIndex((item) =>
item.article == pizza.name
)
if (ind === -1){
order.push({article: pizza.name, price: pizza.price, volume:1})
total = total + order[order.length-1].price
} else if (ind != -1){
order[ind].volume++
total = total + order[ind].price
}
this.setState({
order:order,
total:total
})
console.log("youpiii");
console.log(this.state.total);
console.log(this.state.order);
}
render() {
const menuDisplay= menu.map( (item) => {
return (
<div>
<img onClick={() => this.addItem(item.number)} src={`${process.env.PUBLIC_URL}${item.picture}`} alt="picture" />
<div className="tagPrice">
<p>{item.name}</p>
<p>{item.price} €</p>
</div>
</div>
)
});
const currentOrder = [...this.state.order]
const orderDisplay = currentOrder.map((item) => {
let price = item.price*item.volume;
console.log(price);
return (
<div>
<h1>{item.volume} × {item.article}</h1>
<p>{price} €</p>
</div>
)
} );
return (
<div className="takeOrder">
<div className="orderban">
<h1>Pizza Reflex</h1>
</div>
<div>
<div className="menuDisplay">
{menuDisplay}
</div>
<div className="orderBoard">
<h1>Détail de la commande N°{this.state.number}</h1>
{orderDisplay}
<div className="total">
<h2>Soit un total de {this.state.total} € </h2>
</div>
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
export default connect ( mapDispatchToProps) (getOrder);
Can someone tell me what I've missed ?
Thanks for your help !
What you are missing is more of your code it can not be solved with what you have.
In more details what I need is the this.state , combinedReducer
The easiest fix you can do now is changing yow mapDispatchToProps works better if it is an obj
const mapStateToProps = (state) => {
return {
// here you specified the properties you want to pass yow component fom the state
}
};
const mapDispatchToProps = {action1, action2};
export default connect ( mapDispatchToProps) (getOrder);
connectreceives two params mapStateToProps and mapDispatchToProps,
mapDispatchToProps is optional, but mapStateToProps is mandatory, there for you need to specified, if your are not going to pass anything you need to pass a null value
export default connect (null, mapDispatchToProps) (getOrder);
also avoid exporting components without a name
example
function MyButton () {}
const MyButtonConnect = connect(state, dispatch)(MyButton);
export default MyButtonConnect

error:Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined

Thanks for everyone stop here whether you inspire me or not.In App.js,there is error message:Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
This is very classic todolist project in reduxjs official document,but I reshape it with react and react-redux hooks.
Tree view down:
Index.js:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore } from "redux";
import rootReducer from "./reducers";
import { Provider } from "react-redux";
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js:
import React from "react";
import Addtodo from "./components/Addtodo";
import Footer from "./components/Footer";
import Todolist from "./components/Todolist";
const App = () => {
return (
<>
<Addtodo />
<Todolist />
<Footer />
</>
);
};
export default App;
actions.js
let nextTodoId = 0;
const addtodo = (text) => {
return {
type: "ADD_TODO",
id: nextTodoId++,
text: text
};
};
const toggletodo = (id) => {
return { type: "TOGGLE_TODO", id: id };
};
const setfilter = (filter) => {
return {
type: "SET_VISIBILITY_FILTER",
filter: filter
};
};
const todoActions = { addtodo, toggletodo };
const filterActions = { setfilter };
const allActions = {
todoActions,
filterActions
};
export default allActions;
reducers.js
import { combineReducers } from "redux";
const todo = (state = {}, action) => {
switch (action.type) {
case "ADD_TODO":
return { id: action.id, text: action.text, completed: false };
case "TOGGLE_TODO":
if (state.id === action.id)
return { ...state, completed: !state.completed };
else return state;
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [...state, todo(undefined, action)];
case "TOGGLE_TODO":
return state.map((s) => todo(s, action));
default:
return state;
}
};
const visibilityFilter = (state = "ALL", action) => {
switch (action.type) {
case "SET_VISIBILITY_FILTER":
if (state !== action.filter) return action.filter;
default:
return state;
}
};
const rootReducer = combineReducers({ todos, visibilityFilter });
export default rootReducer;
Addtodo.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import allActions from "../actions";
const Addtodo=() => {
const dispatch = useDispatch();
return(
<>
<input
ref={(node) => {
this.input = node;
}}
/>
<button
onClick={() => {
dispatch(allActions.todoActions.addtodo(this.input.value));
this.input.value = "";
}}
>
Add To Do
</button>
</>
);
};
export default Addtodo;
Todolist.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import allActions from "../actions";
const getvisibletodos = (todos = [], currentFilter) => {
switch (currentFilter) {
case "ALL":
return todos;
case "ACTIVE":
return todos.filter((todo) => todo.completed === false);
case "COMPLETED":
return todos.filter((todo) => todo.completed === true);
}
};
const Todolist = () => {
const todos = useSelector((state) =>
getvisibletodos(state.todos, state.visibilityFilter)
);
const dispatch = useDispatch();
return (
<ul>
{todos.map((todo) => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
onClick={() => dispatch(allActions.todoActions.toggletodo(todo.id))}
>
{todo.text}
</li>
))}
</ul>
);
};
export default Todolist;
Footer.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import allActions from "../actions";
const Filterlink = ({
children,
filter
}) => {
const active = useSelector(state => state.visibilityFilter ===filter);
const dispatch=useDispatch();
if (active) {
return <span>{children}</span>;
}
return (
<a href='#'
onClick={e => {
e.preventDefault();
dispatch(allActions.filterActions.setfilter(filter));
}}
>
{children}
</a>
);
};
export const Footer = () => (
<p>
Show:
{' '}
<FilterLink filter='ALL'>
All
</FilterLink>
{', '}
<FilterLink filter='ACTIVE'>
Active
</FilterLink>
{', '}
<FilterLink filter='COMPLETED'>
Completed
</FilterLink>
</p>
);
The codesandebox link available:https://codesandbox.io/s/lesson-27-28-29-transition-to-react-redux-hooks-stackoverflow-0fsh6
Wish everyone get new taste in coding everyday.

Resources