useEffect does not run in initial render - reactjs

In Piano component I am getting window's dimensions using a hook (useWindowDimensions()). In useEffect, I want to render component everytime width of window changes.
It re-renders the component everytime width changes (because keyData depends on it and it is in dependencyList) but does not render in initial render. So component does not render until I resize the window.
Following code is Piano component.
const svgRef = React.useRef(null);
const svgEl = d3.select(svgRef.current);
const { width } = useWindowDimensions();
const height = width / 4;
const keyData = generateKeyData(88, width);
React.useEffect(() => {
svgEl.selectAll("*").remove();
generatePianoNode(svgEl, keyData);
}, [svgEl, keyData]);
return (
<svg ref={ svgRef } width={ width } height={ height } />
);
I tried to put another useEffect hook with empty dependency list with the same code but it also didn't work.

The useEffect hook actually renders on initial component mount but the problem here is that the way you generate piano node .
You need to somehow put the value in a state and update state so the component re-renders everytime the state changes and also when the component mounts .
const [value , setValue ] = React.useState("1")
React.useEffect(() => {
// your logic
// setValue("2")
}, [value]);
Now the useEffect hook will run on component mount and then everytime the state value changes .
Note
If the value change simultaneously it will cause rendering problem in the component so a better option in this case is assigning an event listener in useEffect like this :
const handleScroll = () => {
// your logic
}
React.useEffect(() => {
if (typeof window !== "undefined"){
window.addEventListener("scroll" , handleScroll)
}
return () => {
// on component unmount remove event listener
if (typeof window !== "undefined"){
window.removeEventListener("scroll" , handleScroll)
}
}
}, []);

Related

React useEffect syntax reason on mount

I'm following this tutorial
https://javascript.plainenglish.io/5-advanced-react-patterns-a6b7624267a6
Can you explain me what's the purpose of this code, please?
const firstMounded = useRef(true);
useEffect(() => {
if (!firstMounded.current) {
onChange && onChange(count);
}
firstMounded.current = false;
}, [count, onChange]);
https://github.com/alex83130/advanced-react-patterns/blob/main/src/patterns/compound-component/Counter.js#L9
useEffect is react renders after a component is mounted / rendered in the DOM . In your case , the useEffect will be triggered,
when the component is mounted for the first time
when the value of count changes
when the onChange changes ,

useEffect is not triggered by a change in ref.current from a child component

I have created a popup that the user can use to add stuff to the application, every field is a separate component, because I need to reuse them in several places in different configruations.
I have tried to create an innerRef that when changed (i.e. new value is typed), the useEffect of the component should be triggered to show or hide the Done button if all values are valid.
I know that all values are valid or not from the valid prop that I assign to .current
export default function AddStock() {
const selectTypeOfQuantityRef = useRef({});
const [allValid, setAllValid] = useState(false);
useEffect(() => {
const allValues = [selectTypeOfQuantityRef.current.valid];
allValues.every((value) => value) ? setAllValid(true) : setAllValid(false);
console.log(allValues.every((value) => value)); // does not get triggered
}, [selectTypeOfQuantityRef.current]);
return (
<>
<AddPopup>
<SelectTypeOfQuantity innerRef={selectTypeOfQuantityRef} />
{allValid && <DoneButton/>}
<CancelButton/>
</AddPopup>
</>
);
}
And this is the select itself (custom of course), that sets innerRef, whenever its state changes.
Everything here works, the state of this small component itself is managed correctly, but it just does not get triggered the state update of the parent component
export default function SelectTypeOfQuantity({ defaultValue = null, innerRef }) {
const [selectTypeOfQuantity, setSelectTypeOfQuantity] = useState(defaultValue);
const [valid, setValid] = useState(false);
const [errMessage, setErrMessage] = useState("Избери стойност!");
useEffect(() => {
innerRef.current.value = selectTypeOfQuantity;
handleValidation(selectTypeOfQuantity);
}, [selectTypeOfQuantity]);
const handleValidation = (value) => {
const result = validateAutocomplete(value);
if (result.valid) {
setValid(true);
setErrMessage(null);
innerRef.current.valid = result.valid;
} else {
setValid(false);
setErrMessage(result.errMessage);
}
};
const selectTypeOfQuantityOnChange = (e, val) => {
setSelectTypeOfQuantity(val ? val.value : null);
};
return (
<Select onChange={selectTypeOfQuantityOnChange}/>
);
}
useRef does not trigger rerenders, thus useEffect will not be called
Use useRef when you need information that is available regardless of component lifecycle and whose changes should NOT trigger rerenders. Use useState for information whose changes should trigger rerenders.
Solution
As React's Philosophy states, all the data must reside within React, that's why even input components are supplied with a value and onChange event. React can't track data changes that happens outside it. As I understand from your question, the changes are happending within the React App, So instead of tracking the data through the innerRef, track them within React using React's own methods.

React component state wiped before component unmounted

If I return a function from useEffect I can be sure that that function will run when a component unmounts. But React seems to wipe the local state before it calls my unmounting function.
Consider this:
function Component () {
const [setting, setSetting] = useState(false)
useEffect(() => {
setSetting(true)
// This should be called when unmounting component
return () => {
console.log('Send setting to server before component is unmounted')
console.log(setting) // false (expecting setting to be true)
}
}, [])
return (
<p>Setting is: {setting ? 'true' : 'false'}</p>
)
}
Can anyone confirm that the expected behaviour is that the components state should be wiped? And, if that is the correct behaviour, how does one go about firing off the current component state to a server just before the component is unmounted?
To give some context, I'm debouncing a post request to a server in order to avoid firing it every time the user changes a setting. The debouncing works nicely, but I need a way to fire the request once a user navigates away from the page, as the queued debouncing method will no longer fire from the unmounted component.
It's not that React "wipes out the state value", it's that you have closure on setting value (the value on-mount).
To get expected behavior you should use a ref and another useEffect to keep it up to date.
function Component() {
const [setting, setSetting] = useState(false);
const settingRef = useRef(setting);
// Keep the value up to date
// Use a ref to sync the value with component's life time
useEffect(() => {
settingRef.current = setting;
}, [setting])
// Execute a callback on unmount.
// No closure on state value.
useEffect(() => {
const setting = settingRef.current;
return () => {
console.log(setting);
};
}, []);
return <p>Setting is: {setting ? "true" : "false"}</p>;
}

functional component rerender on state change

I've implemented a window resize event on initial component load. The event detects the window inner width and saves the value in a hook. Based on the width hook there is a second useEffect function, triggered on width change:
export const AppRouter = (props) => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
useEffect(() => {
setAppLayoutForViewportWidth();
}, [width]);
}
Now to the unexpected behavior: the entire component rerenders on width hook change and not only the useEffect based on the width hook.
Can someone name the reason, why the entire component rerenders? Can I only rerender the width-based useEffect?
Why?
setState({}) always forces to re-render. (unless you return false in: shouldComponentUpdate(nextProps, nextState)) You can check this by putting a console log in
componentDidUpdate(prevProps, prevState) {
console.log("Component did update")
}
Your setWidth(window.innerWidth);will change state due to it being: useState(window.innerWidth); which will force a rerender.
How to prevent it:
If you want to control when it rerenders, create a logic in shouldComponentUpdate to return false when you want to prevent rerender.
If you are in a functional component have a look at React.Memo. More on that in this thread: How can I prevent my functional component from re-rendering with React memo or React hooks?
React.Memo functions similarly to pure component. However, you can
also adjust its behavior by passing it a function which defines what
counts as equal. Basically, this function is shouldComponentUpdate,
except you return true if you want it to not render.
const areEqual = (prevProps, nextProps) => true;
const MyComponent = React.memo(props => {
return /*whatever jsx you like */
}, areEqual);

How to specify a constructor with a functional component (fat arrow syntax)?

Given this component:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
const NewGoalInput = props => {
return (
<input type="text" onKeyUp={handleKeyUp}/>
)
}
const handleKeyUp = (e) => {
if (e.key === "Enter") {
// TODO Add goal
}
}
export default NewGoalInput
How do I add a constructor where I can define the state without using the extends React.Component syntax?
Since it's a stateless component it doesn't have the component lifecycle.
Therefor you can't specify a constructor.
You have to extend React.Component to create a stateful component which then will need a constructor and you'll be able to use the state.
Update
Since React 16.8.0 and Hooks got introduced there are more options.
Hooks are a new feature proposal that lets you use state and other React > features without writing a class. They are released in React as a part of > v16.8.0
Stateless:
import React from "react"
const Stateless = ({name}) => (
<div>{`Hi ${name}`}</div>
);
Stateful:
Has access to component lifecycle methods and local state.
class Stateful extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
componentDidUpdate() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
render() {
const { count } = this.state;
return (
<div>
<p>You've clicked {count} times.</p>
<button onClick={() => this.setState({ count: count + 1 })}>
Click me
</button>
</div>
);
}
}
Using Hooks:
Able to use State Hook and Effect Hook.
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
import React, { useState, useEffect } from "react";
const UsingHooks = () => {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You've clicked ${count} times.`;
});
return (
// <> is a short syntax for <React.Fragment> and can be used instead of a wrapping div
<>
<p>You've clicked {count} times.</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</>
);
}
Now that we have useState and hooks the answers are kind of out of date. I came across this question because I was doing something wrong. Here's some simplified code of what I was doing.
// set an initial state
const [ value, setValue ] = useState(0)
// gets called after component is re-rendered
useEffect(() => {
// callback to parent that set props
props.update()
})
// if we have an existing value passed in
if (props.value) {
setValue(props.value)
}
This code was converted from a stateful class to a function using hooks, originally setting the default props in the constructor - but functions don't have constructors and that check happens every time the component re-renders:
calls useState
triggers re-render
useEffect is triggerd
parent is called which sets the props
props update so child renders again
GOTO 1
As you can see this results in an infinite loop. The solution is really quite simple. Here's a mock diff from the original.
- const [ value, setValue ] = useState(0)
+ const [ value, setValue ] = useState(props.value || 0)
- if (props.value) {
- setValue(props.value)
- }
Basically, just initialise the state from the props and don't do silly things like calling useState except in response to an event or callback of some type.
You can use useMemo hook (as below) to demonstrate as constructor for functional component. Somebody suggested to use useEffect but it will be invoked after render.
useMemo(() => {
console.log('This is useMemo')
}, []);
you could set a useState as the first line inside of your functional component and add a function as "initial value":
const MyComponentName = props => {
useState(() => {
console.log('this will run the first time the component renders!');
});
return <div>my component!</div>;
};
You don't. The kind of component in your example is called "stateless functional component". It has no state and no lifecycle methods. If you want your component to be stateful you'll have to write it as a class component.
To simulate constructor in FC use useEffect.
useEffect(() => {
... here your init code
}, []);
That's it! EZ! This useEffect runs only once when the component loads and never runs after, just don't forget to add square brackets at the end.
For those who want to run a function once before the component is mounted, here is a hook (written in TypeScript).
Normally useEffect and useLayoutEffect suffice, but they run after the component is mounted, and sometimes you want to run code before that happens (like a constructor).
import React, { useRef } from "react";
function useOnce<Type>(callBack: () => Type): Type {
const result = useRef<Type | null>(null);
if (result.current !== null) {
return result.current;
}
result.current = callBack();
return result.current;
}
const Component: React.FC<{}> = () => {
const result = useOnce(() => {/* Code you would normally put in a constructor */});
return <div />
}
Alternatively, you can use react-afc
import { afc, reactive } from 'react-afc'
function heavyCalc() {/*...*/}
const Conponent = afc(props => {
// Called once, before the first render
const state = reactive({
name: 'Stack',
inputsCount: 0
})
// Without useMemo(..., [])
const result = heavyCalc()
// The function is created once and does not cause
// a re-render of child components
function onInput(e) {
state.inputsCount++
state.name = e.currentTarget.value
}
// Saved between renders (no longer need useRef)
let rendersCount = 0
// Must return the render-function
return () => {
// The function works like a regular react-component
// Here you can use the usual hooks
rendersCount++
return (
<input onChange={onInput} value={state.name}/>
)
}
})
The package has the necessary methods for working with state (including redux), react-hooks, lifecycle methods and context

Resources