I've got this React parent component here. The children components at this point are just returning dropdown menus. I expected that componentWillReceiveProps would update the state here, which in turn should be passed to StopList as props. However, when state.selectedSub is changed through handleSubSelect, nothing happens and StopList doesn't receive any props.
Is my mistake with the asynchronous nature of componentWillReceiveProps? Is it in the wrong place in my code? Am I using the wrong lifecycle method?
// We're controlling all of our state here and using children
// components only to return lists and handle AJAX calls.
import React, { Component } from 'react';
import SubList from './SubList';
import StopList from './StopList';
class SubCheck extends Component {
constructor (props) {
super(props);
this.state = {
selectedSub: '--',
selectedStop: null,
stops: ['--'],
};
this.handleSubSelect.bind(this);
this.handleStopSelect.bind(this);
}
// We want the user to be able to select their specific subway
// stop, so obviously a different array of stops needs to be
// loaded for each subway. We're getting those from utils/stops.json.
componentWillReceiveProps(nextProps) {
var stopData = require('../utils/stops');
var stopsArray = [];
var newSub = nextProps.selectedSub
for(var i = 0; i < stopData.length; i++) {
var stop = stopData[i];
if (stop.stop_id.charAt(0) === this.state.selectedSub) {
stopsArray.push(stop.stop_name);
}
}
if (stopsArray.length !== 0 && newSub !== this.state.selectedSub) {
this.setState({stops: stopsArray});
}
}
handleSubSelect(event) {
this.setState({selectedSub:event.target.selectedSub});
}
handleStopSelect(event) {
this.setState({selectedStop:event.target.selectedStop})
}
render() {
return (
<div>
<SubList onSubSelect={this.handleSubSelect.bind(this)}/>
<StopList stops={this.state.stops} onStopSelect={this.handleStopSelect.bind(this)}/>
</div>
);
}
}
export default SubCheck;
You are duplicating data, and causing yourself headaches that aren't necessary.
Both selectedSub and selectedStop are being stored as props and as state attributes. You need to decide where this data lives and put it in a singular location.
The problem you are encountering entirely revolves round the fact that you are changing the state attribute and expecting this to trigger a change to your props. Just because they share a name does not mean they are the same value.
Related
I was playing with ReacJS. I noticed a thing-
In case of Class Component during re-rendering class variable's updated value is updated in screen like:
import React, { Component } from "react";
class Temp extends Component {
constructor(props) {
super(props);
this.count = 0;
this.state = {
foo: 0,
};
}
render() {
return (
<button
onClick={() => {
this.setState({ foo: this.state.foo + 1 });
this.count++;
}}
>
{this.count} - {this.state.foo}
</button>
);
}
}
export default Temp;
But in case of function component the updated value of the ordinary variable is not updated in the screen during re-rendering.
import React, { useRef, useState } from "react";
const RefComponent = () => {
const [stateNumber, setStateNumber] = useState(0);
let refVar = 0;
function incrementAndDelayedLogging() {
setStateNumber(stateNumber + 1);
refVar++;
}
return (
<div>
<button onClick={incrementAndDelayedLogging}>Click</button>
<h4>state: {stateNumber}</h4>
<h4>refVar: {refVar}</h4>
</div>
);
};
export default RefComponent;
Is this how React was implemented or I'm messing around something? I'm curious to know about it.
Thanks
Functional components in React don't have instances, so things like class or instance variables don't necessarily make sense; like others have pointed out in the comments here, React will render (call) functional components and "reset" any local variables that are not explicitly state. Behavior like instance variables for functional components are achieved with useRef.
From the docs:
The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.
This is a consequence of functional components.
Think about it like this: Each time a state var is updated or a prop is updated your function gets called again. All variable declaration will happen again (states are a special case), so let refVar = 0; gets called again.
If you need that variable to live for multiple renders you'll need to declare it in a context that survives re-renders.
You have at least 2 ways of achieving this
declare a state for it with useState
declare it at the module level, but know all your instances of RefComponent will share the same instance
I have a react app. On this app I am rendering 10 tables. When a user makes a change to a table I only want that one table to re-render, not all 10.
To accomplish this task I have used React.useMemo() with a comparer function. Here it is:
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
const { categoryTotal: ctPrev, ...prev } = prevProps;
const { categoryTotal: ctNext, ...next } = nextProps;
if (
!ctPrev.totalPrice.eq(ctNext.totalPrice) &&
!ctPrev.totalWeight.eq(ctNext.totalWeight) &&
!ctPrev.totalWorn.eq(ctNext.totalWorn) &&
!ctPrev.totalConsumable.eq(ctNext.totalConsumable)
) {
console.log('totals did change')
return false;
}
for (var key in next) {
if (next[key] !== prev[key]) {
console.log('not equal', key);
return false;
}
}
console.log('props did not change')
return true;
}
export default React.memo(CategoryTable, areEqual);
I have verified that true is being returned for every table except the one that changes. So only that one table should re-render and not all 10 right? Wrong. Here is my flamegraph:
The name of my table component is CategoryTable. As you can see, the CategoryTable (memo) is grayed out but the subsequent CategoryTable is green and renders as does all of its children. I have confirmed that every category table is rendering by putting a console.log in the CategoryTable component.
How do I actually stop this component from re-rendering? Also does react.memo stop all components below in the tree from rendering or just the wrapped component?
React.memo return cach if the value didnt change its really usefull but in your case you can try pureComponent ,it prevent to render the children components if their props dont change
I'm using mobx-react / mobx-react-lite for state management
Using classes i can define a non observable idToDelete to store the clicked item id, open a Modal using an observable and when the user clicks "Delete", i know the item to delete. The id is "remembered" by the component trough the re-renders
class Greeting extends React.Component {
idToDelete = null;
confirmDelete = id => {
this.idToDelete = id;
openConfirm = true;
}
closeModal = () => {
openConfirm = true;
this.idToDelete = null;
}
#observable
openConfirm = false;
render() {
// List of items with delete button
<button onClick=this.confirmDelete(id)>Delete</button>
// Confirm Delete Modal
}
}
But in a stateless component the id will become null (the initialization value) on each re-render.
Using useLocalStore hook i can store observable values:
All properties of the returned object will be made observable
automatically
But i dont want to re-render just because i am storing/changing the id.
Using React.React.createContext / useContext seems like a bit overkill to me (it's kind of private value and it is not relevant outside the component itself)
Is there a "local storage" way to achieve this? (without observable convertion)
What are best practices for this situation?
You can use the useRef hook to save the value. A change to this value will not trigger a re-render and the value will remain the same across renders unless you override it.
Its also explained in detail here
Yes! The useRef() Hook isn’t just for DOM refs. The “ref” object is a
generic container whose current property is mutable and can hold any
value, similar to an instance property on a class.
eg:
import { useRef } from 'react';
const idToDelete = useRef("");
confirmDelete = id => {
idToDelete.current = id;
}
closeModal = () => {
idToDelete.current = null;
}
Also mind the catch, you need to use .current to access the data.
I am trying to write a function that handles a button click.
I want things to happen:
The function gets a string and changes the state named "chosenGenre" to the given string.
When a certain button is clicked, it calls the function and returns a string.
The string/state "chosenGenre" is saved so I can use it later in another component.
This is what I have wrote but I think the way I deliver the string after the button is clicked isn't right.
import React, { Component } from 'react';
import Genre from './Genre';
import './GenreSelectPage.css';
import Blues from '../Blues.png';
import Classical from '../Classical.png';
import Country from '../Country.png';
export default class GenreSelectPage extends Component{
state = {
chosenGenre: ""
}
handleClick = (genreName) => {
this.setState({chosenGenre: genreName});
}
render(){
return(
<div className = "GenreSelectPage">
<h3>Select your desired Kollab Room Genre</h3>
<Genre genrePicture= {Blues} genreClicked = {this.handleClick("blues")}/>
<Genre genrePicture= {Classical} genreClicked = {this.handleClick("Classical")}/>
<Genre genrePicture= {Country} genreClicked = {this.handleClick("Country")}/>
)
}
}
What should I change in order to call the function in the right way and keep the result?
the problem is this.handleClick("blues") is executed during render, it returns undefined, similar to genreClicked={undefined}, with a side effect to schedule 3 asynchronous setStates...
you can use a factory function that returns an event handler function with access to a closure variable:
makeHandleClick = (genreName) => (event) => {
this.setState({ chosenGenre: genreName });
// console.assert(this.state.chosenGenre); // FYI: not possible, setState is async
}
render() {
...<Genre genrePicture={Blues} genreClicked={this.makeHandleClick("blues")} />
I'm a begginer in React and would like to figure out how to modify values get using props.
f.e:
I have a MobX GameStore.tsx with #observable values:
export class GameStore {
#observable money = 0;
#observable CPS = 0;
#observable taskCodeLines = 0;
#observable taskCodeLinesTarget = 10;
...
#observable staffFrontEndCount = 4;
#observable staffFrontEndStartCost = 100;
#observable staffPHPCount = 2;
#observable staffPHPStartCost = 250;
}
Now I want to have a few StaffMember objects in Staff class:
render() {
return(
<div className="staff">
<ul className="staff-list">
<StaffMember job="Front End Developer" count={ gameStore.staffFrontEndCount } startCost = { gameStore.staffFrontEndStartCost } />
<StaffMember job="PHP Developer" count={ gameStore.staffPHPCount } startCost = { gameStore.staffPHPStartCost } />
</ul>
</div>
);
}
I pass down a data like name of this objects and some values. And now I want to modify some of them, like:
#observer
export default class StaffMember extends React.Component<any, any> {
#computed get increaseStaffCount() {
return this.props.count;
}
#action hireStaff() {
let cost = this.props.startCost * 1.4 * (this.props.count + 1);
if (gameStore.money >= cost) {
gameStore.money -= cost;
this.props.count += 1; // It's illegal because props data is read-only
this.countCPS();
}
}
How can I do this? Is this OK to create logic like above?
How should I create instances of classes in react and build a generic methods for them?
Thanks for help ;)
React does not allow the modification of props values over the course of a component's life. And there are currently two ways it has gotten around the need to change the value of props.
Load it into a state
Utilize Redux
On the first item, as xSkrappy said before, you can load the props into a Component's state, which can be updated over the course of a component's life, adding this method inside the Component in the following manner:
componentDidMount() {
this.setState({ count: this.props.count })
}
This creates a local state in the component that is equal to the prop value that was passed down to the component from its parent. And you can begin to change it from there.
You can also use the componentWillReceiveProps lifecycle method to re-render the component when the props value changes in its parent component, like such:
componentWillReceiveProps(nextProps) {
if(nextProps.count !== this.props.count) {
this.setState({ count: nextProps.count })
}
}
The second method involves utilizing Redux, a state container that can be used in React applications. Its pattern involves creating a store where the state of the entire application can be managed, and any given component can be connected to that store and receive that state as props.
While utilizing Redux is a lot more complex than the first option given, in the end you are given a lot more freedom because you can make your count value accessible to any component in your application!
Sadly implementing Redux is too lengthy a process to just detail in this answer, so I'll direct you to what I think is a good guide to refactoring your application to use Redux, should you wish to go with this option
The answer to that would be after passing the props inside StaffMember put it inside a state then from there you can modify the state :)
In ReactJs, Props are immutable so you can't modify it. Instead of using Props You can use State. State are mutable you can modify it. Or, you can use Redux concept as per your requirement.
For ex:- First make a state
this.state = {
usersList:[]
};
then you can add modification in your state like this
componentDidMount() {
this.setState({ usersList: this.props.count})
}