React - useState not updating when used inside of useEffect - react-hooks

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 ]);

Related

useEffect not getting called on local storage dependency

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'));

Jest store state and RTL rendered component's onClick event handler different states

I'm using the following code to test a state-dependent react component using jest and rtl:
test("render author, date and image correctly after going next post", async () => {
const store = configureStore({
reducer: {
data: dataReducer
}
});
const Wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
render(<Post />, { wrapper: Wrapper });
const getSpy = jest.spyOn(axios, 'get').mockReturnValue(mockPostJSON);
await store.dispatch(FETCH_POSTS());
expect(getSpy).toBeCalledWith('https://www.reddit.com/r/EarthPorn/.json');
const beforeClick = await screen.findByTestId('authorAndDate');
expect(beforeClick.innerHTML.toString()).toBe(mockPostsList[0].author + ' - ' + mockPostsList[0].date);
fireEvent.click(screen.getByText('Next post'));
const afterClick = await screen.findByTestId('authorAndDate');
expect(afterClick.innerHTML.toString()).toBe(mockPostsList[1].author + ' - ' + mockPostsList[1].date);
})
The problem I'm having is that before the click everything in the store is set up correctly and the authorAndDate element displays the first item in the array of posts. But after the click is fired the store goes back to the initial state it had before loading the mock data. I checked within the component's event handler and right before it does anything the state has been reset. The code is as follows:
const handleNextClick = () => {
store.dispatch(GO_NEXT_POST());
store.dispatch(FETCH_COMMENTS());
}
I've been an hour over the code trying to find something that would reset the state and found nothing. I'm guessing it's some kind of interaction between jest and rtl but I can't figure out why the store in the test has one state and the store in the component's event handler has another :S
Well, figured it out. Can't use store.dispatch directly as it's accessing a stale state. Needed to use the useDispatch hook. Hope this serves anybody who faces the same problem in the future.

How to use mapStateToProp in react redux

I am creating search function, as a request I have to use mapStateToProps to get the data, how should I do in this file?
This is my code for that
import React, { useState, useEffect } from "react";
import useDebounce from "../../custom-hook/index";
import "../Search/index.css";
function Search() {
// State and setter for search term
const [searchTerm, setSearchTerm] = useState("");
// State and setter for search results
const [results, setResults] = useState([]);
// State for search status (whether there is a pending API request)
const [isSearching, setIsSearching] = useState(false);
// Run every 5ms
const debouncedSearchTerm = useDebounce(searchTerm, 500);
// Here's where the API call happens
// We use useEffect since this is an asynchronous action
useEffect(
() => {
// Make sure we have a value (user has entered something in input)
if (debouncedSearchTerm) {
// Set isSearching state
setIsSearching(true);
// Fire off our API call
searchCharacters(debouncedSearchTerm).then(results => {
// Set back to false since request finished
setIsSearching(false);
// Set results state
setResults(results);
});
} else {
setResults([]);
}
},
// This is the useEffect input array
[debouncedSearchTerm]
);
// Pretty standard UI with search input and results
return (
<div>
<input
placeholder="Search..."
className="search"
onChange={e => setSearchTerm(e.target.value)}
/>
{isSearching && <div>Searching ...</div>}
{results.map(result => (
<div key={result.id}>
<h4>{result.title}</h4>
<img
src={`${result.thumbnail.path}/portrait_incredible.${
result.thumbnail.extension}`}
alt="something"
/>
</div>
))}
</div>
);
}
// API search function
function searchCharacters(search) {
console.log(search);
}
export default Search;
As my code above, you can see that in return I have isSearching fucntion, it set by useState, but now I don't do like that, I have to set mapSateToProp to get prop from store
Please help me for this thank you so much
This piece of code is taken from react-redux documentation. You should import connect function form this library and then wrap your component.
function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}
export default connect(mapStateToProps)(TodoList)
Updated:
Since you are using hooks useSelector might work better for you.

Component only rerenders if ternary expression is directly inside render() method

I'm building an app with React, Redux and TypeScript.
In the top navbar I have a "Log in" link which when clicked dispatches an action. For now, all this action does is set a boolean called auth to true.
I have a lot of connected components which listen to that auth property of the redux store and decide which sub-components to render, based on ternary expressions that evaluate this.props.auth.
I was surprised to see that when I clicked "Log in" some components would rerender as expected while others would have their state successfully changed but would only alter their display if I refreshed the page or routed away and came back. After some hours of hair-pulling I believe I finally isolated the difference between the two kinds of components described above: if the ternary expression that evaluates this.props.auth is directly inside the render() method, the component behaves as expected, however, if the ternary expression is inside a .map() function which is then called by the render() method, then this weird behavior happens where I have to refresh in order for the correct rendering to match the prop values. What's going on? Does this lose it's value, is this a sync/async problem?
class LatestArticles extends Component<LatestArticlesProps> {
public latestArticlesList: JSX.Element[] = Articles.map((a: IArticle) => {
return (
<React.Fragment key={a.id}>
// some TSX
{this.props.auth === true ? <UserImgOverlay /> : <UnlockButton />}
// some more TSX
</React.Fragment>
)
});
public render(): JSX.Element {
return (
<React.Fragment>
// some TSX
{this.latestArticles}
// some more TSX
</React.Fragment>
)
}
Do let me know if you need any more context but I would like to ask for help understanding what's going on. Why does the prop change only trigger a rerender if the ternary expression is directly inside the render() method and is there any way to go around this while still mapping the data? Thank you for your attention.
EDIT
Here's my mapStateToProps:
// Components/Navbar/index.tsx
const mapStateToProps = ({ articles, auth }: IApplicationState) => {
return {
articlesPerPage: articles.articlesPerPage,
articlesPerPageStep: articles.articlesPerPageStep,
auth: auth.auth
}
}
I actually have another example of this kind of behavior happening with an onClick method:
class LatestArticles extends Component<LatestArticlesProps> {
public latestArticlesList: JSX.Element[] = Articles.map((a: IArticle) => {
return (
<React.Fragment key={a.id}>
// some TSX
<StarsRating rating={3} onClick={this.handleRatingClick} />
// some more TSX
</React.Fragment>
)
});
constructor(props: LatestArticlesProps & IOwnProps) {
super(props)
this.handleRatingClick = this.handleRatingClick.bind(this);
}
public render(): JSX.Element {
return (
<React.Fragment>
// some TSX
{this.latestArticles}
// some more TSX
</React.Fragment>
)
}
public handleRatingClick = () => {
alert('Clicked!')
}
}
^ When I click on the star nothing happens, but if I extract <StarsRating rating={3} onClick={this.handleRatingClick} /> from the .map function and put it directly inside the render() method, I get the alert saying 'Clicked!'... I suspect this is losing its value but I'm not sure how to test that.
I think the problem is with the latestArticlesList variable. It is initialized too early. It should be a function:
public latestArticlesList: JSX.Element[] = () => Articles.map((a: IArticle) => {
I assume Articles should be this.props.articlesPerPage.
And finally when you use it, call it:
<React.Fragment>
// some TSX
{this.latestArticles()}
// some more TSX
</React.Fragment>

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 :)

Resources