Getting an error "A non-serializable value was detected in an action, in the path: `payload`. Value: " in react-redux - 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;

Related

RematchJS Refresh Issue

I am new at using RematchJS and have managed to display hardcoded Objects from an array. The dispatcher (addTopicAsync) however does not seems to update the Array, when I try to add a new Object.
The Array is briefly updated and the topic is flashes on the screen, but the Array is empty shortly afterwards.
My model code:
import { createModel } from "#rematch/core";
import { RootModel } from ".";
export interface Topic {
topic: String
}
export interface TopicsList {
list: Array<Topic>
}
const TOPICS_LIST_STATE = {
list: []
}
export const topics = createModel<RootModel>()({
state: TOPICS_LIST_STATE as TopicsList,
reducers: {
addTopic: (state, topic) => {
return { list: [...state.list, { topic }] }
},
clearTopics: () => {
return { list: [] }
}
},
effects: (dispatch) => ({
async addTopicAsync(topic: string) {
await dispatch.topics.addTopic(topic)
},
async clearTopicsAsync() {
await dispatch.topics.clearTopics()
}
})
});
My application code:
// eslint-disable-next-line #typescript-eslint/no-unused-vars
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, Dispatch } from '#vanilla/data'
import { Topic } from 'libs/data/src/lib/models/topics';
export function App() {
const [topic, setTopic] = useState("aaa")
const topicsState = useSelector((state: RootState) => state.topics)
const dispatch = useDispatch<Dispatch>()
const topicChange = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault()
setTopic(event.target.value)
}
const updateTopicList = async () => {
await dispatch.topics.addTopicAsync(topic)
console.log('topicsState : ', topicsState.list)
}
return (
<>
<h3>Topics</h3>
<form>
<input type='text' value={topic} onChange={topicChange} />
<button onClick={() => { updateTopicList() }}> Add Topic</button>
</form>
<div className="container">
{topicsState.list.map((topicRecord: Topic, index: number) => (
<h5 key={index}>
{topicRecord.topic}
</h5>
))}
</div>
</>
)
}
export default (App)

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))
}

[updated]Intergrating NextJS and Redux State Management

this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)

Dispatching Redux Thunks to re-render React Component

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

State updates but Component doesn't re-render

I'm creating a simple react-redux chat application. I managed to display some dummy data from my redux state in my Message component. I succeed to push a new 'message' to the redux state from my Submit component. But the new item doesn´t render in the Message component.
So I tried to console log the previous state and the new state from the messageReducer and it seems to work. I get the state array with all the dummy data + the new pushed object.
Here is the Github repo if needed: https://github.com/MichalK98/Chat.V.2
// Message Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Message extends Component {
render() {
return (
<ul id="chatroom">
{this.props.messages.map((msg) => (
<li className={(msg.username == 'You' ? "chat-me" : "")} key={msg.id}>
<p>{msg.message}</p>
<small>{msg.username}</small>
</li>
)).reverse()}
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.message.messages
}
}
export default connect(mapStateToProps)(Message);
// Submit Component
...
import { connect } from 'react-redux';
...
class Submit extends Component {
state = {
message: []
}
clear = async () => {
await this.setState({
message: ''
});
}
handleChange = async (e) => {
await this.setState({
message: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.writeMessage(this.state.message);
this.clear();
}
render() {
return (
<div className="chat-footer">
<form onSubmit={this.handleSubmit} autoComplete="off">
<input onChange={this.handleChange} value={this.state.message} type="text" placeholder="Skriv något..."/>
<button id="btnSend"><SendSvg/></button>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
messages: state.messages
}
}
const mapDispatchToProps = (dispatch) => {
return {
writeMessage: (message) => { dispatch({type: 'WRITE_MESSAGE', messages: {id: Math.random(), username: 'You', message: message}})}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Submit);
// messageReducer
const initState = {
messages: [
{id: 1, username: 'You', message: 'Hi, data from reducer!'},
{id: 2, username: 'Mattias', message: 'Wow..'},
{id: 3, username: 'Alien', message: 'Awesome!'}
]
}
const messageReducer = (state = initState, action) => {
if (action.type === 'WRITE_MESSAGE') {
state.messages.push(action.messages);
console.log('Action ',action.messages);
console.log('State ',state.messages);
}
return state;
}
export default messageReducer;
I expect that the new data will render in my Message component when I add a new object to the state array in messageReducer.
first of all in your Message Component you should Change mapStateToProps :
const mapStateToProps = (state) => {
return {
messages : state.messages
}
}
And then in your message reducer you should change reducer. this is better way for reducer. you shouldn't directly change state :
const messageReducer = (state = initState, action) => {
switch (action.type) {
case "WRITE_MESSAGE":
return { ...state, messages: [...state.messages, { ...action.messages }] }
default:
return state;
}
}
if you need any help i can help you to complete your project :-)

Resources