The react component resets accidentally - reactjs

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

Related

I've got a problem while using react hooks

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.

How can I prevent unnecessary re-renders on the child components in React with Hooks & Context?

I'm working on some code, and this code is huge. We have so many child components(nearly 300) and each one of them are using & manipulating values from the parent component's state via React Context.
Note: I didn't wrote this code from scratch. Which also means the design I'm about to show is not what I would come up with.
The problem: Since every component is using the same state, there are so many unnecessary re-renders happening. Every small state change is causing every component to re-render. And it makes the web app laggy. Literally, there is lag when you enter some input in a field.
I think, when state change happens the functions get rebuilt and that's why every child gets updated, because the value provided by context is changed after state change happened.
Additionally, I tried to use useReducer instead of useState that didn't went well. Besides that, I tried to use React.memo on every child component but the compare function didn't get triggered no matter what I tried. compare function only got triggered on the parent component which has the state as props. At this point, I'm not even really sure what is the problem :D
To give more specific details on the design, here is how we define and pass the callbacks to child components.
Definitions:
const [formItemState, setFormState] = React.useState<FormItemState>({} as FormItemState);
const getAppState = useCallback(() => ({ state: props.state as AppState }), [props.state]);
const getAppAction = useCallback(() => ({ action: props.action as AppAction }), [props.action]);
const getFormItemError = useCallback((key: string) => formItemErrorState[key], [
formItemErrorState,
]);
const getFormItem = useCallback((key: string) => formItemState[key], [formItemState]);
const updateFormItem = useCallback(
(name: string, formItemData: FormItemData): void => {
const previousState = getFormItem(name);
if (!isEqual(previousState, formItemData)) {
formItemState[name] = formItemData;
setFormState((state) => ({
...state,
...formItemState,
}));
}
},
[formItemState, setFormState]
);
Passing them to Context.Provider:
return (
<FormContext.Provider
value={{
getAppAction,
getAppState,
getFormItem,
updateFormItem
}}
>
<SomeComponent>
{props.children} // This children contains more than 250 nested components, and each one of them are using these provided functions to interact with the state.
</SomeComponent>
</FormContext.Provider>
);
Last note: Please ask me if more info is needed. Thanks!
Why dont you just rewrite your state management to redux and pull only the necessary state to be used on each component. React.memo only picks up changes from props

Do I have to worry about useState causing a re-render?

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.

Component unmounts when parent state changes

I'm using React 16.8.2, and I'm having a problem with children of my component unmounting whenever state is changed in the app component.
Here's the scenario:
I have App.jsx (a functional component) with a number of state variables (useState)
The setters for some of these state variables are passed down the tree through a Context provider (useContext in the descendent)
I have a menu component (descendent of app), that invokes these setters to (for example) show a modal dialog
I have a modal dialog component (child of App), that uses the state variable as a property to determine whether it is open or not -- standard React stuff I think.
My problem: when any state variables in App are changed (through hooks of course), the children of App are unmounted and remounted- even if they have no connection to the state being changed. They aren't just re-rendered - the children are unmounted and their state is re-initialized. So the fields are cleared on my dialog, when they shouldn't be, for example.
This is already a fairly complex application, so I've spent a lot of time today isolating the problem. I then set up a simple create-react-app to try to replicate this behavior there - but this test app behaves like it should. Changing parent state, whether through a prop callback, or through a context-provided callback from the child - re-renders but does not unmount/remount and child state remains intact.
But in my real app, the components re-mount and child state gets re-initialized.
I've simplified it down to the barest that I can - I'm setting a fake state variable "foo" with "setFoo" through the Context from the child. Even though foo is not used by any component, changing the value of foo causes the children of App to unmount/remount.
In App.jsx:
const App = props => {
const [foo, setFoo] = useState(false);
// ...
const appControl = {
toggleFoo: () => setFoo(!foo);
};
// ...
return (
<AppContext.Provider value={appControl}>
... a bunch of stuff not using foo anywhere
... including, deep down:
<Menu />
</AppContext.Provider>
);
};
In Menu.jsx:
const Menu = props => {
const appControl = useContext(AppContext);
// ...
return (
... super simplified for test
<div onClick={appControl.toggleFoo}>
Toggle Foo
</div>
);
};
If I understand state properly, I do believe that changing state should result in children being re-rendered, but not re-mounted. This is what I'm seeing in my simple create-react-app test, but not in my real app.
I do see that I'm not on the latest React - perhaps upgrading will fix this?
Thanks for any insight on what I may be doing wrong, or misunderstanding here.
Solved. This is an interesting one. Here's what happened.
In my App component, I had a fairly deep tree of HOC's. Due to some dubious decisions on my part, I ended up breaking App into two components. App and AppCore. I had a reason for it, and it seemed to make sense at 3am. But to be both quick and dirty, I stuck AppCore as a const, inside my App function. I remember thinking to myself "I wonder what problems this will cause?" Now I know. Perhaps a React expert can fully explain this one to me though, as I don't see the difference between JSX assigned to a constant, and JSX returned directly. But there clearly is, and this is simple to reproduce.
To reproduce, create-react-app a test app:
create-react-app test
cd test
Then replace the contents of App.js with:
import React, { useState, useEffect } from "react";
const Menu = props => <div onClick={props.click}>Toggle Foo</div>;
const Test = props => {
useEffect(() => {
console.log("mounted");
return () => console.log("unmounted");
}, []);
return null;
};
const App = props => {
const [foo, setFoo] = useState(false);
// this is the root of the problem
// move this line outside of the function body
// and it mounts/unmounts correctly
const AppCore = props => <Test />;
return (
<>
<Menu click={() => setFoo(!foo)} />
<AppCore />
</>
);
};
export default App;
Then npm start, and when you click on "Toggle Foo" you'll see that the Test component is unmounted/remounted.
The solution here, is to simply move AppCore out of the function body. In my real app, this means I have some refactoring to do.
I wonder if this would be considered a React issue?

React JS Freezes Browser

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.

Resources