React Function component setting state variable V/s using local variable - reactjs

I have a functional component and want to update some state in that component (based on some context API object value i.e. I get via useContext)
Now, I see 2 options;
Using local variable
Using setState
I just wanted to understand the difference between the 2 ways. Also to add, when using setState, I run into below issue
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Just to add, this functional component is a direct child of App component and the context object is updated in another component which is also a direct child of App component.
const MyComponent = () => {
let myTitle = "ABC"; //Option 1 to use 'myTitle'
//const [myTitle, setMyTitle] = useState('ABC'); //Option 2 to use 'myTitle'
if (someContextApiObj.value === "XYZ") {
myTitle = "XYZ";
//setMyTitle("XYZ")
}
return (
<>
<ChildComponent
myTitle={myTitle}
/>
</>
);
};
export default MyComponent;

It seems you are wanting to compute a myTitle value based on some prop or context value being passed to the component. Storing passed props/context values in local state is generally considered anti-pattern in React. Compute the myTitle value and pass it along to the child component.
const MyComponent = () => {
const myTitle = someContextApiObj.value === "XYZ" ? "XYZ" : "ABC";
return <ChildComponent myTitle={myTitle} />;
};
If the myTitle value is an expensive calculation or you just want to provide a stable reference to children, use the useMemo hook to provide this.
useMemo
import { useMemo } from 'react';
const MyComponent = () => {
const myTitle = useMemo(() => {
return someContextApiObj.value === "XYZ" ? "XYZ" : "ABC";
}, [someContextApiObj]);
return <ChildComponent myTitle={myTitle} />;
};

Your code runs into an infinite loop because of your if-clause. If you set the title and the condition doesn't change, it will get set on every rerender causing a new render - infinite chain of rerenders.
Using a state offers you the ability to change the value conditionally, while using a local variable not coupled to a state makes the variable constant.

You should use the Hook useEffect.
const MyComponent = () => {
const [myTitle, setMyTitle] = useState('ABC'); //Option 2 to use 'myTitle'
useEffect(()=> {
if (someContextApiObj.value === "XYZ") {
setMyTitle("XYZ")
}
}, []);
return (
<>
<ChildComponent
myTitle={myTitle}
/>
</>
);
};
export default MyComponent;

Related

Why React is rendering parent element, even if changed state isn't used in jsx? (Using React Hooks)

Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.

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.

Stop re-rendering the react functional component

I am using a third party component that re-renders every time state changes which is good but in some instances I do not want it to re-render even if the state changes. Is there a way to do using react functional component. I have read online and it says use shouldComponentUpdate() but I am trying to use functional component and tried using React.Memo but it still re-renders
Code
const getCustomers = React.memo((props) => {
useEffect(() => {
});
return (
<>
<ThirdPartyComponent>
do other stuff
{console.log("Render Again")}
</ThirdPartyComponent>
</>
)
});
For props:
How do I implement shouldComponentUpdate?
You can wrap a function component with React.memo to shallowly compare its props:
const Button = React.memo((props) => {
// your component
});
It’s not a Hook because it doesn’t compose like Hooks do. React.memo is equivalent to PureComponent, but it only compares props. (You can also add a second argument to specify a custom comparison function that takes the old and new props. If it returns true, the update is skipped.)
For state:
There's no build in way to achieve this, but you can try to extract your logic to a custom hook. Here's my attempt to only rerender when shouldUpdate returns true. Use it with caution, because it's the opposite of what React was designed for:
const useShouldComponentUpdate = (value, shouldUpdate) => {
const [, setState] = useState(value);
const ref = useRef(value);
const renderUpdate = (updateFunction) => {
if (!updateFunction instanceof Function) {
throw new Error(
"useShouldComponentUpdate only accepts functional updates!"
);
}
const newValue = updateFunction(ref.current);
if (shouldUpdate(newValue, ref.current)) {
setState(newValue);
}
ref.current = newValue;
console.info("real state value", newValue);
};
return [ref.current, renderUpdate];
};
You would use it like this:
const [count, setcount] = useShouldComponentUpdate(
0,
(value, oldValue) => value % 4 === 0 && oldValue % 5 !== 0
);
In this case, a rerender would occur (due to usages of setcount) if, and only if, shouldUpdate returns true. i.e., when value is multiple of 4 and the previous value is not multiple of 5. Play with my CodeSandbox example to see if that's really what you want.

How to compare previous context with current context in React 16.x?

Is there a way to compare previous context value with the current context value in the consumer child component's lifecycle methods?
If there is no way to do that, is there any other workaround or an alternate solution to this?
FYI: I'm using react v16.8.1
Using HOC:
const withContext = Wrapped => props => (
<SomeContext.Consumer>
{value => <Wrapped {...props} contextValue={value}/>}
</SomeContext.Consumer>
);
const MyComponent = withContext(class extends React.Component {
...
componentDidUpdate(prevProps) {
if (prevProps.contextValue !== this.props.contextValue) {
...
}
}
...
});
Using hooks:
function MyComponent(props) {
...
const contextValue = useContext(SomeContext);
const [oldContextValue, saveContextValue] = useState(contextValue);
useEffect(() => {
console.log(oldContextValue, contextValue);
saveContextValue(contextValue);
}, [contextValue]);
...
}
Note that useEffect runs only on the first render and when contextValue changes. So if you don't really need old contextValue for something, you don't need useState.
Usually if you're going to have dynamic context its value will most likely come from a state of a parent component.
So why not detect changes in the state instead of trying to detect changes in the Provider?
Like in the example for dynamic context in the react documentation : https://reactjs.org/docs/context.html#dynamic-context

How to update react state without re-rendering component?

I am building a gallery app where I need to create multiple HTTP requests to pull gallery entries(images & videos).
As gallery will be auto scrolling entries, I am trying to prevent re-rendering component when I make subsequent HTTP requests and update the state.
Thanks
Here's an example of only re-rendering when a particular condition is fulfilled (e.g. finished fetching).
For example, here we only re-render if the value reaches 3.
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
value: 0,
}
add = () => {
this.setState({ value: this.state.value + 1});
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.value !== 3) {
return false;
}
return true;
}
render() {
return (
<React.Fragment>
<p>Value is: {this.state.value}</p>
<button onClick={this.add}>add</button>
</React.Fragment>
)
}
}
render(<App />, document.getElementById('root'));
Live example here.
All data types
useState returns a pair - an array with two elements. The first element is the current value and the second is a function that allows us to update it. If we update the current value, then no rendering is called. If we use a function, then the rendering is called.
const stateVariable = React.useState("value");
stateVariable[0]="newValue"; //update without rendering
stateVariable[1]("newValue");//update with rendering
Object
If a state variable is declared as an object, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState({ key1: "value" });
myVariable.key1 = "newValue"; //update without rendering
setMyVariable({ key1:"newValue"}); //update with rendering
Array
If a state variable is declared as an array, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState(["value"]);
myVariable[0] = "newValue"; //update without rendering
setMyVariable(["newValue"]); //update with rendering
None of the answers work for TypeScript, so I'll add this. One method is to instead use the useRef hook and edit the value directly by accessing the 'current' property. See here:
const [myState, setMyState] = useState<string>("");
becomes
let myState = useRef<string>("");
and you can access it via:
myState.current = "foobar";
So far I'm not seeing any drawbacks. However, if this is to prevent a child component from updating, you should consider using the useMemo hook instead for readability. The useMemo hook is essentially a component that's given an explicit dependency array.
It's as easy as using this.state.stateName = value. This will change the state without re-rendering, unlike using this.setState({stateName:value}), which will re-render. For example;
class Button extends React.Component {
constructor( props ){
super(props);
this.state = {
message:"Hello World!"
};
this.method = this.method.bind(this);
}
method(e){
e.preventDefault();
this.state.message = "This message would be stored but not rendered";
}
render() {
return (
<div >
{this.state.message}
<form onSubmit={this.method}>
<button type="submit">change state</button>
</form>
</div>
)
}
}
ReactDOM.render(<Button />, document.getElementById('myDiv'));
If you just need a container to store the values, try useRef. Changing the value of ref.current doesn't lead to re-rendering.
const [ loading,setLoading] = useState(false)
loading=true //does not rerender
setLoading(true) //will rerender
In functional component refer above code, for class use componentShouldUpdate lifecycle

Resources