React input onChange renders all components working so slow - reactjs

I have a React + Redux application and running with webpack. Currently in my application i have some small components developed by me.
In some cases we should render Grid kind of components with hundreds of data. For some reasons i can not use paging. And when user clicks to edit button on my grid line item this will open a small dialog. In that window i have an input text element and if user edits input elements value for each key down this causes render all my page with all data and components. So for each keypress there is a lag about 1 second. How can i deal with this?
I am not using and DevTools
My problem is rendering of my components need time. But in this case this is normal i think. I can not remove onChange from my input because it will become readonly.
I have tried to use defaultValue but i will fill my props after render my component because that data coming from an api.
Code
getEditSubWindow(){
var gridData = this.props.stocks.filter(s=>{ return s.isSelected; });
var windowCommandButtons=[
{text:'Cancel', className:'cancel', onClick:null },
{text:'Save', className:'save', onClick:null }
];
return (
<Window
header="Details"
windowButtons={['close']}
canDock={false}
isDialog={true}
onClose={::this.hideEditSubWindow}
>
<Panel header="Details">
<input type="text" placeholder="Information" value={this.props.currentData.name} onChange={::this.setInformation} onBlur={::this.saveInformation} />
<input type="text" placeholder="Information" defaultValue={this.props.currentData.name} onBlur={::this.saveInformation} />
</Panel>
{this.getGrid(gridData)}
</Window> );
}
setInformation(event){
this.props.currentData.name= event.target.value;
this.props.actions.informationChanged(this.props.currentData, event.target.value);
}
saveInformation(event){
this.props.actions.saveInformation(this.props.currentData, event.target.value);
}
render(){
return (
<div className="stocks">
{this.getGrid(this.props.stocks)}
{this.state.showEditSubWindow?this.getEditSubWindow():null}
</div>
)
}
MY ACTIONS
export function requestStockList() {
return (dispatch) => {
stock.getAll()
.then(data => {dispatch(receiveStockList(data.stock)); } )
.catch(error => { console.log(error) });
}
}
export function saveInformation(info) {
return (dispatch) => {
information.save(info)
.then(data => {dispatch(receiveCurrentData(data.information)); } )
.catch(error => { console.log(error) });
}
}
export function informationChanged() {
return (dispatch) => {
dispatch(receiveCurrentData(data.stock));
}
}
export function receiveStockList(stockList) {
return {
type: RECEIVE_STOCKLIST,
stockList
};
}
export function receiveCurrentData(currentData) {
return {
type: CURRENTDATA_CHANGED,
currentData
};
}
MY REDUCER
const initialState = {
stockList:[],
currentData:{}
};
export default function StockReducer(state = initialState, action) {
var newState = Object.assign({}, state);
switch (action.type) {
case actions.RECEIVE_STOCKLIST:
newState.stockList=action.stockList;
break;
case actions.CURRENTDATA_CHANGED:
newState.currentData=action.currentData;
break;
default:
break;
}
return newState;
}

Related

Why are my state values breaking on dispatch?

I am working on a react app where I have a userSettings screen for the user to update their settings on clicking a save button. I have two sliding switches that are saved and a dispatch function is ran to post the data.
Each switch has their own toggle function, and all the functions run at the same time.
My problem is that when I pass the userSettings object to the child component and run both functions, it runs with the wrong values which results in the data not saving properly.
Here is my code:
Parent component that has the toggle functions, handles the state, and set the userSettings object:
class SideMenu extends React.PureComponent {
constructor(props) {
super(props);
const userToggleSettings = {
cascadingPanels: this.props.userSettings.usesCascadingPanels,
includeAttachments: this.props.userSettings.alwaysIncludeAttachments,
analyticsOptIn: false,
};
this.state = {
userToggleSettings,
};
}
toggleIncludeAttachments = () => {
this.setState((prevState) => ({
userToggleSettings: {
...prevState.userToggleSettings,
includeAttachments: !prevState.userToggleSettings.includeAttachments,
},
}));
};
toggleCascadingPanels = () => {
this.setState((prevState) => ({
userToggleSettings: {
...prevState.userToggleSettings,
cascadingPanels: !prevState.userToggleSettings.cascadingPanels,
},
}));
};
includeAttachmentsClickHandler = () => {
this.toggleIncludeAttachments();
};
cascadingPanelsClickHandler = () => {
this.toggleCascadingPanels();
};
render() {
const darkThemeClass = this.props.isDarkTheme ? "dark-theme" : "";
const v2Class = this.state.machineCardV2Enabled ? "v2" : "";
const phAdjustmentStyle = this.getPersistentHeaderAdjustmentStyle();
const closeButton =
(this.state.machineListV2Enabled &&
this.props.view === sideMenuViews.USER_SETTINGS) ||
(!this.props.wrapper && this.props.view === sideMenuViews.SETTINGS);
return (
<div className="sideMenuFooter">
<SideMenuFooterContainer
userToggleSettings={this.state.userToggleSettings} //HERE IS USER_SETTINGS PASSED
/>
</div>
);
}
}
The child component that dispatches the data
SideMenuFooterContainer:
export function mapStateToProps(state) {
return {
translations: state.translations,
userSettings: state.appCustomizations.userSettings,
};
}
export function mapDispatchToProps(dispatch) {
return {
toggleCascadingPanels: (hasCascadingPanels) =>
dispatch(userSettingsDux.toggleCascadingPanels(hasCascadingPanels)),
toggleIncludeAttachments: (hasIncludeAttachments) =>
dispatch(userSettingsDux.toggleIncludeAttachments(hasIncludeAttachments)),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SideMenuFooter);
SideMenuFooterView (where it calls the dispatch):
const saveUserSettings = (props) => {
console.log("userSettings ==>\n");
console.log(props.userToggleSettings);
props.toggleIncludeAttachments(props.userToggleSettings.includeAttachments);
props.toggleCascadingPanels(props.userToggleSettings.cascadingPanels);
};
const cancelButtonClickHandler = (props) => {
if (props.viewTitle === props.translations.USER_SETTINGS) {
return () => props.closeSideMenu();
}
return () => props.viewBackButtonCallback();
};
const doneSaveButtonsClickHandler = (props) => {
return () => {
saveUserSettings(props);
props.closeSideMenu();
};
};
const SideMenuFooter = (props) => {
return (
<div className="side-menu-footer">
<div className="side-menu-footer-container">
<button
className="btn btn-secondary"
onClick={cancelButtonClickHandler(props)}
>
{props.translations.CANCEL}
</button>
<button
className="btn btn-primary"
onClick={doneSaveButtonsClickHandler(props)}
>
{props.translations.SAVE}
</button>
</div>
</div>
);
};
export default SideMenuFooter;
Dispatch functions:
export function toggleIncludeAttachments(hasIncludeAttachments) {
return async (dispatch, getState) => {
const { translations, appCustomizations } = getState();
const updatedUserSettings = {
...appCustomizations.userSettings,
alwaysIncludeAttachments: hasIncludeAttachments,
};
try {
await saveAppCustomizationByName(
CUSTOMIZATIONS.USER_SETTINGS,
updatedUserSettings
);
dispatch(setSettings(updatedUserSettings));
} catch (err) {
dispatch(
bannerDux.alertBanne({
description: "FAILED TO UPDATE USER DATA",
})
);
}
};
}
export function toggleCascadingPanels(hasCascadingPanels) {
return async (dispatch, getState) => {
const { translations, appCustomizations } = getState();
const updatedUserSettings = {
...appCustomizations.userSettings,
usesCascadingPanels: hasCascadingPanels,
};
try {
await saveAppCustomizationByName(
CUSTOMIZATIONS.USER_SETTINGS,
updatedUserSettings
);
dispatch(setSettings(updatedUserSettings));
} catch (err) {
dispatch(
bannerDux.alertBanner({
description: "FAILED TO UPDATE USER DATA",
})
);
}
};
}
Here is a demo:
When I set them both to false and console log the values, it looks like it is getting the correct values, but in the network call, it is getting different values on different calls
console.log output:
First network call to save data header values:
Second network call to save data header values:
NOTE: The dispatch functions work correctly, they where there before all the edits. I am changing the way it saves the data automatically to the save button using the same functions defined before.
Did I miss a step while approaching this, or did I mishandle the state somehow?

Why cant I remove element from array in Reactjs with redux

I am dynamically adding <div> elements to a component by adding them to an array. This is not a problem and works well. The issue I'm trying to solve here is removing the <div> on double click by passing the id of the <div> that was doubled clicked with props when the reducer is dispatched.
The main issue is the array filter function only works when I code hard the div id both on the div and in the filter function when I want to pass the id of e.target.id on dispatch of delDiv reducer.
Note: I can remove the div successfully by changing the addDivReducer like this:
case "ADD_DIV":
return state.concat(
<DivComponent
key={Math.floor(Math.random() * 100) + 1}
id={11} ***************************************************** Changed
/>
);
case "DELETE_DIV":
state = state.filter((elements) => {
return elements.props.id !== 11; *********************************** Changed
});
return state;
But the desired effect is to pass id as props on dispatch as seen in my code below
The reducer that adds a removes elements look like this:
import DivComponent from "../../components/AddDivComponent";
const addDivReducer = (state, action) => {
switch (action.type) {
case "ADD_DIV":
return state.concat(
<DivComponent
key={Math.floor(Math.random() * 100) + 1}
id={Math.floor(Math.random() * 100) + 1}
/>
);
case "DELETE_DIV":
state = state.filter((elements) => {
return elements.props.id !== action.payload;
});
return state;
default:
return (state = []);
}
};
export default addClipartReducer;
The actions index.js look like:
export const addDiv = (props) => {
return {
type: "ADD_DIV",
payload: props,
};
};
export const deleteDiv = (props) => {
return {
type: "DELETE_DIV",
payload: props,
};
};
The delete reducer is being dispatched when the div is double clicked on like this in AddDivComponent.js:
import { useDispatch } from "react-redux";
import { deleteDiv } from "../../store/actions";
const AddDivComponent = (props) => {
const dispatch = useDispatch();
const removeClipart = (e) => {
dispatch(deleteDiv(e.target.id));
};
return(
<div
id={props.id}
className="my-div"
onDoubleClick={removeDiv}
/>
);
};
export default DivComponent;
Finally the array of <div> elements is being shown here in Canvas.js:
import { useSelector } from "react-redux";
const Canvas = () => {
const divList = useSelector((state) => state.addDIV);
return(
<div className="canvas">
{divList}
</div>
);
};
export default Canvas;
you are mutating state at your DELETE_DIV reducer. If you need to handle state, create a copy a first:
// mutating state here to a new value, can lead to problems
state = state.filter((elements) => {
return elements.props.id !== action.payload;
});
I would suggest to return filter directly, given filter already returns the desired next state, while not mutating the original:
case "DELETE_DIV":
return state.filter((elements) => {
return elements.props.id !== action.payload;
});

When I dispatch, payload(value) goes back to old value in React

// EditableNote.js
function EditableNote({ note }) {
const [editableNote, setEditableNote] = useState(note);
const { title, content } = editableNote;
const dispatch = useDispatch();
useEffect(() => {
setEditableNote(note);
}, [note]);
›
useEffect(() => {
dispatch(saveEditableNote(editableNote)); // I think here is problem
}, [dispatch, editableNote]);
const handleBlur = e => {
const name = e.target.id;
const value = e.currentTarget.textContent;
setEditableNote({ ...editableNote, [name]: value });
};
return (
<EditNote spellCheck="true">
<NoteTitle
id="title"
placeholder="Title"
onBlur={handleBlur}
contentEditable
suppressContentEditableWarning={true}>
{title}
</NoteTitle>
<NoteContent
id="content"
placeholder="Note"
onBlur={handleBlur}
contentEditable
suppressContentEditableWarning={true}>
{content}
</NoteContent>
</EditNote>
);
}
export default EditableNote;
I have EditableNote component which is contentEditable. I set its initial state through props from its parent(Note). So if something is changed in note, then editableNote has to changed.
To keep recent props state, I use useEffect. Everything seems working well.
Here is an issue. If I first change color of note and typing, it is updated as expected. But on contrast, if I first typing and change color, editableNote state is not updated.
// Reducer.js
case actions.GET_NOTE_COLOR:
return {
...state,
bgColor: action.payload
}
case actions.CHANGE_NOTE_COLOR:
return {
...state,
notes: state.notes.map(note => note.id === action.payload ?
{ ...note, bgColor: state.bgColor }
: note
)
};
case actions.SAVE_EDITABLE_NOTE: // payload is old value
return {
...state,
editableNote: action.payload,
}
I check what happened in an action. I found everything works until CHANGE_NOTE_COLOR but when dispatch SAVE_EDITABLE_NOTE, its payload is not updated!
I have no idea.. plz.. help me...TT
You have to use the connect wrapper provided by redux to connect actions and state of redux to your components
https://react-redux.js.org/api/connect

How to pass react hook state to another component for each click

I stuck in this moment creating store with different products, that I want to add to the basket. The problem occur when I wanted to pass the state of cardList into Basket component to change the information from "Basket is empty" to display information how many items are currently in basket.
Below I paste my main hooks component with basket component which include all functionality.
Basket component:
import React from 'react'
const Basket = (props) => {
return (
<div>
{props.cardItems.length === 0 ? "Basket is empty" : <div> You have {props.cardItems.length} products in basket!</div>}
</div>
)
}
export default Basket;
Main component:
function
const [cardItems, setCardItems] = useState([]);
const price = 2.50;
useEffect(() => {
fetch(URL, {
method: 'GET',
headers: {
Accept: "application/json",
}
}).then(res => res.json())
.then(json => (setBeers(json), setFilteredBeers(json))
);
}, [])
function handleMatches(toMatch) {...
}
const displayFilterBeers = event => {...
}
const handleRemoveCard = () => {...
}
const handleAddToBasket = (event, beer) => {
setCardItems(state => {
let beerAlreadyInBasket = false;
cardItems.forEach(item => {
if (item.id === beer.id) {
beerAlreadyInBasket = true;
item.count++;
};
});
if (!beerAlreadyInBasket) {
cardItems.push({ ...beer, count: 1 })
}
localStorage.setItem('baketItems', JSON.stringify(cardItems));
console.log('cardItems: ', cardItems, cardItems.length);
return cardItems;
})
}
return (
<div className="App">
<div className='search'>
<input type='text' placeholder='search beer...' onChange={displayFilterBeers} />
</div>
<BeerList BeersList={filteredBeers} price={price} handleAddToBasket={handleAddToBasket} />
<Basket cardItems={cardItems}/>
</div>
);
}
export default App;
I saw an example that without React hooks that in basket component someone used const {cartItems} = this.props; but I don't know how to achieve something similar using hooks.
I think what you are facing is related to this issue.
So when dealing with array or list as state, react doesn't re-render if you don't set state value to a new instance. It assumes from the high-level comparison that the state hasn't been changed. So it bails out from the re-rendering.
from the issue I found this solution is better than the others -
const handleAddToBasket = (event, beer) => {
const nextState = [...cardItems, beer] // this will create a new array, thus will ensure a re-render
// do other stuffs
setCardItems(nextState);
};

react / redux debounce throttling

I'm trying to debounce a component in my webapp. Actually it is a filter for the maxPrice and if the user starts to print, the filter starts to work and all the results disappear until there is a reasonable number behind it.
What I tried so far:
import _ from 'lodash'
class MaxPrice extends Component {
onSet = ({ target: { value }}) => {
if (isNaN(Number(value))) return;
this.setState({ value }, () => {
this.props.updateMaxPrice(value.trim());
});
};
render() {
const updateMaxPrice = _.debounce(e => {
this.onSet(e);
}, 1000);
return (
<div>
<ControlLabel>Preis bis</ControlLabel><br />
<FormControl type="text" className={utilStyles.fullWidth} placeholder="egal"
onChange={updateMaxPrice} value={this.props.maxPrice}
/>
</div>
);
}
}
I'm getting the error
MaxPrice.js:11 Uncaught TypeError: Cannot read property 'value' of null
at MaxPrice._this.onSet (MaxPrice.js:11)
at MaxPrice.js:21
at invokeFunc (lodash.js:10350)
at trailingEdge (lodash.js:10397)
at timerExpired (lodash.js:10385)
In my old version I had onChange={this.onSet} and it worked.
Any idea what might be wrong?
As you mentioned in comments, it's required to use event.persist() to use event object in async way:
https://facebook.github.io/react/docs/events.html
If you want to access the event properties in an asynchronous way, you
should call event.persist() on the event, which will remove the
synthetic event from the pool and allow references to the event to be
retained by user code.
It means such code, for example:
onChange={e => {
e.persist();
updateMaxPrice(e);
}}
Here is my final solution. Thanks to lunochkin!
I had to introduce a second redux variable so that the user see's the values he is entering. The second variable is debounced so that the WepApp waits a bit to update.
class MaxPrice extends Component {
updateMaxPriceRedux = _.debounce((value) => { // this can also dispatch a redux action
this.props.updateMaxPrice(value);
}, 500);
onSet = ({ target: { value }}) => {
console.log(value);
if (isNaN(Number(value))) return;
this.props.updateInternalMaxPrice(value.trim());
this.updateMaxPriceRedux(value.trim());
};
render() {
return (
<div>
<ControlLabel>Preis bis</ControlLabel><br />
<FormControl type="text" className={utilStyles.fullWidth} placeholder="egal"
onChange={e => {
e.persist();
this.onSet(e);
}} value={this.props.internalMaxPrice}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
maxPrice: state.maxPrice,
internalMaxPrice: state.internalMaxPrice
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({updateMaxPrice:updateMaxPrice,
updateInternalMaxPrice:updateInternalMaxPrice}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(MaxPrice);

Resources