Props in state anti pattern - reactjs

So i was reading the react docs which suggest using props in state is considered to be an anti pattern. I wanted to know what the write way is. I have a parent state container passing data to a child component which also has a state.
constructor(props) {
this.state = {
name = this.props.name
}
}
So i was wondering is it ok if i update the state in the CDM method
constructor(props) {
this.state = {
name = ''
}
}
componentDidMount() {
this.setState({name : this.props.name})
}
Any help or discussion here would be grateful.

Using props in state is a big no no. Here is an example how to do deal with situations like these
class Parent extends Component {
state = {
name: 'John Doe'
}
updateName = (name) => {
this.setState({ name });
};
render() {
return (
<div>
<label>Your Name</label>
<Input value={this.state.name} updateName={this.updateName} />
</div>
);
}
}
class Input extends Component {
handleUpdate = (e) => {
this.props.updateName(e.target.value);
};
render() {
return <input value={this.props.value} onChange={this.handleUpdate} />
}
}

Related

React ref state is null

I want to access the state of a Child component by using refs, but the state of the ref is always null.
In my React app, I have an Editor(basically, it is a form) that manipulates its own states, e.g. value change, update. The editor is used on multiple pages.
Editor.jsx
export default class Editor extends React.Component {
constructor(props) {
super(props);
this.state = {
value1: null,
... other values
};
}
onValue1Change = (e) => {
this.setState({value1: e.target.value});
}
onSave = (e) => {
// save values
}
render() {
return (
<div>
<input value={this.state.value1} onChange={this.onValue1Change}/>
... other input fields
<button onClick={this.onSave}>Save</button>
</div>
)
}
}
Now, there is a RegisterForm which covers all fields in the Editor. I made a small change in the Editor to hide the Save button so I can use it in the RegisterForm:
RegisterForm.jsx
export default class RegisterForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: null,
firstname: null,
lastname: null
};
this.Editor = React.createRef();
}
onSave = (e) => {
let childState = this.Editor.current.state;
// childState is ALWAYS null!
}
render() {
return (
<div>
<input value={this.state.email} onChange={this.onEmailChange}/>
<input value={this.state.firstname} onChange={this.onFirstnameChange}/>
<input value={this.state.lastname} onChange={this.onLastnameChange}/>
...
<Editor ref={this.Editor} showSave={false}/>
...
<button onClick={this.onSave}>Save</button>
</div>
)
}
}
Turns out this.Editor.current.state is always null.
I have two questions.
Why this.Editor.current.state is null?
If I want to use props, how should I change my code? E.g. If I let RegisterForm pass props to Editor, I'd imagine something like this:
Editor.jsx
export default class Editor extends React.Component {
// same constructor
onValue1Change = (e) => {
this.setState({value1: e.target.value}, () => {
if(this.props.onValue1Change) this.props.onValue1Change(e);
});
}
// same render
}
RegisterForm.jsx
export default class RegisterForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: null,
firstname: null,
lastname: null,
value1: null,
};
}
onValue1Change = (e) => {
this.setState({value1: e.target.value});
}
render() {
return (
<div>
<Editor showSave={false} onValue1Change={this.onValue1Change}/>
...
</div>
)
}
}
does it make the Child component render twice? Any suggestions on how to improve it?
You are passing the ref as a prop to the <Editor/> component but not doing anything with it after that.
For example:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Receive props and ref through the forwardRef() callback parameter, then pass the ref to the child node.
This is called ref forwarding
I made a code sandbox for you to test it!

How to update component based on container's state change

I have a React container called UserContainer which renders a component called UserComponent.
The code looks approximately like this (I have removed the unnecessary bits):
// **** CONTAINER **** //
class UserContainer extends React.Component<ContainerProps, ContainerState> {
state = { firstName: "placeholder" };
async componentDidMount() {
const response = await this.props.callUserApi();
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
private isChanged(componentState: ComponentState) {
return this.state.firstName === componentState.firstName;
}
async save(newValues: ComponentState) {
if (!this.isChanged(newValues)) {
console.log("No changes detected.");
return;
}
const response = await this.props.changeFirstName(newValues.firstName);
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
render() {
return <UserComponent firstName={this.state.firstName} onSave={(newValues: ComponentState) => this.save(newValues)} />;
}
}
export default UserContainer;
// **** COMPONENT **** //
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
export default UserComponent;
The problem is that this.state.firstName in the component is always "placeholder". Even after the container gets its values from the API, the state of the component is not changed (however, the props are changed). When adding console.log into the individual methods, the flow of individual steps is following:
Container render()
Component constructor()
Component render()
Container didMount()
Container render()
Component render()
As you can see, the component constructor is called just once, prior to the container receiving its data from the backend API. Is there a way to pass the updated container state into the component in order to display the real data?
There are really FEW cases where updating state by props is necessary, I suggest you to read the full blog post from facebook under paragraph "Preferred Solutions": https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
componentWillReceiveProps(nextProps: ComponentProps){
if(nextProps.firstName != this.props.firstName){
this.state = { firstName: nextProps.firstName }
}
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
For latest React version please use getDerivedStateFromProps
You are already passing the updated data to the component. Only mistake is, you are assigning it once. So, whenever you get the updated values, it doesn't reflect, since you don't have only assigned it once.
Two ways to go about it.
If there is no manipulation taking place. Change this.state.firstName to this.props.firstName
<input type="text" value={this.props.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
If there is some manipulation taking place, you'll be doing it in the componentWillReceiveProps method and then setting your firstName state. This method will be triggered whenever you'll be updating the states.
Example -
componentWillReceiveProps(nextProps) {
if(this.props.firstName!==nextProps.firstName) {
//do your validation
}
}
EDIT
As dubes rightly pointed out, componentWillReceiveProps method is deprecated. So you'll have to use the static getDerivedStateFromProps and have to return the new resulting state from this method.
Hope this helps :)

React - how to update state in parent component onclick in child - what am I doing wrong?

I have a parent and child component and simply take the input and on click I want to sort of update the parent. My onChange function works, but onClick I get the error message: Cannot read property 'name' of undefined - meaning that I never actually updated the parent. I cant figure out what am I doing wrong because as I understand, Im am passing the function correctly. Anyone knows? Thanks!
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {name: 'Frarthur'};
this.changeName = this.changeName.bind(this);
}
changeName(newName) {
this.setState({
name: newName
});
}
handleInput() {
console.log("helloooooo", this.state.name)
}
render() {
return (
<div>
<Child name={this.state.name} onChange={this.changeName} onClick={this.handleInput}/>
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleInput2 = this.handleInput2.bind(this);
}
handleChange(e) {
const name = e.target.value;
this.props.onChange(name);
}
handleInput2() {
this.props.onClick()
}
render() {
return (
<div>
<h1>
Hey my name is {this.props.name}!
</h1>
<input onChange={this.handleChange}/>
<input type="submit" onClick={this.handleInput2}/>
</div>
)
}
}
You could bind them in your constructor, like you did w/ changeName:
constructor(props) {
super(props);
this.state = {name: 'Frarthur'};
this.changeName = this.changeName.bind(this);
this.handleInput = this.handleInput.bind(this);
}
Alternatively, you could apply the proposed ES7 functionality (still considered "experimental" but extremely likely to be supported) property initializer by using fat arrow function:
handleInput = () => {
console.log("helloooooo", this.state.name)
};
Note, this may require you to update your project's configuration to support stage-0 preset for babel.
You haven't bind the
handleInput()
method in the Parent component. Here is the corrected Parent component
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { name: 'Frarthur' };
this.changeName = this.changeName.bind(this);
this.handleInput = this.handleInput.bind(this);
}
changeName(newName) {
this.setState({
name: newName
});
}
handleInput() {
console.log("helloooooo", this.state.name)
}
render() {
return (
<div>
<Child name={this.state.name} onChange={this.changeName} onClick={this.handleInput} />
</div>
)
}};

How to use dynamically created classes in react components

I have a class of this form:
export default class FixedMem {
constructor(totalMem){
this._totalMem = totalMem
}
get totalMem(){
return this._totalMem
}
addMem(mem){
this._totalMem += mem
}
}
I import it into my react component like this :
import Fixed from '../somewhere'
If i want to create a new classes with varying parameters based on input from a textbox and display its values. How do i call its methods from inside the render method ?. This somewhat illustrates my problem
class fixedBlock extends Component {
constructor(){
super()
this.state = {
"textInput":"",
"totalMem":0,
"fixed":null
}
}
handleInputChanged(e){
this.setState({
"textInput":e.target.value
})
}
handleButtonPressed(){
this.setState({"fixed":new Fixed(parseInt(this.state.textInput))})
}
incrementButtonPressed(){
this.state.fixed.addMem(2)
}
render(){
return(
<div>
<input type="button" onClick={this.handleInputChanged} value=
{this.state.textInput}>
<button onClick={this.handleButtonPressed}>create</button>
<button onClick={this.incrementButtonPressed}> increment </button>
<p>{this.state.fixed.totalMem}</p>
</div>
)
}
}
this doesn't work, another approach i had to solve this problem was using closures, so inside my react component :
class fixedBlock extends Component{
constructor(){//stuff here}
FixedMem () {
var FixedObj = null
return {
initFixed: function (totalMem) {
FixedObj = new Fixed(totalMem, divisions)
},
totalMem: function () {
return FixedObj.totalMem
},
increment: function(){
FixedObj.addMem(2)
}
render(){//stuff here}
}
How do i even use this in the render method ?
There are several issues with your code example. Missing closing tags and rebinding of methods missing.
Here's an example of dynamically usage of a class instance in a React component. However I can not recommend to use this approach. This is mainly as proof of concept.
class MyValue {
constructor(val) {
this._val = parseInt(val, 10) || 0;
}
get total() {
return this._val;
}
set total(val) {
this.val = val;
}
add(val) {
this._val += val;
}
subtract(val) {
this._val -= val;
}
}
Here's the React component
class Block extends React.Component {
constructor() {
super();
this.state = {
textInput: "",
myValue: new MyValue()
};
}
handleInputChanged(e) {
this.setState({
textInput: e.target.value
});
}
handleButtonPressed() {
this.setState({ myValue: new MyValue(this.state.textInput) });
}
incrementButtonPressed() {
this.state.myValue.add(2);
this.forceUpdate(); /* React does not know the state has updated, force update */
}
render() {
return (
<div>
<input type="number" step="1" onChange={this.handleInputChanged.bind(this)} />
<button onClick={this.handleButtonPressed.bind(this)}>create</button>
<button onClick={this.incrementButtonPressed.bind(this)}>increment</button>
<p>{this.state.myValue.total}</p>
</div>
);
}
}
As an alternative approach. You could use a pattern where you separate logic from presentation. Here's an example using function as child. The Calculator handles the calculation and Presentation uses the calculator and present the GUI.
class Calculator extends React.Component {
constructor() {
super();
this.state = {value: 0};
}
add(value){
this.setState(prevState => ({value: prevState.value + value}));
}
subtract(value){
this.setState(prevState => ({value: prevState.value - value}));
}
set(){
this.setState(prevState => ({value: parseInt(prevState.input, 10) || 0}));
}
input(value){
this.setState({input: value});
}
render() {
return this.props.children(
{
value: this.state.value,
add: this.add.bind(this),
subtract: this.subtract.bind(this),
set: this.set.bind(this),
input: this.input.bind(this),
});
}
}
const Presentation = props => (
<Calculator>
{ ({value,add,subtract,set,input}) => (
<div>
<button onClick={() => add(2)}>add 2</button>
<button onClick={() => subtract(3)}>subtract 3</button>
<input type="number" step="1" onChange={e => input(e.target.value)} />
<button onClick={set}>set</button>
<p>{value}</p>
</div>)
}
</Calculator>);
The problem with the first attempt is that you are mutating a Component's state without letting React know about it. You need to use setState() or forceUpdate(). One way to still have FixedMem manage your state while letting React know could be:
class FixedBlock extends Component {
constructor(props) {
super(props);
this.state = {
textInput: '',
totalMem: 0
};
this.fixedMem = new FixedMem(0);
this.sync = this.sync.bind(this);
}
sync() {
const totalMem = this.fixedMem.totalMem;
this.setState({ totalMem });
}
handleInputChanged(evt) {
this.setState({ textInput: evt.target.value });
}
handleButtonPressed() {
this.fixedMem = new FixedMem(parseInt(this.state.textInput));
this.sync();
}
incrementButtonPressed() {
this.fixedMem.addMem(2);
this.sync();
}
render() {
return (
<div>
<input type="text" onChange={this.handleInputChanged.bind(this)} />
<button onClick={this.handleButtonPressed.bind(this)}>create</button>
<button onClick={this.incrementButtonPressed.bind(this)}>increment</button>
<p>{this.state.totalMem}</p>
</div>
);
}
}

Get props value in method react/redux

I am very new to react and redux. I have created an application using react/redux. I have implemented routing in the application and also have able to manage the state using redux. Now my problem is After set the state i am only able to get the props in render() not anywhere in the component. below are my code,
export class EmpSearch extends React.Component {
constructor(props) {
super(props);
this.state = {
Empnumber: ''
};
}
EmpSearch = (e) => {
if (e.key === 'Enter') {
browserHistory.push('/Emp/' + e.target.value);
}
}
updateEmpNumber(e) {
this.props.dispatch({
type: 'UPDATE_EMP_NUMBER',
payload: e.target.value
});
}
render() {
return (
<div className="row">
<form>
<div className="form-group">
<label htmlFor="Empnumber">Emp Number</label>
<input type="text" className="form-control" id="Empnumber" placeholder="Emp Number" value={this.props.Empnumber} onChange={this.updateEmpNumber.bind(this)} onKeyPress={this.EmpSearch}/>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state){
return {
Empnumber: state.Empnumber
}
}
export default connect(mapStateToProps)(EmpSearch);
I want to pass the value to below component but instead of passing and getting value to render i want to create a method and want to pass the props there only.
class EmpDetail extends React.Component {
render() {
const empNumber = this.props.Empnumber;
return (
<div className="container">
Empnumber = {empNumber}
</div>
);
}
}
function mapStateToProps(state){
return {
Empnumber: state.Empnumber
}
}
export default connect(mapStateToProps)(EmpDetail);
I am successfully able to get the props value in render but could not in function.I have tried a lot to to create a method and pass the props to the method and get the props value but could not. Please help me out.
The problem is that you are not binding your function and hence in the function this refers to the context of the function. Also componentDidMount or componentWillMount will be executed only once and hence will contain only the inintial value
Second function
class EmpDetail extends React.Component {
constructor(props) {
super(props);
this.handleProp=this.handleProp.bind(this);
}
componentWillReceiveProps(nextProps) {
this.handleProp(nextProps);
}
handleProp(props) {
console.log('In function' + props.Empnumber);
return props.Empnumber;
}
render() {
const empNumber = this.props.Empnumber;
return (
<div className="container">
Empnumber = {this.props.Empnumber}
</div>
);
}
}
function mapStateToProps(state){
return {
Empnumber: state.Empnumber
}
}
export default connect(mapStateToProps)(EmpDetail);

Resources