I've got a simple counter in react-redux which I am using to learn how to use these frameworks. I am trying to implement a pair of number inputs which determine the payload of an increment/decrement action pair.
The expected result is that I enter a number into both input fields, and when I click the increment/decrement buttons, the counter goes up or down by the specified amount. What is actually happening is that the increment button simply concatenates numbers onto the end of the counter value, whereas the decrement button behaves as expected. For example:
The expected behaviour here is that I will press + and the counter will go to 5, then if I pressed - it would go down to -5.
Here I have pressed + twice. As you can see, the counter has not gone up to 10 as you might expect, but instead 5 has been concatenated onto the value in the store, rather than added.
Here I have pressed - once. The leading zero has disappeared, and the number has gone down by 10 as expected.
The Code:
Being Redux, it is a bit boilerplate-y, but here is my code:
src/App.js:
import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {increment, decrement} from './actions/counterActions';
function App() {
const counter = useSelector(state => state.counter);
const dispatch = useDispatch();
return (
<div className="App">
<h1>Counter: {counter}</h1>
<input type = "number" id = "incrementAmount"></input>
<br />
<input type = "number" id = "decrementAmount"></input>
<br />
<button onClick = {() => dispatch(increment(document.getElementById("incrementAmount").value))}>+</button>
<button onClick = {() => dispatch(decrement(document.getElementById("decrementAmount").value))}>-</button>
</div>
);
}
export default App;
src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import allReducers from './reducers/';
import { Provider } from 'react-redux';
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
let store = createStore(allReducers, devToolsExtension);
store.subscribe(() => console.log("State: ", store.getState()));
ReactDOM.render(
<React.StrictMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
src/actions/counterActions.js:
export const increment = (incrementAmount) => {
return {
type: "INCREMENT",
payload: incrementAmount
};
}
export const decrement = (decrementAmount) => {
return {
type: "DECREMENT",
payload: decrementAmount
};
}
src/actions/index.js:
import {combineReducers} from 'redux';
import counterReducer from './counterReducer'
const allReducers = combineReducers({counter: counterReducer});
export default allReducers;
src/reducers/counterReducer.js:
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + action.payload;
case "DECREMENT":
return state - action.payload;
default:
console.warn("Action type not recognised: ", action.type);
return state;
}
};
export default counterReducer;
So far I have tried using the Redux DevTools, which show that the state after pressing + is being treated as a string for some reason:
But I've got no idea why!
Any help would be much appreciated.
Cheers.
You should convert payload to number first:
return state + Number(action.payload);
Otherwise the result is a combined string.
Working sample: https://codesandbox.io/s/relaxed-minsky-ptcp3?file=/src/App.js
Related
I'm facing an error that has been searching by myself for 2 days. But currently It's still not resolved, so I came here to ask If anyone ever faced this?
I'm using Redux toolkit in a sharepoint online project for passing data to each other components.
The first component worked perfectly, but when I use useSelector function for the 2nd one, this error appears
Although when I tried using console.log for each component, both are still receiving the data but
using data for the 2nd component will happen this error.
So has anyone ever faced this please help me out~, here is my codes
slice:
import { createSlice } from '#reduxjs/toolkit';
export interface titleState {
title: string;
}
const initialState: titleState = {
title : 'Your title'
};
export const titleSlice = createSlice({
name: 'title',
initialState,
reducers: {
SET_TITLE: (state, action) => {
state.title = action.payload;
}
}
});
export const { SET_TITLE } = titleSlice.actions;
export default titleSlice.reducer;
store
import { configureStore } from '#reduxjs/toolkit';
import titleReducer from "../features/titleSlice/titleSlice";
export const store: any = configureStore({
reducer: {
title: titleReducer
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
first component:
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch, RootState } from "../../../../redux/store/store";
const FirstComponent: FunctionComponent<FirstComponent> = (
props
) => {
const STATE_TITLE = useSelector((state: RootState) => state.title);
console.log(STATE_TITLE);
const dispatch = useDispatch<AppDispatch>();
const handleTitle = (e) => {
dispatch(SET_TITLE(e.target.value));
setTitle(e.target.value);
}
return (
<div>
<textarea
onChange={handleTitle} //works fine
/>
</div>
}
second component:
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch, RootState } from "../../../../redux/store/store";
const SecondComponent: FunctionComponent<ISecondComponentProps> = (props) => {
const TITLE_STATE = useSelector((state: RootState) => state.title)
console.log(TITLE_STATE)
return (
<div>
{YOUR_TITLE} //this line happens error
</div>
)
and here is the error from development tab :
The error happens because your TITLE_STATE is an object and not a string. Try changing the return statement of the second component to
<div>
{TITLE_STATE?.title}
</div>
If this works, the error was because you were trying to render objects directly. And investigate why your textarea component returns an object instead of string as value, since that is the root cause here
I am facing a problem with re-rendering after a state change in my NextJS app.
The function sendMessageForm launches a redux action sendMessage which adds the message to the state.
The problem is unrelated to the returned state in the reducer as I am returning a new object(return {...state}) which should trigger the re-render!
Is there anything that might block the re-render ?
This is the file that calls & displays the state, so no other file should be responsible ! But if you believe the problem might lie somewhere else, please do mention !
import { AttachFile, InsertEmoticon, Mic, MoreVert } from '#mui/icons-material';
import { Avatar, CircularProgress, IconButton } from '#mui/material';
import InfiniteScroll from 'react-infinite-scroller';
import Head from 'next/head';
import { useState, useEffect } from 'react';
import Message from '../../components/Message.component';
import styles from '../../styles/Chat.module.css'
import { useRouter } from 'next/router'
import {useSelector, useDispatch} from "react-redux"
import {bindActionCreators} from "redux"
import * as chatActions from "../../state/action-creators/chatActions"
const Chat = () => {
const router = useRouter()
const { roomId } = router.query
const auth = useSelector((state)=> state.auth)
const messages = useSelector((state)=> state.chat[roomId].messages)
const dispatch = useDispatch()
const {getMessages, markAsRead, sendMessage} = bindActionCreators(chatActions, dispatch)
const [inputValue, setInputValue] = useState("")
const sendMessageForm = (e) => {
e.preventDefault()
console.log("***inputValue:", inputValue)
sendMessage(roomId, inputValue)
}
const loadMessages = (page) => {
if(roomId)
getMessages(roomId, page)
}
//user-read-message
useEffect(() => {
//user-read-message
markAsRead(roomId, auth.user._id)
}, [messages]);
return (
<div className={styles.container}>
<Head>
<title>Chat</title>
</Head>
<div className={styles.header}>
<Avatar/>
<div className={styles.headerInformation}>
<h3>Zabre el Ayr</h3>
<p>Last Seen ...</p>
</div>
<div className={styles.headerIcons}>
<IconButton>
<AttachFile/>
</IconButton>
<IconButton>
<MoreVert/>
</IconButton>
</div>
</div>
<div className={styles.chatContainer}>
<InfiniteScroll
isReverse={true}
pageStart={0}
loadMore={loadMessages}
hasMore={messages.hasNextPage || false}
loader={<div className={styles.loader} key={0}><CircularProgress /></div>}
>
{Object.keys(messages.docs).map((key, index)=>{
return<Message
key={index}
sentByMe={messages.docs[key].createdBy === auth.user._id}
message={messages.docs[key].msg}
/>})}
</InfiniteScroll>
<span className={styles.chatContainerEnd}></span>
</div>
<form className={styles.inputContainer}>
<InsertEmoticon/>
<input className={styles.chatInput} value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
<button hidden disabled={!inputValue} type='submit' onClick={sendMessageForm}></button>
<Mic/>
</form>
</div>)
};
export default Chat;
useSelector requires a new object with a new reference from the object you are passing to it in order to trigger the re-render
What you're doing with return {...state} is just creating a new object for the parent object but not the nested one useSelector is using, which is in your case :
const messages = useSelector((state)=> state.chat[roomId].messages)
So, you should return the whole state as a new object WITH a new state.chat[roomId].messages object
In other words, the references for the root object & the one being used should be changed.
Can't render the noofcartItems in my react UI. I get only NaN value as output in my UI.
Anything wrong in the syntax ? The context I created also seems to be failing.
Please ignore the console logs as I used it for debug purposes.
import CartContext from '../../CartStore/cart-context.js';
import CartIcon from '../Cart/CartIcon.js';
import './CartButton.css';
import { useContext } from 'react';
const CartButton = (props) => {
const context = useContext(CartContext);
const noofcartItems = context.items.reduce((curNo, item) => {
console.log(curNo, item.amount,curNo + item.amount, 'curNo + item.amount');
return curNo + item.amount;
}, 0);
console.log(noofcartItems,'No of cart items');
return (<button className='button' onClick={props.onClick}>
<span className='icon'>
<CartIcon/>
</span>
<span>Cart</span>
<span className='badge'>{noofcartItems}</span>
</button>
)
};
export default CartButton;
import React from 'react'
const CartContext = React.createContext({
items:[],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {}
});
export default CartContext;
You should console log your context.items array and check for the values of amount variable. It seems that one of the amount values must be undefined.
I have a very simple code for incrementing and decrementing when + or - buttons are pressed. but whenever I press the buttons, I receive the error. I've been working on it for about 4 hours and can't find out why. I'm pretty sure that I'm not doing any async actions and my actions are returning JavaScript objects
actions code:
export const increment = () => {
return { type: 'INCREMENT' }
}
export const decrement = () => {
return { type: 'DECREMENT' }
}
reducer code:
export default function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
app component code:
import React from "react";
import {decrement, increment } from "./actions";
import { useSelector, useDispatch } from "react-redux";
export default function App() {
const counter = useSelector((state) => state.counter);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment)}>+</button>
<button onClick={() => dispatch(decrement)}>-</button>
<p>{counter}</p>
</div>
);
}
Much appreciated.
You are dispatching action creator functions not the actions
The increment and decrement functions are called action creators. They return the action object.
You should call the action creator function for dispatching an action.
<button onClick={() => dispatch(**increment()**)}>+</button>
In Redux, actions are objects. You have to execute actions functions into dispatch: dispatch(increment) could be dispatch(increment())
That problem is solved by using a middleware like redux-thunk or saga in the store
configuration.
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import thunk from "redux-thunk";
//Applying redux-thunk solves the problem
//Actions must be plain objects. Use custom middleware for async actions.
export const store = createStore(rootReducer, applyMiddleware(thunk));
I am currently following this tutorial. I've hit a bit of a snag involving mapStateToProps in the following code:
import React from 'react';
import Voting from './voting';
import {connect} from 'react-redux';
const mapStateToProps = (state) => {
return {
pair: state.getIn(['vote','pair']),
winner: state.get('winner')
};
}
const VotingContainer = connect(mapStateToProps)(Voting);
export default VotingContainer;
Here is the Voting component that's imported:
import React from 'react';
import Vote from './Vote';
import Winner from './winner';
const Voting = ({pair,vote,hasVoted,winner}) =>
<div>
{winner ? <Winner winner={winner}/> :
<Vote pair={pair} vote={vote} hasVoted={hasVoted}/>
}
</div>
export default Voting;
It is supposed to render two buttons from the pair prop. The vote prop is a function that will be executed on click, hasVoted disables buttons when true and winner only renders the winner component as shown.
The state is expected to be an immutableJS map that looks like this:
Map({
vote:{
pair:List.of('Movie A','Movie B')
}
});
Instead I am getting an error saying that state is undefined in the state.getIn line.
The code setting the state is in index:
const store = createStore(reducer);
const socket = io(document.location.protocol + '//' + document.location.hostname + ':8090');
socket.on('state', state => store.dispatch({
type: 'SET_STATE',
state
}));
I have logged store.getState()after setting and it is as expected but undefined in mapStateToProps. I also logged the state variable in above context and it's also as expected.
I also set the state normally and it surprisingly works!:
store.dispatch({
type: 'SET_STATE',
state: {
vote: {
pair: ['Movie A', 'Movie B']
}
}
});
The value of state above is exactly what is received from the server
Lastly here's what my reducer looks like:
import React from 'react';
import {Map, fromJS} from 'immutable';
const reducer = (state = Map(), action) => {
switch (action.type) {
case 'SET_STATE':
return state.merge(action.state);
}
}
export default reducer;
What am I doing wrong?
EDIT: I realised that mapStateToProps is not being called after the store.dispatch(). I went through the docs for the possible reasons mapStateToProps is not being called and it's not one of them.
You reducer doesn't have a default action in switch statement. Which is why even though you mentioned the initial state in reducer params, undefined is returned as store initial state
import React from 'react';
import {Map,fromJS} from 'immutable';
const reducer = (state = Map() ,action) => {
switch(action.type){
case 'SET_STATE': return state.merge(action.state);
default:
return state;
}
}
export default reducer;
Adding the default statement will fix the issue :)
I ended up here too because I had failed to pass my rootreducer function to my createStore method. I had:
const store = createStore(applyMiddleware(...middlewares));
I needed:
const store = createStore(rootReducer(), applyMiddleware(...middlewares));