How to modify value passed by props - javascript

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})
}

Related

React Class component updates the value for class variable in re-render but not the Function Component

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

Best way to store non-observable data in a React Stateless Component (with Hooks)

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.

Reactive models

Reactive View components (React, Angular, Vue, etc) revolutionized interface programming by eliminating the need to keep the view up to date with props/state. But as far as I know an analogous Model pattern has not been implemented/popularized (if Redux solves this problem, it's unclear to me how it can do so with classes).
The main area I'm running into a need for this (across many applications) is when some kind of constraining is involved. Some child/related model needs to be constrained when it is created, when it is updated, when the parent is updated, or when siblings are updated.
It could be done something like the following (just threw this together quickly to illustrate), but this isn't very efficient at scale because it's not selective (i.e. React only rerenders components that change).
Are there any implementations of this kind of data structure, or is this something I have to roll myself?
class ReactiveModel {
setState(newState) {
this.state = newState;
this.updateChildren();
}
setProps(newProps) {
this.props = newProps;
this.updateChildren();
}
updateChildren() {
var childrenSpecification = this.renderChildren();
//for each child
//create a new instance if one doesn't exist
//update props if any need to be updated
//updating props or state on a child triggers updateChildren on it so updating bubbles down
}
}
class Box extends ReactiveModel {
constructor(props) {
super(props);
this.state = {
boxObjects: this.props.boxObjectsFromApi //just plain data - "Object" instances, not "Box" instances
}
}
addBox(boxObject) {
this.setState({boxObjects: [...this.state.boxObjects, boxObject]}); //should call updateChildren
}
renderChildren() {
return {
boxes: this.state.boxObjects.map(({width}) => {
return {
class: Box,
props: {
width: Math.min(this.props.width, width) //constrain child width to be inside parent width
}
};
}
}
}
}

React componentWillReceiveProps not updating state

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.

How to access one component's state from another component

How do I access one component's state in another component? Below is my code and I'm trying to access the state of component a in component b.
var a = React.createClass({
getInitialState: function () {
return {
first: "1"
};
},
render: function () {
// Render HTML here.
}
});
var b = React.createClass({
getInitialState: function () {
return {
second: a.state.first
};
},
render: function () {
// Render HTML here.
}
});
But I'm not getting anything.
Even if you try doing this way, it is not correct method to access the state. Better to have a parent component whose children are a and b. The ParentComponent will maintain the state and pass it as props to the children.
For instance,
var ParentComponent = React.createClass({
getInitialState : function() {
return {
first: 1,
}
}
changeFirst: function(newValue) {
this.setState({
first: newValue,
});
}
render: function() {
return (
<a first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
<b first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
)
}
}
Now in your child compoenents a and b, access first variable using this.props.first. When you wish to change the value of first call this.props.changeFirst() function of the ParentComponent. This will change the value and will be thus reflected in both the children a and b.
I am writing component a here, b will be similar:
var a = React.createClass({
render: function() {
var first = this.props.first; // access first anywhere using this.props.first in child
// render JSX
}
}
If two components need access to the same state they should have a common ancestor where the state is kept.
So component A is the parent of B and C.
Component A has the state, and passes it down as props to B and C.
If you want to change the state from B you pass down a callback function as a prop.
I would suggest you use a state manager like Redux (personal favorite), MobX reflux, etc to manage your state.
How these works is they allow you to contain all shared state in one state storage (called a store), and whatever component needs access to a part of that shared state, it will just get it from the store.
It looked very hard to get started with but once you get over the small challenges, get 2 or 3 "wtf's" out of the way. It gets easier.
Take a look here: http://redux.js.org/
EDIT: Redux is good but the boilerplate code is really a turn off... for those of you looking for a simpler, more magical (this can be good and bad) solution use mobx : https://mobx.js.org/
in the child component create function that sets the state:
changeTheState(){
this.setState({something:"some value"})
}
and in parent component give the child a ref as following:
<Child ref={component => this._child = component}/>
then in parent make a function to access the changeTheState()
parentFunction(){
this._child.changeTheState();
}
and just use the parentFunction.
If you have A and B component where B is a child of A, you can pass a function to change the state of A though props to B.
function B(props) {
return <button onClick={props.changeA} />
}
class A extends React.Component {
//constructor
//pass this function to B to change A's state
handleA() {
this.setState({});
}
render() {
return <B changeA={() => this.handleA()} />
}
}
Take a look at React Context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
You can also update Context from a nested component if required.

Categories