I'm learning redux, and i've a method addPosts to add posts to the list of posts, and I'm doing it like this.
import { createSlice } from "#reduxjs/toolkit";
var initialState = [{ number: 1 }, { number: 2 }, { number: 3 }, { number: 4 }];
export const postsSlice = createSlice({
name: "postsSlice",
initialState,
reducers: {
addPost: (state, action) => {
state = [...state, action.payload];
},
},
});
export const allPosts = (state) => state.posts;
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
and using the state like this.
import { useSelector, useDispatch } from "react-redux";
import { addPost, allPosts } from "./postsSlice";
function Posts() {
var posts = useSelector(allPosts);
var dispatch = useDispatch();
return (
<div>
{posts.map((post) => (
<div>{post.number}</div>
))}
{/* add post */}
<button
onClick={() => {
dispatch(addPost({ number: 1 }));
console.log(posts);
}}
>
addpost
</button>
</div>
);
}
export default Posts;
using state.push(action.payload) works somehow, altough the documentation says not use update state like this, and update in an immutable way.
like this state = [...state, action.payload]. it does not update state with this immutable way.
I don't know what is wrong that i'm doing.
thanks in advance for any help
You are misreading the wrong documentation for the wrong tool it seems - in a Redux Toolkit createSlice reducer, it is always 100% correct to use something like state.push to mutably modify the object in the state variable.
What you cannot do however is what you are trying here: reassign the state variable. That had never any effect in any kind of Redux reducer, unless you would return that state variable later.
If you want to do that, you will need to return [...state, action.payload] instead and leave the state variable alone altogether - it should not be reassigned.
But the recommended way would be that push.
For more, please read Writing Reducers with Immer
As per this instead of directly changing into state you can return in this way
return [...state, action.payload]
Depending on your definition of initialState
Please have a look into working example of react-redux-toolkit-slice-example
Below is the definition of slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = [{ number: 1 }];
export const postsSlice = createSlice({
name: "postsSlice",
initialState,
reducers: {
addPost: (state, action) => {
return [...state, action.payload];
}
}
});
export const allPosts = (state) => state.posts || [];
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
Defining the reducer(postSlice) in store
import { configureStore } from "#reduxjs/toolkit";
import postsReducer from "../features/posts/postsSlice";
export default configureStore({
reducer: {
posts: postsReducer
}
});
Use of slice in component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { addPost, allPosts } from "./postsSlice";
const Posts = () => {
var posts = useSelector(allPosts);
var dispatch = useDispatch();
return (
<div>
{posts.map((post, key) => (
<div key={key}>{post.number}</div>
))}
{/* add post */}
<button
onClick={() => {
dispatch(
addPost({
number: Math.max(...posts.map(({ number }) => number)) + 1
})
);
console.log(posts);
}}
>
Add Post
</button>
</div>
);
};
export default Posts;
Related
I have an input field where I am trying to pass some information before moving onto a separate page. My problem is the Redux state is not changing, but the console is showing the value is being passed correctly. I'm assuming something is wrong with my Slice but I believe I am passing the payload correctly. My Redux slice looks like:
import { createSlice } from "#reduxjs/toolkit";
export const walletSlice = createSlice({
name: "wallet",
initialState: {
wallet: "xxx-xxxx-xxx-xxxx",
},
reducers: {
setWalletAddress: (state, action) => {
state.value = action.payload;
},
},
});
export const { setWalletAddress } = walletSlice.actions;
export default walletSlice.reducer;
While my from component looks like:
import { setWalletAddress } from "../../redux/wallet";
import { useDispatch } from "react-redux";
export default function AddressForm() {
return (
const dispatch = useDispatch();
const handleChangeWallet = (event) => {
dispatch(setWalletAddress (event.target.value));
console.log(event.target.value);
};
<React.Fragment>
<TextField
onChange={handleChangeWallet}
label="Wallet address"
/>
</React.Fragment>
);
}
My store looks pretty standard:
export default configureStore({
reducer: {
wallet: walletReducer,
},
});
I assume that you need to use correct state field name in the reducer function. I guess following code line makes an issue,
state.value = action.payload;
Instead of this, you need to write correct field name wallet not value
state.wallet = action.payload;
You've mistyped value instead of wallet
setWalletAddress: (state, action) => {
state.wallet = action.payload;
},
Not able to access the redux store current state in a Class component.
It shows up console error
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
When I tried to implement the same using a function component with useSelector and useDispatch, everything works as expected. What has gone wrong over here?
reducer.js
let initialState={
count:0
}
const reducer=(state=initialState,action)=>{
switch(action.type){
case ADD_INCREMENT:
return {
...state,
count:state.count+1
};
default: return state;
}
}
export default reducer;
action.js
const Increment=()=>{
return {
type:ADD_INCREMENT
}
}
store.js
import reducer from './reducer';
const store=createStore(reducer);
export default store;
Class Component
import { connect } from 'react-redux';
const mapStateToProps=state=>{
return {
count:state.count
}
}
const mapDispatchToProps=(dispatch)=>{
return {
count:()=>dispatch(action.Increment())
}
}
class Orders extends Component {
render() {
return (
<div>
<h1>Count: {this.props.count} </h1>
</div>
);
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Orders);
In App.js the entire container is wrapped with Provider and store is passed as props
Issue
You've named your state and your action both count, the latter is the one injected as a prop.
const mapStateToProps = state => {
return {
count: state.count // <-- name conflict
}
}
const mapDispatchToProps = (dispatch) => {
return {
count: () => dispatch(action.Increment()) // <-- name conflict
}
}
Solution
Provide different names, count for the state, maybe increment for the action.
const mapStateToProps = state => ({
count: state.count,
});
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch(action.Increment())
})
I have looked into multiple sources trying to solve this problem but could not find any answers. I have a functional component <Dashboard /> which will display some information from an API.
I expected the component to first get into useEffect, execute the getData function and then display {devices} on the screen. What happens, though, is that the store state is updated, but the component not. The {devices} variable is always undefined. I don't think I understand how to access my state variable from reducers/all/dashboard.js with useSelector.
dashboard/index.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import api from "../../services/api";
import * as DashboardActions from "../../store/actions/dashboard";
const Dashboard = (props) => {
const dispatch = useDispatch();
const devices = useSelector(state => state.device)
useEffect(() => {
async function getData() {
const pathname = "/dashboard";
await api
.get(pathname)
.then((res) => {
dispatch(DashboardActions.setData(res.data));
})
.catch((res) => {
console.log(res.response.data);
});
}
getData();
console.log("devices ue ", devices);
}, [dispatch]);
return (
<div>
<h1>Dashboard</h1>
<span>{devices}</span>
</div>
);
};
export default Dashboard;
reducers/all/dashboard.js
const INITIAL_STATE = {
devices: [],
};
function dashboard(state = INITIAL_STATE, action) {
console.log("Action ", action)
if ("DASHBOARD_SET_DATA" === action.type) {
const data = action.data;
console.log("Data: ", data.devices)
state = { ...state, devices: data.devices };
console.log("State ", state)
}
return state;
}
export default dashboard;
actions/dashboard.js
export function setData(data) {
return {
type: "DASHBOARD_SET_DATA",
data,
};
}
I would appreciate any help a lot.
Thanks in advance!
The react-redux useSelector hook is selecting state from your redux store state object.
If your dashboard reducer is combined into your root reducer, something like
const rootReducer = combineReducers({
... other reducers
dashboard,
... other reducers
});
Then the devices state value should be accessed from state.dashboard.devices.
The update for your component:
const devices = useSelector(state => state.dashboard.devices)
Edit: the bug was is a separated helper function that was mutating the state (not displayed in the post).
I'm experimenting with ReactDnD to create a sortable image grid via drag and drop. I've been following this tutorial 1 and trying to implement it with redux instead of React Context.
The issue that I'm having is that my props don't get updated after I re-arrange the images. I have been debugging the reducers and noticed that the state gets somehow updated before the reducer has the chance to do so (which would trigger mapStateToProps to reload my component with the updated state). The problem though it that I have no idea why that happens. I have the feeling that since ReactDnD is also using Redux, it's somehow causing this.
Here are the different parts:
Index.js
export const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</Provider>,
document.getElementById('root')
)
App.js (parent component of DroppableCell and DraggableItem)
class App extends React.Component {
componentDidMount() {
this.props.loadCollection(imageArray)
}
render() {
return (
<div className='App'>
<div className='grid'>
{this.props.items.map((item) => (
<DroppableCell
key={item.id}
id={item.id}
onMouseDrop={this.props.moveItem}
>
<DraggableItem src={item.src} alt={item.name} id={item.id} />
</DroppableCell>
))}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return { items: state.items }
}
export default connect(mapStateToProps, {
moveItem,
loadCollection,
})(App)
DroppableCell (calling the action creator from parent component)
import React from 'react'
import { useDrop } from 'react-dnd'
const DroppableCell = (props) => {
const [, drop] = useDrop({
accept: 'IMG',
drop: (hoveredOverItem) => {
console.log(hoveredOverItem)
props.onMouseDrop(hoveredOverItem.id, props.id)
},
})
return <div ref={drop}>{props.children}</div>
}
export default DroppableCell
DraggableItem
import React from 'react'
import { useDrag } from 'react-dnd'
const DraggableItem = (props) => {
const [, drag] = useDrag({
item: { id: props.id, type: 'IMG' },
})
return (
<div className='image-container' ref={drag}>
<img src={props.src} alt={props.name} />
</div>
)
}
export default DraggableItem
Reducer
import { combineReducers } from 'redux'
const collectionReducer = (state = [], action) => {
// state is already updated before the reducer has been run
console.log('state:', state, 'action: ', action)
switch (action.type) {
case 'LOAD_ITEMS':
return action.payload
case 'MOVE_ITEM':
return action.payload
default:
return state
}
}
export default combineReducers({
items: collectionReducer,
})
The action creator
export const moveItem = (sourceId, destinationId) => (dispatch, getState) => {
const itemArray = getState().items
const sourceIndex = itemArray.findIndex((item) => item.id === sourceId)
const destinationIndex = itemArray.findIndex(
(item) => item.id === destinationId
)
const offset = destinationIndex - sourceIndex
//rearrange the array
const newItems = moveElement(itemArray, sourceIndex, offset)
dispatch({ type: 'MOVE_ITEM', payload: newItems })
}
found the bug - unfortunately was outside the code posted as I thought it was a simple helper function. I realised I was using the 'splice' method to rearrange the imageArray, and therefore mutating the state.
The common cause for my issue when researching this is mutating the state and not returning a new object of the state which causes redux to not recognize a change. However, this is not and has never been an issue and i'm well aware of it. I'm returning a new object. In the logger which you can see in the attached image it displays the successful api call resolved and the nextState is updated but never rendered. Refreshing the page acts exactly the same even though i expected to possibly need to do so upon initial landing to root page.
Component:
import pokemonReducer from '../../reducers/pokemon_reducer';
import PokemonIndexItem from './pokemon_index_item';
import {Route} from 'react-router-dom';
import PokemonDetailContainer from './pokemon_detail_container';
class PokemonIndex extends React.Component {
componentDidMount() {
this.props.requestAllPokemon();
}
render() {
const pokemon = this.props.pokemon;
return (
<section className="pokedex">
<Route path='/pokemon/:pokemonID' component={PokemonDetailContainer} />
<ul>{pokemon && pokemon.map(poke => <li>{poke.name}{poke.id}</li>)}</ul>
</section>
);
}
}
export default PokemonIndex;
and the container:
import {connect} from 'react-redux';
import { selectAllPokemon } from '../../reducers/selectors';
import PokemonIndex from './pokemon_index';
import { requestAllPokemon } from '../../actions/pokemon_actions';
const mapStateToProps = state => ({
pokemon: selectAllPokemon(state)
});
const mapDispatchToProps = dispatch => ({
requestAllPokemon: () => dispatch(requestAllPokemon())
});
export default connect(mapStateToProps, mapDispatchToProps)(PokemonIndex);
the reducer:
import { RECEIVE_ALL_POKEMON, RECEIVE_SINGLE_POKEMON} from '../actions/pokemon_actions';
const pokemonReducer = (initialState = {}, action) => {
Object.freeze(initialState);
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, initialState, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
let poke = action.payload.pokemon
return Object.assign({}, initialState, {[poke.id]: poke})
default:
return initialState;
}
};
export default pokemonReducer;
secondary reducer:
import { combineReducers } from 'redux';
import pokemonReducer from './pokemon_reducer'
const entitiesReducer = combineReducers({
pokemon: pokemonReducer,
});
export default entitiesReducer;
rootreducer:
import {combineReducers} from 'redux';
import entitiesReducer from './entities_reducer';
const rootReducer = combineReducers({
entities: entitiesReducer
});
export default rootReducer;
as requested here is the selectors defined in reducers folder
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
export const selectSinglePokemon = (state) => {
Object.values(state.entities.pokemon)
};
and here is the actions created:
export const RECEIVE_ALL_POKEMON = "RECEIVE_ALL_POKEMON";
export const RECEIVE_SINGLE_POKEMON = "RECEIVE_SINGLE_POKEMON";
import * as APIUtil from '../util/api_util';
export const receiveAllPokemon = (pokemon) => (
{
type: RECEIVE_ALL_POKEMON,
pokemon
}
);
export const requestAllPokemon = () => (dispatch) => {
APIUtil.fetchAllPokemon()
.then(
pokemon =>
{ dispatch(receiveAllPokemon(pokemon));}
);
};
export const receiveSinglePokemon = data => (
{
type: RECEIVE_SINGLE_POKEMON,
data
}
);
export const requestSinglePokemon = id => (dispatch) => {
APIUtil.fetchSinglePokemon(id)
.then(pokemon => {dispatch(receiveSinglePokemon(pokemon));
return pokemon;});
};
nextstate showing in console
As you stated in your question, your redux state is getting properly set but your new state is never being rendered and I think this has to do with your selector. It looks to me that you forgot to return your computed state.
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
// will return undefined
For returning your state you have two options:
Explicit return
export const selectAllPokemon = (state) => {
return Object.values(state.entities.pokemon);
};
Implicit return
export const selectAllPokemon = (state) => (
Object.values(state.entities.pokemon);
);
I refer to this article or look at the examples I created in playground to get a better unstanding of implicit and explicit return in arrow functions.