Delete Item from Array (React-Native + Redux) - javascript

I have the checklist with users and when I click on the checkbox user should add to the InputField or delete from InputField, if I check to it again.
For now works only ADD.
import ...
export default class NewEvent extends React.Component {
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect = id => {
addMembers(id) }
findSelectedContacts = (contacts, membersArray) => {
const newArr = [];
contacts.forEach(item => {
if(membersArray.indexOf(item.id.toString()) > -1) {
newArr.push(` ${item.name}`)
}
});
return newArr;
}
render() {
const { navigation, members, location, contacts } = this.props;
const membersArray = members ? members.split(',') : [];
const selectedArray = this.findSelectedContacts(contacts, membersArray)
const inputFill = selectedArray.join().trim();
return (
<InputField
customStyle={[eventStyles.input]}
icon="addGuest"
placeholder="Add guests"
onGetText={texts => {
this.handlerChangeText(texts)
}}
value={inputFill}
/>
);
}
}
Also, I have reducer, which adds guests to input:
import { handleActions } from 'redux-actions';
import * as types from '../actions/actionTypes';
export const initialState = {
members: '',
};
const addMembers = (members, id) => {
const res = members ? `${members},${id}` : `${id}`;
return res;
}
export default handleActions(
{
[types.ADD_GUEST]: (state, action) => ({
...state,
members: addMembers(state.members, action.payload),
}),
},
initialState
);
Please advise, how I can change my reducer? I need to add or delete the user from InputFiled if I click on the ONE checkbox.

Currently, it appears that you are storing the members list as a comma-separated string. A better option would be to store the list as an actual array, and then convert that to a string when it's needed in that format, e.g. rendering.
The reducer for it might look something like this (trying to follow your existing code style:
export const initialState = {
// initialState changed to array
members: [],
};
const addMember = (members, id) => {
// add id to the end of the list
return members.concat(id);
}
const removeMember = (members, id) => {
// return a new list with all values, except the matched id
return members.filter(memberId => memberId !== id);
}
export default handleActions(
{
[types.ADD_GUEST]: (state, action) => ({
...state,
members: addMember(state.members, action.payload),
}),
[types.REMOVE_GUEST]: (state, action) => ({
...state,
members: removeMember(state.members, action.payload),
}),
},
initialState
);
And if you then need the list as a string, in your component render() method - or preferrably in your react-redux mapStateToProps selector you can convert it to a string:
memberList = state.members.join(',')

Related

creating a redux like stores using custom hooks

Here I implemented redux like store using custom hooks. everything goes well and code executed correctly but problem is that in reducer under switch statement "TOGGLE" there I return a updated state which is finally stored in globalstate but if I returned empty object {} instead of {products: updated} still globalstate updating correctly with a change that has been done in reducer...since i am not passing globalstate reference then how it is updated correctly
and what listeners exactly do in dispatch method in code
import MarkFavMulti from "./MarkFavMulti";
import classes from "./MarkFav.module.css";
import useStore from "../HookStore/Store";
import {reducer2} from "../SampleReducer";
const MarkFav = props => {
const [outfit, dispatch] = useStore(reducer2);
const onClicked = (id) => {
dispatch({type: "TOGGLE", id: id});
}
const element = outfit.products.map((item) => {
return <MarkFavMulti cloth={item.name}
favorite={item.favorite}
price={item.price}
key={item.id}
clicked={onClicked.bind(this, item.id)} />
});
return (
<main className={classes.Markfav}>
{element}
</main>
);
};
export default MarkFav;
import {useState, useEffect} from "react";
let globalState = {};
let listeners = [];
const useStore = (reducer) => {
const setState = useState(globalState)[1];
const dispatch = (action) => {
let curr = Object.assign({},globalState);
const newState = reducer({...curr}, action)
globalState = {...globalState,...newState};
for(let listener of listeners) {
listener(globalState);
}
};
useEffect(()=>{
listeners.push(setState);
return () => {
listeners.filter(item => item !==setState);
}
},[setState]);
return [globalState, dispatch];
};
export const initStore = (initialState) => {
if(initialState) {
globalState = {...globalState, ...initialState};
}
}
export default useStore;
let initialState = {
products: [
{ id: 1, name: "shirt", price: "$12", favorite: false },
{ id: 2, name: "jeans", price: "$42", favorite: false },
{ id: 3, name: "coat", price: "$55", favorite: false },
{ id: 4, name: "shoes", price: "$8", favorite: false },
]
}
const configureStore = () => {
initStore(initialState);
};
export default configureStore;
export const reducer2 = (state=initialState, action) => {
switch (action.type) {
case "TOGGLE":
let update = {...state};
let updated = [...update.products];
updated = updated.map(item => {
if(item.id === action.id) {
item.favorite = !item.favorite;
return item;
}
return item;
});
return {products: updated};
//if we return {} ...it will updated correctly in globalstate
default:
throw new Error("not reachable");
}
}
The behavior that you are describing is due to this object assignment right here:
item.favorite = !item.favorite;
Here you are directly mutating the properties of the item object. You probably thought that it would be fine since you are using a copy of the products array.
let update = {...state};
let updated = [...update.products];
What actually happens is that updated is a "shallow copy" of the original array. The array itself is a new array, but the items in that array are the same items as in the state. You can read more about that here.
You need to return a new item object instead of mutating it. Here's a concise way to write it using the ternary operator.
case "TOGGLE":
return {
...state, // not actually necessary since products is the only property
products: state.products.map((item) =>
item.id === action.id
? {
...item,
favorite: !item.favorite
}
: item
)
};

How can I use Redux to only update one instance of a component?

I'm trying to use Redux to update my Card Component to disable and change colors on click. Redux dispatches the action fine, but it updates all Cards not just the one that was clicked. Each Card has an object associated with it that hold the word and a value. The value is the className I want to use to change the color when clicked
Component
const Card = ({ wordObj, updateClass, isDisabled, cardClass }) => {
const showColor = (e) => {
updateClass(wordObj);
console.log(cardClass)
};
return (
<button
onClick={(e) => showColor()}
disabled={isDisabled}
className={cardClass}>
{wordObj.word}
</button>
);
};
const mapStateToProps = (state) => ({
cardClass: state.game.cardClass,
});
export default connect(mapStateToProps, { updateClass })(Card);
Action
export const updateClass = (obj) => (dispatch) => {
console.log(obj)
dispatch({
type: UPDATE_CARD,
payload: obj,
});
};
Reducer
const initialState = {
words: [],
cardClass: 'card',
isDisabled: false,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_WORDS: {
return {
...state,
words: payload,
};
}
case UPDATE_CARD:
return {
...state,
isDisabled: true,
cardClass: ['card', payload.value].join(' '),
};
default:
return state;
}
}```
All of your card components are consuming the same cardClass field in the state. When you modify it in this line:
cardClass: ['card', payload.value].join(' ')
All cards that are consuming this field have their classes updated. The same occurs to the isDisable field.
You need to create one object for each card in your state. Here is my implementation (was not tested):
const initialState = {
cards: []
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
// create a card object for each word
case SET_WORDS: {
return {
...state,
cards: payload.map(word => {
return { word: word, cardClass: "card", isDisabled: false }
})
};
}
case UPDATE_CARD:
// here i'm using the wordObj.word passed as payload
// to identify the card (i recommend to use an id field)
const cardIndex = state.cards.findIndex(card => card.word === payload.word);
// get the current card
const card = state.cards[cardIndex];
// create an updated card object obeying the immutability principle
const updatedCard = { ...card, isDisabled: true, cardClass: ['card', payload.value].join(' '), }
return {
...state,
cards: [
...state.cards.slice(0, cardIndex), // cards before
updatedCard,
...state.cards.slice(cardIndex + 1) // cards after
]
};
default:
return state;
}
}
Your mapStateToProps selects a string, but said string changes on any updateClass and that causes all your cards to update, because the selection of state.game.cardClass produces a different value, which triggers a new render for the connected component.
Maybe what you want, is something that identifies the selection, i.e. an id for each card, and select with that id in the mapStateToProps to avoid reading the change, because what's happening right now is the following:
Card A[className="card A"] == after dispatch ==> mapStateToProps => [className="card B"]
Card B[className="card A"] => dispatch('B') => mapStateToProps => [className="card B"]
B is updating the state of both A and B, and that's why the extra render occurs

How to update redux state of arrays?

So I'm creating something like "Trello" clone with react redux nodejs and mongoDB and i have some issue.
The problem is when I add a card to a list its not update the redux state, so I will see the card in the list only after a refresh page. (the card added to the DB but not to redux state).
just for more info: boardlists is an array inside the object board from mongo, inside that array there is objects of list, inside each of them there is an array of cards.
here is my code:
REDUCER
const initialState = {
boardLists: [
],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ITEMS_BEGIN:
return {
...state,
loading: true,
errors: null,
};
case FETCH_ITEMS_SUCCESS:
return {
...state,
loading: false,
boardLists: action.payload.data.boardLists,
};
case FETCH_ITEMS_FAILURE:
return {
...state,
loading: false,
errors: action.payload.errors,
boardLists: [],
};
//handless creation of data
case ADD_LIST:
return {
boardLists: [...state.boardLists, action.payload.list],
};
case ADD_CARD:
return {
boardlists: [...state.boardlists, action.payload.card],
}
ACTIONS
export const fetchItemsBegin = () => ({
type: FETCH_ITEMS_BEGIN,
});
export const fetchItemsSuccess = (data) => ({
type: FETCH_ITEMS_SUCCESS,
payload: { data },
});
export const fetchItemsFailure = (errors) => ({
type: FETCH_ITEMS_FAILURE,
payload: { errors },
});
//dispatched when item needs to be created
export const addList = (list) => {
return {
type: ADD_LIST,
payload: { list },
};
};
// add card
export const addCard = (card) => {
return {
type: ADD_CARD,
payload: { card }
};
};
//dispatched when all the lists from board stored in redux store needs to be read
export const getBoardLists = () => {
return (dispatch) => {
// function starts
dispatch(fetchItemsBegin()); // fetching begins
return http
.get(`${myUrl}/boards/one`) // req data from server
.then(({ data }) => {
console.log(data);
// if data is found
dispatch(fetchItemsSuccess(data)); // success
})
.catch((error) => dispatch(fetchItemsFailure(error))); //errors
};
};
COMPONENT THAT HANDLE THE ADD FUNCTION
handleAddCard = () => {
//add card
const { text } = this.state;
const { listID } = this.props;
const newCard = {
// _id: uuidv4(),
text,
};
cardService.createCard(newCard, listID);
this.props.addCard(newCard);
};
.
.
.
.
.
const mapStateToProps = ({ boardLists, loading, errors }) => ({
boardLists,
loading,
errors,
});
export default connect(mapStateToProps, { addList, addCard, getBoardLists })(ActionButton);
It appears you need to update an object in your lists array, and not add the card item to the list array itself.
In the actions:
// add card
export const addCard = (card, listId) => {
return {
type: ADD_CARD,
payload: { listId, card }
};
};
In the Reducer, you will need to find the list with matching id and add the card to its array e.g.:
case ADD_CARD:
const {listId, card} = action.payload;
return {
...state,
boardLists: state.boardLists.map(list => {
list.cards = list.cards || [];
return list.id === listId ? {...list, cards: [...list.cards, card]} : list
}),
}
This other question on stack overflow could be useful for this part. link

how to check if the current item is available in the cart list in reactjs

I am working on a reactjs ecommerce app for that I need to check in productDetail Page is the product is already available in cart page. Both of the components have different api endpoint. I am not sure how to do that. I am using react-redux for state management.
CartActions.js
export const getItemsAddedToCart = () => dispatch => {
dispatch({
type: cartActionTypes.GET_CART_LOAD
});
new _rest()
.get(URLConstants.urls.GET_ALL_ITEMS_IN_CART)
.then(response => {
dispatch({
type: cartActionTypes.GET_CART_SUCCESS,
payload: response.data
});
})
.catch(err => {
dispatch({
type: cartActionTypes.GET_CART_ERROR,
error: err
});
});
};
export const IsItemInCart = (pId) => ({
type: cartActionTypes.CHECK_ITEM_IN_CART,
pId
});
These are my actions inside productDetails page i am passing product id inside IsItemIncart. Then In reducer I am trying to look into cart ans update state.
CartReducer.js
const initialState = {
loading: false,
loaded: false,
error: null,
itemsInCart: null,
pagination: null,
totalamount: 0,
checkedItems: [],
paymentStatus: "NIL_TRANSACTION",
order: {
addressId: "NIL",
paymentType: "NIL"
},
isItemInCart: false,
bulkOrderData: []
};
export default function (state = initialState, action) {
switch (action.type) {
case cartActionTypes.GET_CART_LOAD:
return {
...state,
loading: false
};
case cartActionTypes.GET_CART_SUCCESS:
return {
...state,
loaded: true,
loading: false,
totalItemInCart: action.payload._embedded.cartResourceList.length,
itemsInCart: action.payload._embedded.cartResourceList,
totalamount: action.payload._embedded.cartResourceList.reduce(
(acc, item) => {
return item.totalAmount + acc;
},
0
),
pagination: action.payload.page
};
case cartActionTypes.CHECK_ITEM_IN_CART:
const findItem = state.itemsInCart.findIndex(
productId => productId === action.pid
);
return {
...state,
isItemInCart: true,
/*
* Check For Item In Cart Here
* */
};
Here is My reducer I want to check them here which i am not sure how to do that.
In CartAction.js
export const IsItemInCart = (productId) => {
return (dispatch, getState) => {
// you can use your cart reducer name instead of cartData
const cartData = getState().cartData;
// match your productId with your products in cart
const product = cartData.find((singleProductInCart) => {return singleProductInCart.productId === productId});
// then proceed further depending on you get product or not
}
}
Actions in redux are meant to transform the state as you've done with getItemsAddedToCart. IsItemInCart is not how you should use actions as checking whether a particular item is in the cart is a derived property that can be calculated outside the store (in the component or otherwise) like below:
import React from 'react';
import { connect } from 'react-redux';
import { getItemsAddedToCart } from './CartActions';
// these will get mapped to the components props
// using the connect function from react-redux
const mapDispatchToProps = (dispatch) => ({
fetchCartItems: () => dispatch(getItemsAddedToCart())
})
const mapStateToProps = (state, props) => ({
cartList: state.cart.itemsInCart,
});
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
// if has not been fetched yet
if (!this.props.cartList) {
this.props.fetchCartItems();
}
}
checkItemInCart(pID) {
return this.props.cartList.find(
(product) => product.id === pID);
}
....
....
}
export default connect(mapStateToProps, mapDispatchToProps)(ExampleComponent);
Read usage with react.

How do I access specific items in an object of arrays in reducers

I want to toggle an item's completed property by with clicking. The problem is I have no idea how to do this within reducer state. The property is stored inside an object of arrays which makes it tricky to locate in reducer.
App.js
import React,{ useReducer,useState } from 'react';
import logo from './logo.svg';
import './App.css';
import {reducer, initialState} from "./reducers/reducer"
function App() {
const [item,setItem] = useState("")
const [state,dispatch] = useReducer(reducer,initialState)
const handleCompleted = () => {
dispatch({type:"TOGGLE_COMPLETED",payload:0})
console.log(state[0])
}
const handleChanges = e => {
setItem(e.target.value)
}
const addTodo = e => {
dispatch({type:"ADD_TODO",newItem:{item:item,id:Date.now(),completed:false}})
e.preventDefault()
console.log(state)
}
return (
<form onSubmit={addTodo}>
<button>submitTodo</button>
<input onChange={handleChanges} value={item} />
<div>
<button onClick={handleCompleted}>completed</button>
{state.list.map(i => <p key ={i.id}>{i.item}</p>)}
</div>
</form>
);
}
export default App;
Reducer.js
export const initialState = {
list :[{item: 'Learn about reducers',
completed: false,
id: 3892987589}]
}
export const reducer = (state,action) => {
switch(action.type){
case "TOGGLE_COMPLETED" :
return state.list[action.payload].completed = !state.list[action.payload].completed
case "ADD_TODO" :
return {...state,list:[...state.list,action.newItem]}
default:
return state}
}
you can use findIndex. In your case use the id to find the index in the array then change the status of completed
const initialState = {
list: [{
item: 'Learn about reducers',
completed: false,
id: 3892987589
}]
}
function changeStatus(id) {
let getIndex = initialState.list.findIndex(e => e.id === id);
initialState.list[getIndex].completed = true;
}
changeStatus(3892987589);
console.log(initialState.list)
You can modify the function as follows in your App.js
const handleCompleted = (id) => {
dispatch({type:"TOGGLE_COMPLETED",payload:id})
}
In the render function
change <button onClick={handleCompleted}>completed</button> to
{state.list.map(i => <p key ={i.id}>{i.item}<button onClick={this.handleCompleted.bind(this, i,id)}>completed</button></p>)}<button onClick={this.handleCompleted.bind(this, id)}>completed</button>
And in Reducers.js
Note: Usually, you would create a map of ids corresponding to the items for ease of updates. But this would also work for now.
export const reducer = (state,action) => {
switch(action.type){
case "TOGGLE_COMPLETED" :
const modifiedState = state.map(item=> {
if(item.id === action.payload.id){
return {
...item,
completed: true
}
}
return item
});
return modifiedState
case "ADD_TODO" :
return {...state,list:[...state.list,action.newItem]}
default:
return state}
}

Categories

Resources