useEffect not getting called on local storage dependency - react-hooks

I tried as following but on change of localstorage useEffect is not getting called.
I have also tried adding dependency localStorage.getItem('quantity') but doesn't work.
import React from 'react';
import Categories from '../molecules/Categories';
import './SideBar.scss';
import { useState, useEffect } from 'react';
const SideBar = () => {
let [quantity, setQuantity] = useState(JSON.parse(localStorage.getItem('quantity')));
useEffect(() => {
console.log("in useEffect");
const onStorage = () => {
setQuantity(JSON.parse(localStorage.getItem('quantity')));
};
window.addEventListener('storage', onStorage);
return () => {
window.removeEventListener('storage', onStorage);
};
}, []);
return (
<div className='sidebar'>
<Categories />
<div>count:{quantity}</div>
</div>
)
}
export default SideBar;

Having an empty dependency array (as in your code above) will ensure that a useEffect runs only once, but I'm not sure why you would need this useEffect to run whenever storage is updated. This useEffect basically just adds an event listener, which does not need to be renewed after every storage update.
It looks like onStorage is what you actually want to run whenever storage is updated, but that is handled by the event listener that the useEffect adds. If you add a console.log inside onStorage, you should see that onStorage is running after every storage update.
Note, too, though, that a storage event will fire only if localStorage is updated by a different window/page; see this MDN article. If the same window is updating localStorage, then to trigger your event handler you will need to dispatch a storage event manually whenever you update the quantity in localStorage:
window.dispatchEvent(new Event('storage'));

Related

Setting state with values inside an object

Im basically trying to set the state with the City value but when I set thee state and console log the state its always empty. Im even console logging along the way.
sample city object from first console.log:
{_id: '625a495ae4bea1099502f824',
City: 'Dakhla Oasis'
Country: 'Egypt'
}
import React from "react";
import { useState } from "react";
export default function Cities() {
const [singlecity, setSingleCity] = useState('');
function GetSingleCity() {
console.log(city)
setSingleCity(city.City);
console.log(singlecity);
}
setState function in react is asynchronous, so if you console.log right after the set state, you will not see the updated value immediately. However if yo try to render the state, you will see the rendered html will be updated. If you want to console.log the latest value of singleCity, you could either move the console.log outside GetSingleCity, so console.log will run on every re-render, or put console.log inside a useEffect:
useEffect(() => {console.log(singleCity)}, [singleCity])
useEffect will run the callback every time the state singleCity has changed, so you will see the updated value

React - useState not updating when used inside of useEffect

In my project, I want to save all console.log's in an array.
I created a simple example that represents what I am trying to do.
A simple button that when pressed - logs to the console.
An array for all logged messages
An useEffect hook that overrides the console.log function.
However, the array doesn't get bigger than 1 item.
The code:
import { useState, useEffect } from "react";
export default function App() {
const [myLogs, setMyLogs] = useState<any[]>([]);
useEffect(() => {
var log = console.log;
console.log("from useEffect");
console.log = function () {
var args = Array.prototype.slice.call(arguments);
log.apply(this, args);
setMyLogs({ ...myLogs, ...args });
};
}, []);
return (
<div>
<button onClick={() => console.log("clicked :)")}>Click to Log</button>
<p>{myLogs.toString()}</p>
</div>
);
}
Codesanbox link
I tried adding myLogs as a dependency to the useEffect hook thinking it might help but it just created an infinite loop. (This part I understand)
The issue is that of a stale enclosure of initial myLogs state and also that of mutating the state invariant from array to object type.
Use a functional state update to correctly update from any previous state and not the state value closed over in callback scope. Maintain the array state invariant.
useEffect(() => {
const log = console.log;
console.log("from useEffect");
console.log = function () {
const args = Array.prototype.slice.call(arguments);
log.apply(this, args);
setMyLogs(myLogs => [...myLogs, args]);
// or [...myLogs, ...args] if you want all the log args flattened
};
}, []);
you are setting log as the object it should be an array. and correct way of doing this is below
setMyLogs((prev) => [ ...prev, ...args ]);

Long action is locking renderer process

I have a function that I'm calling in a component template. It reacts on changes in a vue-select component.
<v-select
v-model="model"
:options="eventList"
:placeholder="currentEventTitle"
v-on:input="selectedEvent"
taggable
label="name"
></v-select>
...
</template>
<script>
...
methods: {
...mapActions({
selectedEvent: "selectedEvent"
})
}
This selectedEvent action that is being dispatched contains many HTTP calls, commits to the store, and calls to other actions. The issue is that once this has been called, the UI completely locks and doesn't update until about halfway through the function.
How should I call an action that will commit (mutate), and dispatch other actions, and not lock the UI?
Update
The problem has disappeared in our production build and only exists when we have development mode enabled.
If vuex observer/ reactivity is the reason for the ui block one can simply freeze big objects to prevent reactivity:
actions = {
// ...,
assignBigData(context, data){
//...
// mutate data
const clone = {...data}
context.commit('commiter', Object.freeze(clone))
},
selectedEvent: async function(context, ...args){
const response = await request()
context.dispatch('assignBigData', Object.freeze(response))
}
}

react-redux together with components status

in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)

react-redux can update child smart components without updating parents?

Imagine the following React structure:
SmartComponentA -> DumbComponentB -> SmartComponentC
Also imagine that SmartComponentA and SmartComponentC each get connected to different slices of the state in their mapStateToProps functions.
Lastly, imagine that we put a console.log in the render method of each of these components.
When I actually try this, on the first render, I see that all components log as expected. But then if I change the data for SmartComponentC, I only see a single log message (C's log message), and I don't see SmartComponentA or DumbComponentB logging anything. How is that possible? How is react-redux getting React to update a child without updating its parents?
I would have assumed that the overriding of shouldComponentUpdate inside of the connect method would mean SmartComponentA would not get re-rendered (since its slice of the state didn't change), and therefore would cause a short-circuiting that would prevent SmartComponentC from getting re-rendered. While connect's implementation is not the same as the pure render mixin, both work by changing shouldComponentUpdate, but the pure render docs clearly state that React will "bail out" (as they put it) if a parent doesn't need to re-render:
for C2's subtree and C7, it didn't even have to compute the virtual DOM as we bailed out on shouldComponentUpdate.
source
If my question still isn't clear, here is sort of pseudo-code for the setup, and I'm asking why I can keep typing in C's input and it only log's C's messages to the console and not A's and B's (why is it not short-circuiting)?
//////////////////////////////////////////////
const SmartComponentA = (props) => {
console.log('rendering SmartComponentA');
return <DumbComponentB bData={props.bData} />;
};
const mapStateToProps = (state) => { bData: state.bData };
export default connect(mapStateToProps)(SmartComponentA);
//////////////////////////////////////////////
const DumbComponentB = (props) => {
console.log('rendering DumbComponentB');
return (
<div>
{props.bData}
<SmartComponentC />
</div>
);
}
export default DumbComponentB;
//////////////////////////////////////////////
const SmartComponentC = (props) => {
console.log('rendering SmartComponentC');
return (
<div>
<input value={props.cValue} onChange={props.changeCValue} />
</div>
);
}
const mapStateToProps = (state) => { cValue: state.cValue };
export default connect(mapStateToProps, { changeCValue })(SmartComponentC);
//////////////////////////////////////////////
On the first render I see all log messages, then if I keep typing in the input, I only see C's log message each time I press a key.
Prop changes trigger the React component lifecycle which will typically trigger the lifecycle of each child component unless -- as you observe, the process can be short-circuited by shouldComponentUpdate.
But prop changes aren't they only thing that triggers the component lifecycle -- state changes do too. And that's how connect function works. The Connect component subscribes to the store and on any store change checks to see if it will update the smart component's props (based on mapStateToProps). If so it will set it's own state triggering the lifecycle functions for the Connect component and it's child.

Resources