I have a React component which has 2000 elements and based on some filter conditions I update my state, which internally causes re-rendering. Everything seems to be working fine. But when I togglefilter from 2000 elements to say 1000 elements and back&forth, the rendering takes a lot of time and sometimes the browser freezes. I did chrome timeline profiling, the major time consuming piece is rendering. Any help would be appreciated.
As suggested by #enjoylife is a great step but what if you have many components structures in your view, that would be very difficult to debug even memoising the component won't be able to subside the continuous or loop rendering.
I learnt this after I ran into strange freezing and weird error that wouldn't stop any time a user logged in on the homepage. Imagine of all screens. Sometimes, you would hardly notice your component re-rending.
Detect your screen/page (loop) re-rendering with console log
const Home = () => {
conso.log('home re-rending')
// some hooks
return <BigComponent />
}
As written above. The logs must not show more than a limited time deemed after a component has mounted. In my case, it's once. But if it is too much(logs) and would certainly freeze your pc. Therefore, follow the below steps carefully and retrace your steps.
Tips and prerequisite before trying out this proposed solution. Please make sure you have style guide setup e.g. Eslint, it's great. In my case, I reproduced the source code with cra, then sorted out the first and last listed problem which I encountered.
Be careful with the use of React hooks such as useEffect especially. Avoid causing a side effect in a component.
In my case, I created a reusable useUpdateEffect hook and what I intend it to solve as par the name was to detect an update of React props or window props, but it backfires, I won't share the code.
Also, do extra check if you passed correct and expected dependencies, on this Eslint deserve an accolade.
Avoid random keys in React list. Use unique and constant keys in a component list as react depend on it to identify each item. According to react library
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. You may use the item index as a key as a last resort:
Avoid variable name conflict in your reducer and React component. Please consider the use of style guides as your friend to avoid this fall.
I made the stupid mistake to create a Foo class and use in its render function, which also leads to the freezing scene. Write here for anyone who could meet this problem again.follow this thread.
Avoid infinite loops, Imagine rendering a lot of data at a go. this happen
just in case you share my fate, I urge you to check your loops and make sure you do not have a += instead of -= (or vice versa). Those infinite loops can be quite a big pain in the neck.
Keep your reducer as a reducer, Avoid Action creator, an API call in your reducer or using another reducer in your reducer so, for instance, reducerA in reducerB. When you call to update reducerA in reducerB, the update in reducerA would trigger an update in reducerB whereby cause page/screen to re-render multiple times. for example
// this react reducer in my case
// reducer js file - reducerB
const useBusinesses = () => {
// reducerB as discussed above - the loading context
const { loading } = useLoadingContext(); // the culprit
const [data, setData] = useState(initialState); // initial state,
const [state, dispatch] = useReducer(reducer, data);
useEffect(() => setData(state), [state, setData]);
const { businesses, errorMessage } = state;
const setBusinesses = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_SUCCESS`, data: payload });
const setBusinessesError = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_ERROR`, data: payload });
const fetchBusinesses = async (lglt, type = 'food', limit = 12) => {
try {
// update reducerB: triggers multiple update in reducerA while requesting is pending
loading(FETCH_BUSINESSES, true);
const request = await API.businesses.getWithquery(
`long=${lglt[0]}&latt=${lglt[1]}&limit=${limit}&type=${type}`
);
loading(FETCH_BUSINESSES, false);
setBusinesses(request.data);
} catch (err) {
loading(FETCH_BUSINESSES, false);
// if (!err.response) dispatch(alertMessage(FETCH_BUKKAS, true, 'Please check your network'));
setBusinessesError(err.response.data);
}
});
return { businesses, errorMessage, fetchBusinesses };
};
export const [BusinessesProvider, useBusinessesContext] = constate(useBusinesses);
//home js file
Home = () => {
const { fetchBusinesses } = useBusinessContext();
conso.log('home re-rending')
// some hooks
useEffect(() => {
console.log('am i in trouble, yes!, how many troubles')
fetchBusinesses(coordinates)
}, [fetchBusinesses, coordinates])
return <BigComponent />
}
A quick fix is to implement shouldComponentUpdate See the docs, for whichever child component is being rendered ~2000 times.
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
Another quick check is to ask yourself if your following the convention of using small, stateless children, passing only props. If not, it might be time to refactor.
Related
I've got a problem while using react hooks.
const { formData, onChange, errors } = useContext(ResearchTaskContext);
const [annotations, setAnnotations] = useState('');
useEffect(() => {
onChange('formData.expenses.annotations', annotations);
}, [annotations]);
As you can see, when I change local state, need to change context state too. This is creating mode. Now I need fetch and prefilled the data for updating mode. in order to do it, I've added new useEffect hook.
useEffect(() => {
setAnnotations(formData.expenses.annotations);
}, [formData.expenses.annotations]);
But this code occur too many renders.
All context states are empty initially, so if I remove dependencies, fetch data can't affect.
You're essentially creating an infinite loop, I'd recommend to do away with annotations state as a whole, and use the formData.expenses.annotations as your source of truth.
Is there any special condition, that causes a react component to reset completely (not just re-render)?
What do I mean by reset?
Reverting back ALL STATES to their default values (that given in useState()) and re-running all my useEffect() callbacks (even with empty dependency-list ([])) … LIKE ANOTHER OR A NEW COMPONENT!
This wonderful event happens for one of my components during its lifetime (after a lot of correct re-rendering for this component and other components). I don't know why! And I thought this is an impossible event in React:
export default function Project (props) {
const [temp, setTemp] = useState(false)
const tempRef = useRef(false)
console.debug({ temp }, tempRef)
useEffect(() => {
console.debug('INITIALIZATION:', { temp }, tempRef)
setTemp(true)
tempRef.current = true
}, [])
// ...
}
I'm suspicious of react-router-dom#6.0.0-beta.8 that I used in my project. But I can't change it, because of the scale of the project and a lot of usage of it.
UPDATE: All parents of this component are working as expected. Even after this event (for this component).
NB: I've asked this on wordpress.stackexchange, but it's not getting any response there, so trying here.
I'm not sure if this is WordPress specific, WordPress's overloaded React specific, or just React, but I'm creating a new block plugin for WordPress, and if I use useState in its edit function, the page is re-rendered, even if I never call the setter function.
import { useState } from '#wordpress/element';
export default function MyEdit( props ) {
const {
attributes: {
anAttribute
},
setAttributes,
} = props;
const [ isValidating, setIsValidating ] = useState( false );
const post_id = wp.data.select("core/editor").getCurrentPostId();
console.log('Post ID is ', post_id);
const MyPlaceholder = () => {
return(
<div>this is a test</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
If I comment out const [ isValidating, setIsValidating ] = useState( false ); then that console.log happens once. If I leave it in, it happens twice; even if I never check the value of isValidating, never mind calling setIsValidating. I don't want to over-optimize things, but, equally, if I use this block n times on a page, the page is getting rendered 2n times. It's only on the admin side of things, because it's in the edit, so maybe not a big deal, but ... it doesn't seem right. Is this expected behavior for useState? Am I doing something wrong? Do I have to worry about it (from a speed perspective, from a potential race conditions as everything is re-rendered multiple times)?
In your example code, the console.log statement is being immediately evaluated each time and triggering the redraw/re-rendering of your block. Once console.log is removed, only the state changes will trigger re-rendering.
As the Gutenberg Editor is based on Redux, if the state changes, any components that rely on that state are re-rendered. When a block is selected in the Editor, the selected block is rendered synchronously while all other blocks in the Editor are rendered asynchronously. The WordPress Gutenberg developers are aware of re-rendering being a performance concern and have taken steps to reduce re-rendering.
When requesting data from wp.data, useEffect() should be used to safely await asynchronous data:
import { useState, useEffect } from '#wordpress/element';
export default function MyEdit(props) {
...
const [curPostId, setCurPostId] = useState(false);
useEffect(() => {
async function getMyPostId() {
const post_id = await wp.data.select("core/editor").getCurrentPostId();
setCurPostId(post_id);
}
getMyPostId();
}, []); // Run once
const MyPlaceholder = () => {
return (
<div>Current Post Id: {curPostId}</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
As mentioned in the question, useState() is used in core blocks for setting and updating state. The state hook was introducted in React 16.8, its a fairly recent change and you may come across older Gutenberg code example that set state via the class constructor and don't use hooks.
Yes, you have to worry about always put an array of dependencies, so that, it won't re-render, As per your query, let's say are planning to edit a field here is the sample code
const [edit, setEdit]= useState(props);
useEffect(() => {
// logic here
},[edit])
that [edit] will check if there is any changes , and according to that it will update the DOM, if you don't put any [](array of dependencies) it will always go an infinite loop,
I guess this is expected behavior. If I add a similar console.log to native core blocks that use useState, I get the same effect. It seems that WordPress operates with use strict, and according to this answer, React double-invokes a number of things when in strict mode.
I'm working on an react native app.
This app use a database, the main component use 2 differents hook.
The first hook retrieves the results of a SQL query and store them in a variable.
The second hook creates a list from the first variable
Like this:
const [people, setPeople ] = useState([]);
useEffect (() => {
db.getAllPeople().then(row => setPeople(row))
},[])
const [listData, setListData] = useState([]);
useEffect(()=> {
setListData(
Array(people.length)
.fill('')
.map((_, i) => ({ key: `${i}`, name: `${people[i].name}`}))
)
}, [people]);
After that, my main component displays a SwipeList from the results.
Here is the problem. I am using another component to add an element to my database. When I return to my main component I would like this new element to be displayed. But the problem is that the 2 hooks are not called on the component change and the list therefore remains unchanged.
I've tried to use the useFocusEffect but it doesn't work in my case.
Any suggestions ?
I think the useState hook manages the state of the component itself, unless you are passing this state among your parent and child or using callbacks to set the state on the component that you want to render, you could use a single source of truth to handle the changes in data, react itself will notice this changes and therefore, render the changed screens, considering that you have asynchronous operations when querying the database, a combination of redux and redux saga may help you.
https://github.com/redux-saga/redux-saga
There're one issues with your current code, or potential issues
Your second useEffect might get called when people becomes an empty list, this will reset your list data. The cure is to put a if statement inside, ex.
useEffect(()=> {
if (!people) return;
setListData(...)
}, [people]);
To be honest, if these two lists are connected, you shouldn't use two hook. The best way is to define listData
const listData = (a function that takes people as input), ex.
const listData = people.map(v => v)
Of course, there might be a reason why you'd like to introduce more hook in complex situation, ex. useRef, useMemo.
I want to understand what is more general or correct way of error handling with React-Redux.
Suppose, I have phone number sign up component.
That Component throws an error say if the input phone number is invalid
What would be a best way to handle that error?
Idea 1:
Create a component, which takes an error and dispatches an action whenever an error is passed to it
idea 2:
Since the error is related to that component, pass that error to a component (which isn't connected to redux i.e the error handler component won't dispatch the action)
Question: Can someone guide me on proper-way of error handling in React-Redux for large-scale app?
I would say that neither of your initial ideas capture the whole picture. Idea 1 is just a callback. If you want to use a callback: useCallback. Idea 2 will work and is preferable if you don't need to use redux. Sometimes you're better off using redux. Maybe you're setting form validity by checking none of the input fields have errors or something similar. Since we're on the topic of redux, let's assume that's the case.
Usually the best approach to error handling with redux is to have an error field in state that is then passed to an error component.
const ExampleErrorComponent= () => {
const error = useSelector(selectError);
if (!error) return null;
return <div className="error-message">{error}</div>;
}
The error component doesn't have to just display an error, it could also do side effects with useEffect.
How the error is set/unset depends on your application. Let's use your phone number example.
1. If the validity check is a pure function, it can be done in the reducer.
You would then set or unset the error field in response to phone number change actions. In a reducer built with a switch statement it could look like this.
case 'PHONE_NUMBER_CHANGE':
return {
...state,
phoneNumber: action.phoneNumber,
error: isValidPhoneNumber(action.phoneNumber) ? undefined : 'Invalid phone number',
};
2. If errors are reported by the backend, dispatch error actions.
Let's say you're sending the phone number to a backend that does validation before it does something with the number. You can't know if the data is valid on the client side. You'll just have to take the server's word for it.
const handleSubmit = useCallback(
() => sendPhoneNumber(phoneNumber)
.then(response => dispatch({
type: 'PHONE_NUMBER_SUBMISSION_SUCCESS',
response,
}))
.catch(error => dispatch({
type: 'PHONE_NUMBER_SUBMISSION_FAILURE',
error,
})),
[dispatch, phoneNumber],
);
The reducer should then come up with an appropriate message for the error and set it.
Don't forget to unset the error. You can unset the error on a change action or when making another request depending on the application.
The two approaches I outlined are not mutually exclusive. You can use the first to display locally detectable errors and also use the second to display server side or network errors.
I would use a formik with yup validation.
then, for server-side error i would use something like this:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Spinner } from "#blueprintjs/core";
export default ({ action, selector, component, errorComponent }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(action());
}, [dispatch, action]);
const DispatchFetch = () => {
const { data, isRequesting, error } = useSelector(selector());
if (!isRequesting && data) {
const Comp = component;
return <Comp data={data}></Comp>;
} else if (error) {
if (errorComponent) {
const ErrorComp = errorComponent;
return <ErrorComp error={error}></ErrorComp>;
}
return <div>{error}</div>;
}
return <Spinner></Spinner>;
};
return <DispatchFetch></DispatchFetch>;
};
It depends on what kind of error handling are you talking about. If it's only form validation handling then I don't think you need Redux for that - please read this article. If your error is only going to be "consumed" within that component, why send it to redux? You can easily use your local state for that.
On the other hand, if you want to show some kind of error notification to user indicating whether any HTTP call on site failed, you could benefit with redux by dispatching error from all parts of your application (or even generically your middleware)
dispatch({ type: 'SET_ERROR_MESSAGE', error: yourErrorOrMessage });
// simple error message reducer
function errorMessage(state = null, action) {
const { type, error } = action;
switch (type) {
case 'RESET_ERROR_MESSAGE':
return null;
case 'SET_ERROR_MESSAGE':
return error;
}
return state
}
You need to define how is your state going to be organized and whether you need to put some state in redux or just keep it in local state of your component. You could put everything in redux, but personally I'd say it's an overkill - why would you put state X in component Y if only component Y cares about that state? If you structure your code correctly you shouldn't have problem with moving that state from local to redux later on, if for some reason other parts of your app start to depend on that state.
I think about it like this, what should be state? And what should be derived from state?
State should be stored in redux, and derivations should be calculated.
A phone number is state, which field has focus is state, but whether or not it's valid, that can be derived from the values in state.
I would use Reselect to cache derivations and return the same results when the relevant state hasn't been modified.
export const showInvalidPhoneNumberMessage = createSelector(
getPhoneValue,
getFocusedField,
(val, focus) => focus !== 'phone' && val.length < 10 // add other validations.
)
You can then use the selector in mapStateToProps in all components that may care about this value, and you can use it in async actions as well. If the focus hasn't changed, or the value of the field hasn't changed, then no recalculation will occur, it'll return the previous value instead.
I'm adding the selected state check just to show how multiple pieces of state can come together to result in one derivation.
I personally try to approach things by keeping my state as small as possible. For instance, let's say you wanted to create your own calendar. Will you store every single day in state, or do you just need to know a couple things like the current year and month currently being viewed. With just these 2 pieces of state you can calculate the days to display on a calendar, and don't need to recalculate until one of them changes, and this recalculation will happen virtually automatically, no need to think about all the ways theat they could change.