I am pretty much completely new to React. I've read the documentation and watched some YT video's. Now I am trying 'convert' some of this React class component that I found online to a functional component. This is how far I've come: My functional component(codesandbox).
It is a component to automatically wrap SVG text using tspan elements based on a user input field.
I am specifically having a hard time with the useEffect hook. As I understand it, it should function as the equivelant of componentWillMount and componentDidUpdate. Now I've got everything pretty much setup, but it does not yet update the Text component whenever new text is added in the textarea. I believe this is because I have to do something in the useEffect hook in the Text component:
useEffect(() => {
//something so that lines get updated if the user input changes?
}, [lines]);
The things I've tried resulted in an infinite loop. See: Can I use set state inside a useEffect hook.
Could someone point me in the right direction? Feel free to fork the codesandbox :)
Firstly, useEffect is not like componentWillMount, it's componentDidMount - effects fire after a render.
Secondly, in your specific example, what you want is to fire an effect when dummyText updates so it can recalculate lines.
In order to do that, your effect looks like this:
useEffect(() => {
//something so that lines get updated if the user input changes?
}, [dummyText]);
Thirdly, and most importantly, you shouldn't do that either because lines is not actually state of your Text component - it's just a computed value based on the dummyText prop. state is something your component owns, something it has created. Your Text component didn't create lines, it just calculated them based on the dummyText prop.
In your Text component your useEffect function doesn't track the dummyText prop. Try to add it into dependency array, like this (Text.jsx, line 60):
useEffect(() => {
const { wordsWithComputedWidth, spaceWidth } = calculateWordWidths();
const lines2 = calculateLines(wordsWithComputedWidth, spaceWidth, 400);
setLines(lines2);
}, [dummyText]);
Related
Consider the following example (wrote it from memory, so could have some issues):
export function App(props) {
const [howManyTimesUserHasDraggedMap, setHowManyTimesUserHasDraggedMap] = useState(0);
const [mapCenter, setMapCenter] = useState<LatLng>(new LatLng(0, 0));
const [shouldLog, setShouldLog] = useState(true);
const handleCenterChange = (newCenter: LatLng) : void => {
setMapCenter(newCenter);
}
useEffect(() =>
{
setHowManyTimesUserHasDraggedMap((prev) => prev + 1);
if(shouldLog)
console.log('Updated map drag count');
} [mapCenter]);
return (
<MapComponent onCenterChange={handleCenterChange} />
);
}
The point here is that we have, for example, some component called Map which shows Google Maps in a modal and user can interact with the map by dragging and zooming it.
Lets say that I want to keep track of how many time the user has dragged the map, which is basically the same as changing the center of it. The Map component takes onCenterChange prop and I give it a function which updates the mapCenter state with the newCenter whenever the center changes.
Now this example is a bit dumb (I could do all this in the handleCenterChange, but that's beside the point), but for whatever reason I want to use useEffect here to increment the number of drags whenever the mapCenter state changes, so I put mapCenter to the dependency array because that seems like the logical thing to do. I do also have an if-statement there which checks if shouldLog is true and console.logs something irrelevant to the console if so.
Now the issue here is that the dependencies for that useEffect are not exhaustive since shouldLog is missing from the dependency array and I'm getting warnings left and right. I then go read some documentation and there are warnings and alerts everywhere to ALWAYS INCLUDE ALL THE DEPENDENCIES.
But that doesn't really make sense here now does it? Why should I trigger that useEffect if my shouldLog-state changes - that doesn't mean that the mapCenter has changed. shouldLog's value is only relevant if the center changes.
My current understanding is that I can use useEffect to basically subscribe into certain events, such as when the component mounts, when the component unmounts, when the component re-renders or when something in the dependency array changes. So in this case I am subscribing to an event that fires when mapCenter changes and that is when it should ever fire. When the event fires though, it just happens to check what it should do regarding to logging, but the change in logging isn't a reason to fire the event.
So is my understanding completely wrong here or what is going on?
I've never used hooks in React and I'm trying to use useEffect() but I don't seem to get the basics of its correct structure and use.
I was able to achieve the results with plain JavaScript but with useState the state remains untouched.
Then I found useEffect after searching for a while, and this is what I could get it to look like-
// Background Parallax Effect
let [translateY,setTranslateY] = useState(0);
useEffect(()=> {
const getScrollPos = ()=> {
setTranslateY(window.scrollY*(-.2));
requestAnimationFrame(getScrollPos);
}
document.addEventListener("DOMContentLoaded",getScrollPos)
},[translateY]);
I highly suspect that its structure isn't as it is supposed to be.
So I want to know the fixes and how it exactly works to help understand the structure better.
The issue with your first code is that you add translateY as a dependency to useEffect. . You should remove translateY as a dependency and also remove the event listener when the component unmounts. Also you have a requestAnimationCallback within the getScrollPos function which is triggered unconditionally causing infinite loop
useEffect(()=> {
const getScrollPos = ()=> {
setTranslateY(window.scrollY*(-.2));
}
const setScrollPos = () => {
requestAnimationFrame(getScrollPos);
}
document.addEventListener("DOMContentLoaded",setScrollPos);
return () => {
document.removeEventListener("DOMContentLoaded",setScrollPos)
}
},[]);
Note that if you update the state with same value, react prevents a re-render.
In the second code, although you call the state update by using listenScroll directly in render function, it doesn't cause a loop because you would be setting the same value to update state and hence an infinite loop doesn't occur
How can i optimize 'form' rendering. For each key pressed, the component is rendered
Any idea to solve or improve it?
const Example = () => {
const [inputForm, setInputForm] = useState('');
const inputHandler = event => {
setInputForm(event.target.value);
};
console.log('Rendering');
return (
<div>
<div>
<span>Text: {inputForm}</span>
<input value={inputForm} onChange={inputHandler} />
</div>
</div>
);
};
log of component rendering
Thanks guys!
Try to use Uncontrolled Components, from the documentation:
https://pt-br.reactjs.org/docs/uncontrolled-components.html
As #andergtk mentioned, if you want to avoid rendering the input on each key press you'll have to resort to an input that React does not control.
To write an uncontrolled component, instead of writing an event
handler for every state update, you can use a ref to get form values
from the DOM.
but the rendering on every key press that you notice is expected in the case of a controlled input where React has control over the value of the field and should not worry you
A controlled input is more "powerful" because you let React sync your data with your input value
more on this subject in the docs or in this article (there are lots of other resources on this): https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/
improving your case is not about stop rendering, every time input value is changed it needs to be re-rendered, if not changes wouldn't apply
to improve performance you may:
useCallback to the event handler if you gonna do calculations there
maybe split label out of the component returning only input element
useState may be declared outside Input component to make sense
Input props: { value, setValue } ,. then setup callback
react renders its components based on props or state changes, so always something changes on screen react has to re-render that component
I'm building a function React component which uses various useState() hooks. Each of them providing a state and a function which can be called to update it. This works beautifully from within react itself. But in my scenario I have to deal with other (DOM/Google Maps) environments as well.
As a callback from a DOM element put on the Google map, I want to call setState(!state) to flip a boolean. However, this works only once.
I think that the problem is that the setState hook fails to fetch the latest state but uses the initial state instead. Flipping a bool 1 will invert it, but the flipping it again without taking the former change into account does not update anything.
I've managed to solve this by implementing a state that sets the boolean on a data attribute in the DOM (and then flip that bool) but I think that's a rather ugly solution.
How should I update the state in functional React component from a callback function provided by something not React?
You'll want to use functional updates.
const [bool, setBool] = React.useState(false);
// Flip bool depending on latest state.
setBool((prevBool) => !prevBool);
As opposed to using the latest state in the component itself, which can use the wrong state depending on the memoization / state life cycle:
const [bool, setBool] = React.useState(false);
// bool could be behind here. DON'T do this.
setBool(!bool);
If the problem is setState using the wrong state, you can pass a function to setState instead:
setState(state => !state);
This will use the latest state instead of the state which occurred at the React render. Not sure how this will play with the weird outside-of-React situation here, but it may help out. If this page isn't even using React (the component with the state you want to edit isn't even rendered) then HTML LocalStorage might be your best bet for persisting information.
I am confused about why I cannot clear <input> field values using this simple pattern with React:
onComponentDidMount: function () {
this.clearFields();
},
clearFields: function () {
document.getElementById('username_field').value = '';
document.getElementById('password_field').value = '';
},
I don't think it's a problem with React, I think there some other issue at hand but I am not sure what's going on. But the fields definitely do not clear out. Later on, I can call this.clearFields() and that function does work as expected, but not when the component first mounts.
The correct React lifecycle function is called componentDidMount, not onComponentDidMount
However, you don't want to do it this way if the inputs are also rendered with React. It's usually better and more relevant to the application to change values stored in the state, and let the render function deal with setting the value of the input fields.