How to use getDerivedStateFromProps in an update state situation? - javascript

I'm confused about the refactoring of my code.
My Component works like this ==> I set the incoming props from the parent component to the child component's state. Then I can do the update state operations in the component.
This is my UNSAFE_componentWillReceiveProps function:
UNSAFE_componentWillReceiveProps = (nextProps) => {
if (nextProps
&& nextProps.actionToEdit
&& nextProps.actionToEdit.statement
) {
const regex = /#\w+/g;
const found = nextProps.actionToEdit.statement.match(regex);
this.setState({
parameters: found
});
}
};
This is my getDerivedStateFromProps function:
static getDerivedStateFromProps(
nextProps: ICustomActionModalProps,
prevState: ICustomActionModalState
) {
if (nextProps
&& nextProps.actionToEdit
&& nextProps.actionToEdit.statement
) {
const regex = /#\w+/g;
const found = nextProps.actionToEdit.statement.match(regex);
return {
parameters: found
};
}
return null;
}
This is my onChange function:
onChange = (newValue, e) => {
const regex = /#\w+/g;
const found = newValue.match(regex);
this.setState({ parameters: found });
};
I realized something when onChange worked.
==> If I use UNSAFE_componentWillReceiveProps, the state updated perfectly. Because when the onChange function works every time UNSAFE_componentWillReceiveProps doesn't work.
However,
==> If I use getDerivedStateFromProps, the state updated with old props. Because when the onChange function works every time getDerivedStateFromProps works too.
I just want my getDerivedStateFromProps function will able to works like my old UNSAFE_componentWillReceiveProps function.
How can I do that?
Thanks

The problem you're describing is solved either by making your Component fully controlled or fully uncontrolled but with a key.
The first approach may look like this:
// --- parent component: Parent.tsx
import { Child } from "./Child";
export class Parent extends React.Component<{}, { counter: number }> {
state = { counter: 0 };
render() {
return (
<div>
<div>
<button onClick={() => this.setState(({ counter }) => ({ counter: counter + 1 }))}>increase counter</button>
</div>
<Child
parentCounter={this.state.counter}
setParentCounter={(n) => this.setState({ counter: n })}
/>
</div>
);
}
}
// --- child component : Child.tsx
type Props = { parentCounter: number; setParentCounter: (n: number) => void };
export class Child extends React.Component<Props> {
render() {
const { parentCounter, setParentCounter } = this.props;
return (
<>
<div>parent counter: {parentCounter}</div>
<button
onClick={() => {
setParentCounter(parentCounter + 1);
}}
>
increase parent counter
</button>
</>
);
}
}
So, there is not two separate states: one in the parent component and another in the child one. The only state exists in the parent component and child component has the setter prop it may use to change parent's state.
The second approach (uncontrolled component with a key) uses the fact that when the key changes the child component rerenders from scratch and looses it's inner state:
// --- Parent.tsx
import { Child } from "./Child";
export class Parent extends React.Component<{}, { counter: number }> {
state = { counter: 0 };
render() {
const { counter } = this.state;
return (
<div>
<div>parent counter: {counter}</div>
<div>
<button
onClick={() =>
this.setState(({ counter }) => ({ counter: counter + 1 }))
}
>
increase parent counter
</button>
</div>
<Child parentCounter={counter} key={`child-key-${counter}`} />
</div>
);
}
}
// --- Child.tsx
type Props = { parentCounter: number };
type State = { childCounter: number };
export class Child extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { childCounter: props.parentCounter };
}
render() {
return (
<>
<div>child counter {this.state.childCounter}</div>
<button
onClick={() => {
this.setState(({ childCounter }) => ({
childCounter: childCounter + 1
}));
}}
>
increase child counter
</button>
</>
);
}
}

Related

How can I update my child component state from the parent class in React?

I currently have a parent class (App) and a child component (FoodItem). The App component maps a list of FoodItem components.
Each individual FoodItem component has a state called clickCount which increments each time its clicked by the user. However, I need the reset button in my App component to reset the FoodItem state to 0 onClick for all FoodItem components in the map.
Any help with this would be much appreciated.
Update - I have managed to get the reset button to update the FoodItem child component, however it only updates the last item in the mapped list, whereas I require it to update all items in the list.
class App extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
foods: FoodCards,
calorieCount: 0,
vitaminACount: 0,
vitaminDCount: 0,
};
this.increment = this.increment.bind(this);
}
increment = (calories = 0, vitaminA = 0, vitaminD = 0) => {
this.setState({
calorieCount: Math.round(this.state.calorieCount + calories),
vitaminACount: Math.round((this.state.vitaminACount + vitaminA) * 100) / 100,
vitaminDCount: Math.round((this.state.vitaminDCount + vitaminD) * 100) / 100
});
};
resetCounters = () => {
this.myRef.current.resetClickCount();
this.setState({
calorieCount: 0,
vitaminACount: 0,
vitaminDCount: 0,
});
};
render() {
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{this.state.foods.map((item, i) => {
return <FoodItem key={item.id} ref={this.myRef} name={item.name} img={item.img} calories={item.calories} vitamin_a={item.vitamin_a} vitamin_d={item.vitamin_d} updateTotals = {this.increment} />
})}
</main>
<footer>
<div className="reset" onClick={() => this.resetCounters()}>Reset</div>
</footer>
</div>
);
}
}
export default App;
class FoodItem extends React.Component {
constructor(props) {
super(props);
this.state = {
clickCount: 0
};
}
handleUpdateTotals = (calories, vitamin_a, vitamin_d) => {
this.props.updateTotals(calories, vitamin_a, vitamin_d);
this.clickIncrement();
}
clickIncrement = () => {
this.setState({
clickCount: this.state.clickCount + 1
});
}
resetClickCount = () => {
this.setState({
clickCount: 0
});
}
render() {
return (
<div className="product" onClick={() => this.handleUpdateTotals(this.props.calories, this.props.vitamin_a, this.props.vitamin_d)}>
<p>{this.props.name}</p>
{this.state.clickCount > 0 ? <p>Selected: {this.state.clickCount}</p> : <p>Not Selected</p>}
<img src={this.props.img} alt="" />
</div>
);
}
}
You should lift the state up per the react docs. When two children affect the same state (reset button and clickCount), the state should be lifted up to the parent that contains both of these children. So your count should be stored in the parent.
But... to answer your question, you can easily do something like this to trigger a render each time the button is clicked (not recommended though):
const [resetCount, setResetCount] = useState(false);
const resetCountClicked = () => setResetCount(!resetCount);
return
<>
<Child resetCount={resetCount}></Child>
<button onClick={() => resetCountClicked()}>Reset Count</button>
</>
And in the child include:
useEffect(() => setCount(0), [resetCount]);
This is using hooks with function components and useEffect to run an event each time the prop resetCount changes.
With classes you can use a ref passed to the child as shown in this post.
const { Component } = React;
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.resetCount();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Reset Count</button>
</div>
);
}
}
And in the child...
resetCount() {
this.setState({
clickCount: 0
});
}

how to fix 1 state gap?

I'm practice send state to Child-component - Parent-Component - Another-Child-Component.
and I was stuck on my practice,
First, I was successful to make react counter like this
child component
Second, I want to send counter value to another component.
I know how to send state to another child component.
But i stuck my work.
Send state to another component
when i first clicked + button, it works only First child.
and then, when i clicked once more, it works another child too(not match number)
How can I dealing this problem?
This is my code.
// This is Counter.js
class Counter extends Component {
state = {
counter: 0
}
handleIncrement = () => {
this.setState(({ counter }) => ({
counter: counter + 1
}))
this.props.handleCounter(this.state.counter)
}
handleDecrement = () => {
this.setState(({counter}) => ({
counter: counter - 1
}))
this.props.handleCounter(this.state.counter)
}
render() {
return (
<div>
<h1>Counter</h1>
<h3>{this.state.counter}</h3>
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
</div>
)
}
}
// This is App.js file
import Counter from './components/counter';
import Sent from './components/sent'
class App extends React.Component {
state = {
counter: this.handleCounter
}
handleCounter = (counter) => {
console.log("Received Count 1 ")
this.setState({
counter: counter
})
}
render() {
return (
<div className="App">
<Counter handleCounter={this.handleCounter} />
<Sent result={this.state.counter} />
</div>
);
}
}
// This is Sent.js file
import React, { Component } from 'react'
class Sent extends React.Component {
render() {
return (
<div>
<h2>Result ==> {this.props.result}</h2>
</div>
)
}
}

Passing a function with a parameter to child component and sending return value back to parent to be stored in parent state in reactjs

Please forgive me, I am new to programming and JavaScript/React...
This is the question from my assignment:
Make a counter application using React and Node.js. The user must have the ability to click a button to increase, decrease, or reset a counter. The app must have the following components: Display, DecreaseCount , IncreaseCount, ResetCount. Pass the appropriate functions to be used and current counter value to each component.
I'm not sure what the point is of creating components for those simple operations. I also don't understand what will make those arithmetical components unique if I'm passing them both a function and a value to work on. But I am assuming the point of the assignment is to show that you can pass state to a child, work on it within the child, and then pass the worked-on result back to the parent to be stored in its state.
Here is the main page, Display.js.
For now I'm just trying to get the add functionality to work:
import React, { Component } from 'react';
import IncreaseCount from './IncreaseCount';
import DecreaseCount from './DecreaseCount';
import ResetCount from './ResetCount';
class Display extends Component {
constructor(props) {
super(props);
this.increment = this.increment.bind(this);
this.state = {
count: 0
};
}
increment = numToInc => {
this.setState({ count: numToInc++ });
};
decrement = numToDec => {
this.setState({ count: numToDec-- });
};
reset = numToReset => {
numToReset = 0;
this.setState({ count: numToReset });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<IncreaseCount count={this.state.count} operation={this.increment} />
<DecreaseCount count={this.state.count} operation={this.decrement} />
<IncreaseCount count={this.state.count} operation={this.reset} />
</div>
);
}
}
export default Display;
And here is the IncreaseCount component class:
import React, { Component } from 'react';
class IncreaseCount extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
buttonClick = () => {
this.setState({ count: this.props.count }); // I am pretty sure this isn't necessary
this.props.operation(this.state.count);
};
render() {
return <button onClick={this.buttonClick}></button>;
}
}
export default IncreaseCount;
It is not throwing any errors but is not changing the value of either the Increase count or the Display count properties. I was expecting both to be changing in lockstep. My goal is to send the incremented value back to Display to be displayed. Is there a problem with the way I've written and passed my increment function?
You need to use this.props.count within the IncreaseCount
class IncreaseCount extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
...
}
A full example might look something like this:
class Display extends Component {
state = {
count: 0
};
increment = numToInc => {
this.setState({ count: numToInc + 1 });
};
decrement = numToDec => {
this.setState({ count: numToDec - 1 });
};
reset = () => {
this.setState({ count: 0 });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<Operation
name="+"
count={this.state.count}
operation={this.increment}
/>
<Operation
name="-"
count={this.state.count}
operation={this.decrement}
/>
<Operation
name="Reset"
count={this.state.count}
operation={this.reset}
/>
</div>
);
}
}
class Operation extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
render() {
return <button onClick={this.buttonClick}>{this.props.name}</button>;
}
}
Note that you don't have to pass the counter value to each Operation and use a functional setState:
increment = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
Using a single component like <Operation /> is certainly how I'd do it. However, per the requirements of the OP, I'm adding this example that uses all 4 components specified.
import React, { Component } from 'react';
class IncreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>+</button>;
}
}
class DecreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>-</button>;
}
}
class ResetCount extends Component {
render(props) {
return <button onClick={this.props.action}>reset</button>;
}
}
class Display extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
if (this.state.count > 0) {
this.setState({ count: this.state.count - 1 });
}
}
reset() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<h2>{this.state.count}</h2>
<DecreaseCount count={this.state.count} action={this.decrement} />
<IncreaseCount count={this.state.count} action={this.increment} />
<ResetCount count={this.state.count} action={this.reset} />
</div>
);
}
}
export default Display;
This version also prevents the counter from going below 0.

Setting State with Objects from Firebase

I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;

React.JS - multiple elements sharing a state ( How do I modify only one of the elements without affecting the others? )

class App extends Component {
constructor(props) {
super(props);
this.state = { Card: Card }
}
HandleEvent = (props) => {
this.SetState({Card: Card.Active}
}
render() {
return (
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
)
}
}
const Card = props => {
return (
<div style={props.state.Card} onClick={
props.HandleEvent}>Example</div>
)
}
Every time I click on one of the cards all of my elements change states, how do I program this to only change card that I clicked?
Here's a working example
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
0: false,
1: false
};
}
handleEvent(idx) {
const val = !this.state[idx];
this.setState({[idx]: val});
}
render() {
return (
<div>
<Card state={this.state[0]} handleEvent={()=>this.handleEvent(0) } />
<Card state={this.state[1]} handleEvent={()=>this.handleEvent(1) } />
</div>
);
}
}
const Card = (props) => {
return (<div onClick={() => props.handleEvent()}>state: {props.state.toString()}</div>);
}
You can also see it in action here
Obviously this is a contrived example, based on your code, in real world application you wouldn't store hardcoded state like {1: true, 2: false}, but it shows the concept
It's not completely clear from the example what is the Card in the constructor. But here the example of how you can modify clicked element.
Basically you can keep only index of clicked element in parent's state, and then pass it as some property to child component, i.e. isActive here:
const cards = [...arrayOfCards];
class App extends Component {
constructor(props) {
super(props);
this.state = { activeCardIndex: undefined }
}
HandleEvent = (index) => {
this.SetState({
activeCardIndex: index
});
}
render() {
return ({
// cards must be iterable
cards.map((card, index) => {
return (
<Card
key={index}
Card={Card}
isActive={i === this.state.activeCardIndex}
HandleEvent={this.HandleEvent.bind(this, index)}
/>
);
})
});
}
}
const Card = props => {
// style active card
const style = Object.assign({}, props.Card, {
backgroundColor: props.isActive ? 'orange' : 'white',
});
return (
<div style={style} onClick={
props.HandleEvent}>Example</div>
)
}

Categories

Resources