Best way to clean an array and insert a new element - react-redux

I want to know what the best way to clean an array would be, write a new element.
My array, selectedUser, will be nurtured with the information I send as payload, after clicking on a row in the table.
You should always have only one element in the array, so I must delete the information it contains when selecting another element. I am currently doing it as follows, but I don't think it's the right one.
import * as actionTypes from "../actions/userModuleActions";
const initialState = {
selectedUser: []
};
const reducer = (state = initialState, action) => {
state.selectedUser = [];
switch (action.type) {
case actionTypes.UPDATE_USER_MODULE_SELECTED_USER:
return {
...state,
selectedUser: state.selectedUser.concat(action.payload)
};
default:
return state;
}
};
export default reducer;

In this case since you are completely replacing the old value with the new you can replace the selectedUser key in state entirely:
import * as actionTypes from "../actions/userModuleActions";
const initialState = {
selectedUser: []
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_USER_MODULE_SELECTED_USER:
return {
...state,
selectedUser: [action.payload], // note that action.payload is wrapped in []
};
default:
return state;
}
};
export default reducer;
No need to "clear" the selectedUser first.

Have you thought about using another data type for storing the selected user? Maybe you should create a SelectedUser model with attributes and set its initial state to undefined.

Related

Reducer in react-redux-toolkit not update state

My reducer removeMovie is not working. Every time I get all initial data from state without one element. I need to update state on every call.
import { createSlice } from '#reduxjs/toolkit';
import movies from '../../services/movies.json';
export const movieSlice = createSlice({
name: 'movie',
initialState: movies,
reducers: {
addMovie: (state, action) => {
state.push(action.payload);
},
removeMovie: (state, action) => {
state = state.filter(movie => movie.title !== action.payload);
console.log(JSON.stringify(state));
},
},
});
// Action creators are generated for each case reducer function
export const { addMovie, removeMovie } = movieSlice.actions;
export default movieSlice.reducer;
What you are doing in removeMovie reducer is not tracked by Immer library. With this line state = state.filter(movie => movie.title !== action.payload); you are assigning local state variable, and that is not actually change of state. You can try this:
removeMovie: (state, action) => {
const newState = state.filter(movie => movie.title !== action.payload);
console.log(JSON.stringify(newState));
return newState;
},
You can find more about this on the following link: immer usage patterns

React-Redux ailed propType - value undefined

I am trying to use propTypes to help typeCheck/validate data in my app. The data comes through fine but I get a warning; Warning: Failed prop type: The propallSubjectSubcategoriesis marked as required inOddMainPage, but its value isundefined`.
I've read through some of the other responses but I haven't yet fpound a solution to fix my issues.
COMPONENT
class OddMainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
categoryTables: [],
tableColumns: [],
gettingColumns: false,
selectedCategory: "",
selectedTable: ""
}
}
componentDidMount() {
this.props.dispatch(getAllSubjectSubCategories());
}
//more code to render some data
}}
OddMainPage.propTypes = {
allSubjectSubcategories: PropTypes.array.isRequired,
subCategoryTables: PropTypes.array,
tableColumns: PropTypes.array
}
const mapStateToProps = state => ({
allSubjectSubcategories: state.allSubjectSubcategories.allSubjectSubcategories,
subCategoryTables: state.subCategoryTables.subCategoryTables,
tableColumns: state.tableColumns.tableColumns
})
export default connect(mapStateToProps)(OddMainPage);
REDUCER
const initialState = {
subCategoryTables: [],
allSubjectCategories: [],
allSubjectSubcategories: []
}
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
return {
...state,
allSubjectSubcategories: action.allSubCats
}
default:
return initialState.allSubjectSubcategories
}
}
I also tried setting default state to default: return state but get the same results.
STORE
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const initialState = {};
const middleware = [thunk];
let store;
if (window.navigator.userAgent.includes("Chrome")) {
store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
} else {
store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware))
);
}
export default store;
It looks like you're changing the return type of your reducer form an array to an object on the GET_ALL_SUBJECT_SUBCATEGORIES action.
Looking at initialState for getAllSubcategoriesReducer, you can see the value is an array. However the return value for the GET_ALL_SUBJECT_SUBCATEGORIES branch is an object. You'll need to standardize on one or the other.
Since the initial state of the reducer is just an empty array, the value of state.allSubjectSubcategories in mapStateToProps will be that empty array. So when you call allSubjectSubcategories: state.allSubjectSubcategories.allSubjectSubcategories, you get undefined.
If you want to keep the nested version you will need to change initialState (and fix the default case of the reducer):
// NOTE: I've nested all of your sub-reducer keys to match your `mapStateToProps`.
const initialState = {
subCategoryTables: {
subCategoryTables: [],
},
allSubjectCategories: {
allSubjectCategories: [],
},
allSubjectSubcategories: {
allSubjectSubcategories: [],
}
}
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
return {
...state,
allSubjectSubcategories: action.allSubCats
}
// return `state` here or you will keep reseting your reducer state
default:
return state
}
}
If you want to keep the reducer as an array like the initial state you will need to update your reducer and your mapStateToProps:
export const getAllSubjectSubcategoriesReducer = (state = initialState.allSubjectSubcategories, action) => {
switch (action.type) {
case "GET_ALL_SUBJECT_SUBCATEGORIES":
// assuming action.allSubCats is an array, we want to replace this entire reducer state with the new array
return action.allSubCats
// return `state` here or you will keep reseting your reducer state
default:
return state
}
}
Now that the reducer above always returns an array you can update your mapStateToProps to remove the extra key being introduced before:
const mapStateToProps = state => ({
allSubjectSubcategories: state.allSubjectSubcategories, // note the change here
// you probably want these to be updated as well with similar changes to your other reducers
subCategoryTables: state.subCategoryTables,
tableColumns: state.tableColumns
})

How to save the String to the Redux Store

I learn React-Redux and now I need help with the Store. I cannot get this to work I when the action FOUND_BAD_WORD is hit the bad word is in the payload.
I want to update the Redux Store with the word like this:
(JAVA style pseudo code)
import { ADD_ARTICLE } from "../constants/action-types";
import { FOUND_BAD_WORD } from "../constants/action-types";
const initialState = {
articles: []
};
var badword;
export default function reducer(state = initialState, action) {
if (action.type === ADD_ARTICLE) {
return Object.assign({}, state, {
articles: state.articles.concat(action.payload)
});
}
if (action.type === FOUND_BAD_WORD) {
// If user type a bad word then save the word in the store
badword = action.payload;
}
return state;
}
I know this is an easy question but I have search for this and only find advanced answers
I have an Object connect with the mapStateToProps on the store badword but cant get that to work because I need to change the badword first, I think??
Following the way you are doing it, it would be:
if (action.type === FOUND_BAD_WORD) {
return Object.assign({}, state, {badword: action.payload})
}
But it would be more elegant to handle the reducer actions in a switch statement and to use object spread.
import { ADD_ARTICLE, FOUND_BAD_WORD } from "../constants/action-types";
const initialState = {
articles: [],
};
const yourReducerName = (state = initialState, action) => {
switch(action.type) {
case ADD_ARTICLE : return {
...state,
articles: [...state.articles, action.payload]
}
case FOUND_BAD_WORD : return {
...state,
badword: action.payload
}
default: return {...state}
}
}
export default yourReducerName

Load data before createStore

I’ve created some React files where one initializes a Redux store. However, I really need to load some data from a json file before store is initialized.
I’ve tried to import a script loading the json structure then assigning it to the createStore initialState value. But createStore runs before the data is loaded and assigned.
Is there any simple way to say “dont do anything before my axios call is done”???
Action types
actiontypes.js
export const LOAD_DATA_REQUEST='LOAD_DATA_REQUEST';
export const LOAD_DATA_SUCCESS='LOAD_DATA_SUCCESS';
export const LOAD_DATA_ERROR='LOAD_DATA_ERROR';
Actions
actions.js
import * as Actions from './actiontypes';
function load() {
return { type: Actions.LOAD_DATA_REQUEST };
}
function success(res) {
return { type: Actions.LOAD_DATA_SUCCESS, payload: res };
}
function error(ex) {
return { type: Actions.LOAD_DATA_ERROR, payload: ex };
}
export function loadData(url) {
return (dispatch) => {
dispatch(load());
axios.get(url).then((res) => {
dispatch(success(res));
}).catch((ex) => {
dispatch(error(ex));
});
};
}
use this in reducers that requires
import * as Actions from './actiontypes';
const newState = Object.assign({}, state);
switch (action.type) {
case Actions.LOAD_DATA_REQUEST:
{
//maybe you load
newState.loading = true;
return newState;
}
case Actions.LOAD_DATA_SUCCESS:
{
const res = action.payload;
//do what you need for this reducer
return newState;
}
case Actions.LOAD_DATA_ERROR:{
/// maybe you will want to show some error message in some reducer?
return newState;
}
}
You just need the first screen of your application on componentWillMount() call the loadData() action
I hope this can help you

Redux architecture: multiple items editable on same page with new item form

I have list of items on single screen. The list is fetched from redux store and rendered as set of form components. At the bottom of list I have "Add new" button.
In my initial implementation I have used container component with setState({newItem: {...}). This has side effect of view flickering when newItem is cleared and new item is added to redux items.
{items.map((item, index) =>
<ItemForm
key={index}
formKey={index.toString()}
initialValues={item}
submitItem={this.handleUpdateItem.bind(this)}
removeItem={() => this.handleRemoveItem(item.id)}
/>
)}
{newItem &&
<ItemForm
key="new"
formKey="new"
initialValues={newItem}
submitItem={this.handleCreateItem.bind(this)}
removeItem={() => this.handleRemoveNewItem()}
/>
}
I think better way to implement this would be not to use newItem and instead handle the new item inside redux. But in that case I have to change the actions/reducers API, in particular the CREATE_ITEM would no longer append item but rather updates the last item in store.
Is there some way I can reduce the flickering or do you have some suggestions how to do it with redux? In particular - what set of actions / reducers should I use to model better solution? Should I rather make API calls in container and simplify the redux actions?
The complexity of the actions/reducers API for me comes from mixing persisted (requires id to make request) and not persisted objects (requires index to find) in redux.
Here is my redux module:
import axios from 'axios';
import * as _ from 'lodash';
export const FETCH_ITEMS = 'items/FETCH_ITEMS';
export const CREATE_ITEM = 'items/CREATE_ITEM';
export const REMOVE_ITEM = 'items/REMOVE_ITEM';
export const UPDATE_ITEM = 'items/UPDATE_ITEM';
const initialState = {
items: []
};
export default function reducer(state = initialState, action) {
let index;
switch (action.type) {
case FETCH_ITEMS:
return {
...state,
items: action.items
};
case CREATE_ITEM: // would become "last item UPDATE_ITEM"
return {
...state,
items: [
...state.items,
action.item
]
};
case REMOVE_ITEM:
index = _.findIndex(state.items, {id: action.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
case UPDATE_ITEM:
index = _.findIndex(state.items, {id: action.item.id});
return {
...state,
items: [
...state.items.slice(0, index),
action.item,
...state.items.slice(index + 1)
]
};
default:
return state;
}
}
export function fetchItems() {
return function (dispatch) {
return axios.get('/api/items')
.then(res => {
const items = res.data;
dispatch({type: FETCH_ITEMS, items});
})
}
}
export function createItem(item) {
return function (dispatch) {
return axios.post('/api/items', item)
.then(res => {
const item = res.data.item;
dispatch({type: CREATE_ITEM, item});
})
}
}
export function removeItem(id) {
return function (dispatch) {
return axios.delete(`/api/items/${id}`)
.then(() => {
dispatch({type: REMOVE_ITEM, id});
});
}
}
export function updateItem(item) {
const {id, ...rest} = item;
return function (dispatch) {
return axios.put(`/api/items/${id}`, rest)
.then(res => {
const item = deserializeItem(res.data.item);
dispatch({type: UPDATE_ITEM, item});
})
}
}

Resources