delete not working in ternary in setState - javascript

So I have been struggling with getting this section of the application working 100% as can be seen with these related questions:
Method renders correctly when triggered one at a time, but not using _.map in React-Redux container
Object passed into Redux store is not reflecting all key/values after mapStateToProps
So the setup is this... a bunch of buttons are dynamically generated based on the number of data "cuts" for a specific item (basically different ways of looking at the data like geography, business segment, etc.). The user can select one button at a time, click a Select All. This will retrieve the data related to the cut from the server and generate a table below the buttons.
One-at-a-time selections is working, select all is working, clear all is working. However, what I am trying to setup now is if the person clicks the same button again, it toggles that data point off.
This is where I was left after one of my previous questions:
onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => ({cuts: {...prevCuts, [cut]: cut}}),
() => this.props.bqResults(this.state.cuts)
);
}
Works fine for one at a time selections and the Select All (this function is called via a map from a different function).
Modified to this, which I was hoping would toggle the data point off:
onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => (
this.state.cuts.hasOwnProperty(cut)
?
delete this.state.cuts[cut]
:
{cuts: {...prevCuts, [cut]: cut}}),
() => this.props.bqResults(this.state.cuts)
);
What I would think should be happening, is checks if the key is there and if it is to delete it which will toggle the button off. And it does, it changes the button status to unselected.
However, what I would think should also happen, is since this.state.cuts is being modified, it will send the new this.state to the this.props.bqResults action. While the button is toggling off, it still shows the data related to the cut, so that store is not being updated for whatever reason. How should I be handling this?
Here is the remainder of the related code:
// results.js actions
export function bqResults(results) {
console.log(results); // shows all of the selected cuts here
return function(dispatch) {
dispatch({
type: FILTER_RESULTS,
payload: results
})
}
}
// results.js reducer
import {
FILTER_RESULTS
} from '../actions/results';
export default function(state = {}, action) {
switch(action.type) {
case FILTER_RESULTS:
console.log(action.payload); //prints out all the cuts
return {
...state,
filter_results: action.payload
}
default:
return state;
}
return state;
}
const rootReducer = combineReducers({
results: resultsReducer,
});
export default rootReducer;

onCutSelect(cut) {
this.setState(
({cuts: prevCuts}) => {
if (cuts.hasOwnProperty(cut)) {
const newCut = {...this.state.cuts}
delete newCut[cut]
return newCut
} else {
return {cuts: {...prevCuts, [cut]: cut}}
}
},
() => this.props.bqResults(this.state.cuts)
);
}
A few things. First, don't mutate state directly like that. Using delete removes the index number of the existing this.state.cuts. Perform this operation in a way that creates a completely new array when assigning your new value. I use the spread operator for this.
Also, when you return a delete operation, it's not returning what the array is after using delete. It returns delete's return value, which in this case is a boolean.
function delIdx(arr, idx) {
return delete arr[idx]
}
console.log(delIdx([1,3,4], 3))

Related

react shoping cart mutation issue

I'm practicing React, developing a Store, for adding the feature of the Shopping Cart I use this example.
But in my implementation, even though my version is almost the same, the "add to cart" button doesn't differentiate between each product, meaning:
1st click affect all the buttons not only the clicked one and the other buttons change to "add more" legend
each posterior click only adds more products of the same kind the user 1st clicked, ignoring if clicked another one.
Seems the error its caused by a mutation in a Reducer Function:
export const CartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
if (!state.cartItems.find((item) => item.id === action.payload.id)) {
//-------HERE
state.cartItems.push({
//mutation here?
...action.payload,
quantity: 1,
});
//-------
}
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems],
};
What would be an alternative to this?
How do I push items in the state without a mutation?
The complete te file its here
Here you can check the deploy and replicate the error, and here its the correct functionality demo
state.cartItems.push is wrong here, you are mutating the state (antipattern).
first you check whether your item exists as you did at the begging. If the item exists simply return the state. If not you can return
you may return your state as
return {
...state,
...sumItems(state.cartItems + 1),
cartItems: [...state.cartItems, action.payload],
};
Before going into details and complex update operations, you should be familiar with the topics array destruct, object destruct, shallow copy, spread operations, copying arrays, slice, splice, filtering operations. Otherwise you may perform some direct mutation as above.If they are too complex, you can use other libraries which make this operations easier, for example immutable.js
But remember, using extra library will cause a small performance loss.
https://redux.js.org/recipes/using-immutablejs-with-redux
You are mutating the original state, which is bad practice in redux. What you can do is something like this to prevent it:
export const CartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
if (!state.cartItems.find((item) => item.id === action.payload.id)) {
// If you have Lint problems with this declaration, you can set a variable before the switch
const cartItems = [...state.cartItems, action.payload];
return {
...state,
...sumItems(cartItems),
cartItems,
};
}
return state;
}
}

Filter in react query not working properly on first attempt

I am trying to get only females from an array using a filter, but on the first attempt react query returns the whole array, after that it is working fine. Any idea what property I have to add or remove, so this side effect disappears.
Here is my code:
import React, { useState } from "react";
import { useQuery } from "react-query";
import getPersonsInfo from "../api/personCalls";
export default function Persons() {
const [persons, setPersons] = useState([]);
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onSuccess: (data) => {
setPersons(data.data);
},
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
const result = personData.data.filter(
(person) => person.gender === "female"
);
setPersons(result);
};
return (
<>
<button onClick={getFemaleOnlyHandler}>Female only</button>
{status === "loading" ? (
<div>Loading ... </div>
) : (
<div>
{persons.map((person) => (
<div>
<p>{person.name}</p>
<p>{person.lastName}</p>
<p>{person.address}</p>
<p>{person.gender}</p>
<p>{person.country}</p>
<p>{person.city}</p>
</div>
))}
</div>
)}
</>
);
}
I added the full code in code sandbox: https://codesandbox.io/s/relaxed-drake-4juxg
I think you are making the mistake of copying data from react-query into local state. The idea is that react-query is the state manager, so the data returned by react-query is really all you need.
What you are experiencing in the codesandbox is probably just refetchOnWindowFocus. So you focus the window and click the button, react-query will do a background update and overwrite your local state. This is a direct result of the "copy" I just mentioned.
What you want to do is really just store the user selection, and calculate everything else on the fly, something like this:
const [femalesOnly, setFemalesOnly] = React.useState(false)
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
setFemalesOnly(true)
};
const persons = femalesOnly ? personData.data.filter(person => person.gender === "female") : personData.data
you can then display whatever you have in persons, which will always be up-to-date, even if a background update yields more persons. If the computation (the filtering) is expensive, you can also use useMemo to memoize it (compute it only when personData or femalesOnly changes - but this is likely a premature optimization.
I'm not totally familiar with react-query however the problem is likely that it is re-fetching (async!) everytime the component updates. Since setPersons() triggers an update (ie. sets state) it'll update the new persons state to be the filtered female list and then trigger a fetch of all persons again which comes back and sets the persons state back to the full list (ie. see what happens when you click the female filter button and then just leave it).
There is a more idiomatic way to achieve this in React which is to keep a "single source of truth" (ie. all the persons) and dynamically filter that based on some local ui state.
For example see below where data becomes the source of truth, and persons is a computed value out of that source of truth. This has the benefit that if your original data changes you don't have to manually (read: imperatively) update it to also be females only. This is the "unidirectional data flow" and "reactivity" people always talk about and, honestly, it's what makes React, React.
const { data = { data: [] }, status } = useQuery(
"personsData",
getPersonsInfo,
{
onSuccess: (data) => {},
onError: (error) => {
console.log(error);
}
}
);
const [doFilterFemale, setFilterFemale] = useState(false);
const persons = doFilterFemale
? data.data.filter((person) => person.gender === "female")
: data.data;
https://codesandbox.io/s/vigorous-nobel-9n117?file=/src/Persons/persons.jsx
This is ofc assuming you are always just loading from a json file. In a real application setting, given a backend you control, I would always recommend implementing filtering, sorting and pagination on the server side otherwise you are forced to over-fetch on the client.

Why is my array variable undefined in my component when I try to use array methods on it?

I'm having an issue with displaying an array of Pokemon moves that I've retrieved from the database (using a rails/react/redux). The strange thing is that when I display the entire array without trying to use join on it, everything's fine. But when I try to use join on it, I get this error message: Uncaught TypeError: Cannot read property 'join' of undefined. And when I display the entire array variable as is, without modifying anything, it does display.
I've gone through my code and I've confirmed that the routing is fine and the right action creators are being dispatched. I've also gone through my reducers and I can confirm that they are using the right action constants to make decisions as to what the new state object is. The only thing that I can think of is that maybe the problem lies in one of these places below:
// the component that renders my Pokemon details onto the page
class PokemonDetail extends React.Component {
componentDidMount() {
this.props.requestSinglePokemon(this.props.match.params.pokemonId);
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.pokemonId !== this.props.match.params.pokemonId) {
this.props.requestSinglePokemon(this.props.match.params.pokemonId);
}
}
render() {
const { pokemon } = this.props;
if (!pokemon) return null;
return (
<section className='pokemon-detail'>
<figure>
<img src={ pokemon.image_url } alt={ pokemon.name } />
</figure>
<ul>
<li><h1>{ pokemon.name }</h1></li>
<li>Type: { pokemon.poke_type }</li>
<li>Attack: { pokemon.attack }</li>
<li>Defense: { pokemon.defense }</li>
<li>Moves: { pokemon.moves.join(', ') }</li>
</ul>
</section>
);
}
}
// the reducer
const pokemonReducer = (state = {}, action) => {
Object.freeze(state);
let poke;
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, state, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
poke = action.payload.pokemon;
return Object.assign({}, state, { [poke.id]: poke });
default:
return state;
}
}
// the action creators
export const requestSinglePokemon = id => dispatch => {
dispatch(startLoadingSinglePokemon());
return APIUtil.fetchSinglePokemon(id).then(pokemon => dispatch(receiveSinglePokemon(pokemon)));
}
export const receiveSinglePokemon = payload => ({
type: RECEIVE_SINGLE_POKEMON,
payload
});
// the backend APIUtil method
export const fetchSinglePokemon = id => (
$.ajax({
method: 'GET',
url: `api/pokemon/${id}`
})
);
It's the strangest thing...when I do { JSON.stringify(pokemon.moves) } I can literally see that it is an array of strings (of Pokemon moves). And even when I just display it like this, { pokemon.moves }, it seems to be correctly grabbing the right Pokemon; the name, type, attack, and defense text is all there and the moves is also displayed (although not correctly formatted).
NOTE: Upon observing my log (it shows me the action creators that are dispatched when I interact with the page), the error with join seems to pop up before (or instead?) of the action creator that should have been dispatched (i.e. I see that the START_LOADING_SINGLE_POKEMON action is never dispatched before this error pops up)
for the first render of your app , no actions have been dispatched.
componentDidMount occurs after initial render.
hence, unless the parent component of "PokemonDetail" is passing an intial pokemon object with key "moves " whose value is of type array, it would casue this error for first render.
you can either have conditional rendering inside "PokemonDetail" component based on "Pokemon" object or have inital state of "pokemon" contain moves array in reducers.

Reactjs - add/remove item from array and store using checkbox

Prerquisite
I'm fetching a list of accounts (Ajax request) which I display on page load (with a checkbox next to them). By default all the accounts are selected and added to the store (redux).
Goal
Add/remove accounts from array & store (redux) when checkbox are checked/unchecked:
checbox is checked --> add account to array & store
checkbox is unchecked --> remove account from array & store
Logic
I created two separate actions & reducers:
one to manage the checkbox status
one to manage the addition/removal of the account to the array &
store
When testing my code, it works fine at the beginning but eventually the accounts added/removed are not correct. The issue must be in savingAccount() but not sure what I'm doing wrong?
My code
Pre-populating data to the store in ComponentWillMount():
componentWillMount = () => {
let defaultAccount = this.props.account
let defaultCheckbox = this.props.checkboxStatus
for(let i =0; i < this.props.products.arrangements.items.length; i++){
const data = {}
data['id'] = i
data['isSelected'] = true
data['sortCode'] = this.props.products.arrangements.items[i].sortCode
data['accountNumber'] = this.props.products.arrangements.items[i].accountNumber
data['accountName'] = this.props.products.arrangements.items[i].accountName
defaultAccount = defaultAccount.concat(data)
const checkboxesArray = {}
checkboxesArray['id'] = i
checkboxesArray['checked'] = true
defaultCheckbox = defaultCheckbox.concat(checkboxesArray)
}
this.props.addAccount(defaultAccount)
this.props.toggleCheckbox(defaultCheckbox)
}
Displaying list of accounts from Ajax response (this.props.products.arrangements.items)
render() {
return (
<div>
{typeof(this.props.products.arrangements.items) !== 'undefined' &&
(Object.keys(this.props.account).length > 0) &&
(typeof(this.props.checkboxStatus) !== 'undefined') &&
(Object.keys(this.props.checkboxStatus).length > 0) &&
(Object.keys(this.props.products.arrangements.items).length > 0) &&
<div>
{this.props.products.arrangements.items.map((item,i) =>
<div className="accountContainer" key={i}>
<Checkbox
required
label={"Account Number "+item.accountNumber+" Product Name "+item.accountName}
value={true}
checked={this.props.checkboxStatus[i].checked === true? true: false}
onChange = {(e) => {
this.toggleChange(this.props.checkboxStatus[i])
this.saveAccount(e, i, item.accountNumber, item.accountName)
}}
/>
</div>
)}
</div>
}
</div>
)
}
Updating isSelected value when checkbox is checked/unchecked:
saveAccount = (e, i, accountNumber, productName) => {
const data = {};
data['id'] = i
data['accountNumber'] = accountNumber
data['productName'] = productName
if(this.props.checkboxStatus[i].checked === true){
let accountArray = Array.from(this.props.account)
accountArray[i].isSelected = true
this.props.addAccount(accountArray)
}
else {
let accountArray = Array.from(this.props.account)
accountArray[i].isSelected = false
this.props.addAccount(accountArray)
}
}
Reducer
function Eligible(state = { products: {}, account: [], checkboxStatus: [] }, action){
switch (action.type){
case ADD_PRODUCTS:
return {
...state,
products: action.data
}
case ADD_ACCOUNT:
return {
...state,
account: action.data
}
case TOGGLE_CHECKBOX:
return {
...state,
checkboxStatus: action.data
}
default:
return state
}
}
Actions
export const ADD_PRODUCTS = 'ADD_PRODUCTS'
export const ADD_ACCOUNT = 'ADD_ACCOUNT'
export const TOGGLE_CHECKBOX = 'TOGGLE_CHECKBOX'
export function addProducts(data){
return {type: ADD_PRODUCTS, data}
}
export function addAccount(data) {
return { type: ADD_ACCOUNT, data}
}
export function toggleCheckbox(data) {
return { type: TOGGLE_CHECKBOX, data}
}
Updating checkbox status:
toggleChange = (checkbox) => {
let toggleCheckbox = this.props.checkboxStatus
toggleCheckbox[checkbox.id].checked = !checkbox.checked
this.props.toggleCheckbox(toggleCheckbox)
}
I think the asynchronicity of this.setState is probably causing an issue.
this.state contains both accounts and checkboxes:
this.state = {
accounts: [],
checkboxes: []
}
In your change event handler, you call two functions:
onChange = {(e) => {
this.toggleChange(this.props.checkboxStatus[i])
this.saveAccount(e, i, item.accountNumber, item.accountName)
}}
First toggleChange:
toggleChange = (checkbox) => {
let toggleCheckbox = [...this.state.checkboxes];
toggleCheckbox[checkbox.id].checked = !checkbox.checked
this.setState({
checkboxes: toggleCheckbox
})
this.props.toggleCheckbox(this.state.checkboxes)
}
You're updating the checkboxes property of the state (via this.setState) - all good there. But on the last line, you're passing this.state.checkboxes out. Since this.setState is async, this will likely not reflect the changes you just made (you could send toggleCheckbox instead).
The next function called in the event handler is saveAccount, which contains (partially):
const addAccountState = this.state
if(this.props.checkboxStatus[i].checked === true){
addAccountState.accounts = addAccountState.accounts.concat(data)
this.setState(addAccountState)
this.props.addAccount(addAccountState.accounts)
}
Here you're taking the current value of this.state (which may be old due to the async setState). You update the .accounts property of it, then send the whole thing (which includes .accounts and .checkboxes) to this.setState.
Since the .checkboxes state may have been old (the previous this.setState may not have fired yet), this would queue up the old .checkboxes state to overwrite the new state you tried to save in toggleChange().
A quick and dirty fix there could be to use this.setState({accounts: addAccountState.accounts}) instead, but there may be other issues floating around too (like the modifying of this.state properties directly).
Because setState is asynchronous, subsequent calls in the same update
cycle will overwrite previous updates, and the previous changes will
be lost.
Beware: React setState is asynchronous!
Regarding the separation of store and state... one option might be to not store the checkboxes separately at all, but rather compute them based on which accounts are selected.
It will depend on the needs of your application of course, so I'll be making a few assumptions for the sake of example...
Your application needs a list of selected accounts
Your component needs to show a list of all accounts
Your component has checkboxes for each account: checked = selected = part of application's 'selected accounts' list.
In this case, I would have the list of selected accounts passed in via props.
Within the component I would have the list of all accounts in the local state (if your 'all accounts' list is passed in via props already, then just use that - no local state needed).
Within the render function of the component, I would compute whether the checkbox should be checked or not based on whether or not the account exists in the 'selected accounts' list. If it exists, it's checked. If not, not checked.
Then when the user clicks to check/uncheck the box, I would dispatch the function to add or remove the account from the 'selected accounts' list, and that's it. The account would be added or removed, which would cause your props to update, which would re-render your component, which would check or uncheck the boxes as appropriate.
That may not jive exactly with your particular application's needs, but I hope it gives you some ideas! :)

How to validate radio button inside reducer using the event object passed to the reducer?

I have the following reducer in my React app:
const initialState = {
genderRadio : false,
ageRadio : false
}
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
console.log(action.payload.target);
return state
}
return state;
}
export default reducer;
action.payload is basically the event object that is passed to the reducer, like so from my component:
validateRadioInput : (e) => dispatch({ type: 'VALI_RADIO_INP' , payload : e })
What I would like to do in my reducer is check if the input element has been checked or not and update the state. How do I using the event object check if a element is checked or not checked?
NOTE::-
Before integrating redux I was checking if the checkbox is checked calling a method that resided right inside my component like so:
Array.from(document.getElementsByName('customer_gender')).some( (elem , idx) => { return elem.checked })
But of course I can't use this anymore; any suggestions on how I can validate the checkbox in my reducer using the event object?
First set attribute name to your checkbox element like so:
<input type="checkbox" name="genderRadio"/>
or:
<input type="checkbox" name="ageRadio"/>
And modify your code that set correct piece of state depending on the attribute name of checkbox element.
Example:
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
console.log(action.payload.target);
return { ...state, [payload.target.name]: payload.target.checked };
}
return state;
}
export default reducer;
How do i using the event object check if a element is checked or not checked ?
You shouldn't do that. Your Redux reducers shouldn't be coupled to the DOM if you can help it. Though, it is possible that you can traverse the DOM from the event's target, if you're using React you shouldn't be depending on the DOM at all.
One way to do it is to get your component to have a data representation of the view. This could be your React component's state. Or, you could grab it from the DOM if you're not using React with something like this:
validateRadioInput: (e) => {
const checkedArr = Array.from(document.getElementsByName('customer_gender'))
.map(elem => elem.checked);
return dispatch({
type: 'VALI_RADIO_INP',
payload: checkedArr,
});
}
// reducer
const reducer = ( state = initialState , action ) => {
switch(action.type) {
case "VALI_RADIO_INP":
const valid = action.payload.some(checked => checked);
return { ...state, valid };
}
return state;
}
Ultimately, though, I don't agree with the concept of doing form validation sort of logic in Redux -- just do it in the component and then dispatch some action to Redux if it's valid. Redux shouldn't have to deal with every nitty-gritty state in your application; just state that affects multiple components in potentially complex ways.
Also, note that you may be trying to fix an HTML problem in JS since you can only check one radio button at a time, anyway, and you could just make the HTML have a required field. See HTML5: How to use the "required" attribute with a "radio" input field

Categories

Resources