I am using little state machine for the state management. I have following state
export const todoState = { todoList: [] }
Now I am calling this using the actions where action is like
export const updateTodoList = (state, payload) => {
return {
...state,
toDoList: {
...state.toDoList,
...payload
}
}
}
calling this action
updateToDoList({ id: '1', text:'11', isComplete: 'false })
But still actions does not update the array of toDoList and also it does not take the previous values into consideration .
Can any one help me with the actions updation code ? Thanks.
1) TYPO: todoList instead of toDoList
2) todoList is an array not an object. So spread it into an array.
3) Since you are passing an object so no need to spread it
export const updateTodoList = (state, payload) => {
return {
...state,
todoList: [
...state.todoList,
payload, // It will add the payload object into todoList
],
};
};
Related
I am working on a application which uses redux for state management. There, at a some condition, I want to update the state.
My initial state and reducer function looks like this:
import { createSlice } from '#reduxjs/toolkit';
const filterDataTemplate = {
programId: '',
year: '',
};
const initialState = {
//some other state
filterData: { ...filterDataTemplate },
};
const slice = createSlice({
name: 'editFilterSlice',
initialState: initialState,
reducers: {
updateFilterProgramId: (state, action) => {
state.filterData.programId = action.payload;
},
updateFilterYear: (state, action) => {
state.filterData.year = action.payload;
},
},
});
export const {
updateFilterYear,
updateFilterProgramId,
} = slice.actions;
export default slice.reducer;
So filter details containg year and programId is obtained with the help of this code:
const filterDetails = useAppSelector(
(state) => state.locationsFilter.filterData
);
Let's say I have filter data initially:
filterDetails: {year:2021, programId: "Ameria"}
And i want to have my new filter data to be
filterDetails: {year: "", programId: "Ameria"}
So for this what I am doing:
const handleDelete = (e) => {
e.preventDefault();
if (//some condition) {
console.log("delete is called");
dispatch(updateFilterYear(''));
} else {
dispatch(updateFilterProgramId(''));
}
}
handleDelete function is getting called properly when I am clicking a button because I am getting value inside console.
But after running this code my filter data is not updating. I am not sure what I am doing wrong.
Please help with this.
Action.payload is of object type. So You should reference action.payload.year.
I hope this example will be of any use
setTodoDate: {
reducer: (state, action: PayloadAction<TodoDate>) => {
state.currentDate = action!.payload.date;
},
prepare: (value) => ({
payload: { ...value },
}),
}
I think the issue is because you are trying to mutate your state directly. This is bad practice, and Redux state (and more generally react) is intended to be immutable. Reducers should return a copy of the state, along with the updated values. Documentation linked below.
Redux Documentation
Try writing your reducers like the following
updateFilterYear: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
year: action.payload
}
},
updateFilterProgramId: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
programId: action.payload
}
},
I'm using the Redux Toolkit and I'm struggling to find a way to update state within my store that also triggers a reassignment for useSelector.
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state.push(action.payload); // This updates the store but doesn't respect immutability?
}
}
});
I'm aware the above isn't entirely correct, and that something like
state = [...state, ...action.payload]
would be better, BUT for some reason I couldn't get it work correctly any other way. I'm simply trying to add an object to the array.
My component:
export default function App() {
const array = useSelector(selectArray);
return (
{array.map((x) => {
<div>{x.text}</div>
})
)
}
The issue is, whenever the dispatch is called, array doesn't update, which I'd like it to.
I think your issue is the way you push the new value into the array. That is not immutable and it appears the selector is detecting that the array hasn't changed, so it returns the previous value.
Try this:
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state = [ ...state, action.payload ];
}
}
});
This demo should simulate what happens when mutably changing state vs immutably changing state.
const state = {
list: [1]
}
const addItemMutable = (item) => {
const prevState = { ...state }
state.list.push(item)
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
const addItemImmutable = (item) => {
const prevState = { ...state }
state.list = [ ...state.list, item ]
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
addItemMutable(2)
addItemImmutable(3)
I don't understand why React not update my object. In another component through the dispatch I update the state. In this (in code below) code in mapStateToProps categories are changing (console log show one more category). But component not rerender, although in component in useEffect I use props.categories. Event console.log in element does not run
const LeftSidebar = (props: any) => {
console.log('not render after props.categories changed')
useEffect(() => {
props.dispatch(getCategories())
}, [props.categories]);
const addCategoryHandler = (categoryId: number) => {
props.history.push('/category/create/' + categoryId)
};
return (
<div className='left-sidebar'>
<Logo/>
<MenuSidebar categories={props.categories} onClickAddCategory={addCategoryHandler}/>
</div>
);
};
function mapStateToProps(state: State) {
const categories = state.category && state.category.list;
console.log('this categories changes, but LeftSidebar not changing')
console.log(categories)
return { categories };
}
export default connect(mapStateToProps)(LeftSidebar);
I thought if i update state, react update components dependent on this state. How should it work? how should it work? It may be useful, the item that adds the category is not a parent or child, it is a neighbor
My reducer
import {CATEGORIES_GET, CATEGORY_CREATE} from "../actions/types";
export default function (state={}, action: any) {
switch (action.type) {
case CATEGORIES_GET:
return {...state, list: action.payload};
case CATEGORY_CREATE:
return {...state, list: action.payload};
default: return state;
}
}
Thanks for solving problem. All problem was in inmutable data. I used fixtures, and not copied properly array
import {CATEGORIES_GET, CATEGORY_CREATE} from "./types";
import {categoryMenuItems as items} from "../../fixtureData";
import {NewCategory} from "../../types";
let categoryMenuItems = items; // My mistake, I used not immutable value. Not use fixtures for state))
let id = 33;
export function getCategories() {
return {
type: CATEGORIES_GET,
payload: categoryMenuItems
}
}
export function createCategory(newCategory: NewCategory) {
id++
const category = {
title: newCategory.name,
id: id
};
// MISTAKE I use same array, not cloned like let clonedCategoryMenuItems = [...categoryMenuItems]
categoryMenuItems.push(category);
return {
type: CATEGORY_CREATE,
payload: categoryMenuItems
}
}
Not use fixtures for state, use real api :)
Maybe your state not is inmutable. In your reducer use spread operator to add new items
{
list: [
...state.list,
addedCategory
]
}
Instead of
state.list.push(addedCategory)
I am trying to develop an application, that is showing photos from Unsplash given a keyword. I managed to fetch specific photos using unsplash.js:
actions:
export function fetchPhotos(term) {
const unsplash = new Unsplash({
applicationId:
"id",
secret: "secret",
callbackUrl: "callback"
});
const response = unsplash.search
.photos(term, 1, 20)
.then(toJson)
.then(json => json);
return {
type: FETCH_PHOTOS,
payload: response
};
}
export function setCategory(term) {
return {
type: SET_CATEGORY,
categories: [term]
};
}
export function sortPhotos(attribute) {
return {
type: SORT_PHOTOS,
attribute
}
}
Component that renders the photos:
import React, { Component } from "react";
import { connect } from "react-redux";
import SinglePhoto from "../components/SinglePhoto";
class PhotoList extends Component {
renderPhotos() {
const { photos } = this.props;
console.log(photos);
if (!photos) {
return <p>Loading...</p>;
}
return photos.map(photo => {
const url = photo.urls.full;
const id = photo.id;
const alt = photo.description;
return <SinglePhoto url={url} key={id} alt={alt} />;
});
}
render() {
return <div>{this.renderPhotos()}</div>;
}
}
function mapStateToProps(state) {
return {
photos: state.photos,
categories: state.categories
};
}
export default connect(mapStateToProps)(PhotoList);
And reducers:
import { FETCH_PHOTOS, SORT_PHOTOS } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case FETCH_PHOTOS:
return [...action.payload.results];
case SORT_PHOTOS:
break;
default:
return state;
}
}
What I am struggling to do is to actually sort the array of data I receive from the API according to a specific term. The response is an array of objects that makes it impossible to call it in an external component I've called Buttons that I have wanted to set the logic in:
class Buttons extends Component {
render() {
const { created_at: date } = this.props.photos;
console.log(this.props);
return (
<div className="buttons">
{/* <button onClick={() => this.props.sortPhotos(date)}>Sort by creation date</button> */}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
photos: state.photos
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators({sortPhotos}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Buttons);
As I would need to loop over the photos to actually receive their created_at props.
I would like to sort them, for example, taking created_at into account. This would be handled by a button click (there would be other buttons for let's say likes amount and so on). I tried to do this in mapStateToProps until the moment I realized it would be impossible to call this with onClick handler.
As I have read this post, I thought it would be a great idea, however, I am not sure, how can I handle this request by an action creator.
Is there any way that I could call sorting function with an onclick handler?
One approach you can take is using a library such as Redux's reduxjs/reselect to compute derived data based on state, in this case sorted items based on some object key and/or direction. Selectors are composable and are usually efficient as they are not recomputed unless one of its arguments changes. This approach is adding properties to the reducer's state for sort key and sort order. As these are updated in the store via actions/reducers, the selector uses state to derive the elements in the resulting sorted order. You can utilize the sorted items in any connected component.
I've tried my best to recreate a complete example including actions, reducers, selectors, and store structure.
Actions - Created actions for setting sort key/direction. My example is using redux-thunk for handling async actions, but that is in no way necessary:
export const SET_SORT = 'SET_SORT';
const setSort = (sortDirection, sortKey) => ({
type: SET_SORT,
sortDirection,
sortKey
});
export const sort = (sortDirection = 'desc', sortKey = 'created_at') => dispatch => {
dispatch(setSort(sortDirection, sortKey));
return Promise.resolve();
};
Reducer - Updated initial state to keep track of a sort key and/or sort direction with photo objects being stored in a child property such as items:
const initialState = {
isFetching: false,
sortDirection: null,
sortKey: null,
items: []
};
const photos = (state = initialState, action) => {
switch (action.type) {
case FETCH_PHOTOS:
return {
...state,
isFetching: true
};
case RECEIVE_PHOTOS:
return {
...state,
isFetching: false,
items: action.photos
};
case SET_SORT:
return {
...state,
sortKey: action.sortKey,
sortDirection: action.sortDirection
};
default:
return state;
}
};
Selector - Using reselect, create selectors that retrieves items/photos, sortOrder, and sortDirection. The sorting logic can obviously be enhanced to handle other keys/conditions/etc:
import { createSelector } from 'reselect';
const getPhotosSelector = state => state.photos.items;
const getSortKeySelector = state => state.photos.sortKey;
const getSortDirectionSelector = state => state.photos.sortDirection;
export const getSortedPhotosSelector = createSelector(
getPhotosSelector,
getSortKeySelector,
getSortDirectionSelector,
(photos, sortKey, sortDirection) => {
if (sortKey === 'created_at' && sortDirection === 'asc') {
return photos.slice().sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
} else if (sortKey === 'created_at' && sortDirection === 'desc') {
return photos.slice().sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
} else {
return photos;
}
}
);
Component - Utilize selector to render items. Trigger dispatch of sort action via button click passing in a sort key and/or sort order. The linked example uses dropdowns in combination with the button click to set sort key/order:
import { getSortedPhotosSelector } from './selectors';
// ...
handleClick() {
this.props.dispatch(sort('desc', 'created_at'));
}
render() {
const { sortDirection, sortKey, items } = this.props;
<ul>
{items.map(item => <li key={item.id}>{item.created_at}</li>)}
</ul>
<button type="button" onClick={this.handleClick}>SORT</button>
}
const mapStateToProps = state => ({
items: getSortedPhotosSelector(state),
sortKey: state.photos.sortKey,
sortDirection: state.photos.sortDirection
});
export default connect(mapStateToProps)(PhotoList);
Here is a StackBlitz, demonstrating the functionality in action. It includes controlled components such as and to trigger dispatch of a sort action.
Hopefully that helps!
My Redux Store is correctly being updated which can be seen using React Native Debugger. However, the props inside my component are not updating and are undefined.
In my component below you can see I have correctly mapped to the "sessionModerator" reducer. I have verified this and can see the prop when consoling this.props.
Component:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.sharedReducers.sessionModerator
};
};
My reducer is added as seen below:
Reducers Index file:
import { reducer as sessionModerator } from './session/reducers/session-moderator';
export const reducers = combineReducers({
sessionModerator: sessionModerator,
});
Actions File:
import Types from '../../../types';
export const start = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_START,
payload: true
};
};
export const stop = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_STOP,
payload: false
};
};
Reducers File:
import Types from '../../../types';
export const reducer = (state = false, action) => {
switch (action.type) {
case Types.TYPES_SESSION_MODERATOR_START:
return action.payload;
case Types.TYPES_SESSION_MODERATOR_STOP:
return action.payload;
default:
return state;
}
};
In the below image you can see that the store is updated as the value for sessionModerator is set to "true", but the console of the actual props during the operation is undefined.
What I have tried:
I have tried various things mostly revolving around the structure of my state, for example, I tried adding the boolean inside an actual object and updating the value as an object property but that didn't seem to work. I feel like I am not updating the boolean correctly but haven't been able to figure it out.
Any help would be greatly appreciated. Thank you.
sessionModerator is in screenReducers in the debugger not in sharedReducers as in your mapStateToProps.
Try this one:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.screenReducers.sessionModerator
};
};