-- edit -- i created a codesandbox: https://codesandbox.io/s/xenodochial-hofstadter-d3jjo
I'm starting to scratch my head and would like some help on - I assume - a simple problem.
I have a basic App component that renders a controlled text input. Once you submit this input it creates a message on the back-end, which updates a redux store. Everything seems to be working fine on this part.
My issue is when I tried to add a useEffect hook to reset the input value after a new message was added. I noticed that the useEffect that relies on [messages] is called on every render instead of just when messages changes. The simple act of typing in the input - which causes a rerender for every character - makes the hook trigger, even though it is still the same value: an empty array.
Basically I've noticed it only works as intended if my useSelector returns the same instance of an array. So if I were to read only state.messages.allIds it would work since the array does not change. But simply adding a .map(x => x) makes it return a new instance every time.
I have added react-redux's shallowEqual to try and fix this, but to no avail, and I don't understand why it doesn't fix my problem.
const App = () => {
const [message, setMessage] = useState('');
const messages = useSelector(
(state: RootState) => (
state.messages.allIds.map((id: string) => state.messages.byId[id])
),
shallowEqual,
);
useEffect(() => {
console.log('useEffect');
}, [messages]);
// ...
return (
<form onSubmit={/* ... */}>
<input
onChange={event => setMessage(event.target.value)}
value={message}
/>
</form>
);
}
This maybe coming pretty late. But it might benefit others facing the same problem.
If you do not want the function to re-render on state change and only want to extract the value of a redux state variable, you may use useState
const state = useState();
const yourVariable = state.getState().reduxStateVariable;
Related
Basically I have a slider component and a number input component both connected to the same number state. I am wanting to send the very last number to the database using an URQL GraphQL hook after the user is done changing the state. I don't want to send the data every time the user modifies the state but only when they're done editing and that component moves off the screen. I'm also generating quite a few of these at once and only want to use the mutation if the user did in fact change the state at all.
I tried accomplishing this using the useEffect cleanup function doing something like this (i tried to simplify for ease of reading):
const [, executeMutation] = useMutation()
const [ownership, setOwnership] = useState(0);
const [needsRated, setNeedsRated] = useState(false);
useEffect(() => {
return () => {
if (needsRated) {
executeMutation({ownership: ownership})
}
}
}, [needsRated]);
and then my components look like this:
<SliderComponent value={ownership} onChange={(val) => {
setOwnership(val)
setNeedsRated(true)
}}
/>
<NumberInputComponent value={ownership} onChange={(val) => {
setOwnership(val)
setNeedsRated(true)
}}
/>
as it is right now, the cleanup function still uses the mutation every time the state gets changed. is there any way to go about this so i'm not sending 10 database mutations when I only need the final one?
Thanks in advance.
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
In the Reach Hooks FAQ section "How to read an often-changing value from useCallback?" there is an example:
function Form() {
const [text, updateText] = useState('');
const textRef = useRef();
useEffect(() => {
textRef.current = text; // Write it to the ref
});
const handleSubmit = useCallback(() => {
const currentText = textRef.current; // Read it from the ref
alert(currentText);
}, [textRef]); // Don't recreate handleSubmit like [text] would do
return (
<>
<input value={text} onChange={e => updateText(e.target.value)} />
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}
I was so confused:
Instead of useCallback updating the function each time the text changes, this code moves it to textRef in useEffect. But isn't that the same because the callback function doesn't change but you still need 1 step to update the value in useEffect?
Why is textRef in the dependency array? Isn't textRef a reference which doesn't change between renders? We always receive the same reference so wouldn't it be the same as inputting an empty array?
instead of useCallback update the function each time the text change, this code move it to textRef in useEffect but isn't that the same because the callback function doesn't change but you still need 1 step to update the value in useEffect?
With the useRef setup, each render re-runs useEffect but handleSubmit stays the same. In a normal setup, each render would re-run useCallback and handleSubmit would be a new reference to a new function. I can see why on the surface this seems like the same amount of work.
The benefits of the ref approach are what other re-renders happen based on a re-rendering of Form. Each time the value of text changes, the parent Form re-renders and the child input re-renders since it takes text as a prop. ExpensiveTree does not re-render because its prop handleSubmit maintains the same reference. Of course the name ExpensiveTree is designed to tell us that we want to minimize re-rendering of that component since it is computationally expensive.
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 trying to build an input component using React Hooks that hits a remote server to save an updated value on component unmount only.
The remote server call is expensive, so I do not want to hit the server every time the input updates.
When I use the cleanup hook in useEffect, I am required to include the input value in the effect dependency array, which makes the remote API call execute on each update of the input value. If I don't include the input value in the effect dependency array, the updated input value is never saved.
Here is a code sandbox that shows the problem and explains the expected outcome: https://codesandbox.io/s/competent-meadow-nzkyv
Is it possible to accomplish this using React hooks? I know it defies parts of the paradigm of hooks, but surely this is a common-enough use case that it should be possible.
You can use a ref to capture the changing value of your text, then you can reference it in another useEffect hook to save the text:
const [text, setText] = useState("");
const textRef = React.useRef(text);
React.useEffect( () => {
textRef.current = text;
}, [text])
React.useEffect( () => {
return () => doSomething(textRef.current)
}, [])
thedude's approach is right. Tweaked it a bit, for this particular usecase as input ref is always same :
function SavedInput() {
const inputEl = useRef(null);
React.useEffect(() => {
return () => {
save(inputEl.current.value);
};
}, []);
return (
<div>
<input ref={inputEl} />
</div>
);
}
By this way you'll avoid re-render as you are not setting any state.