React ref state is null - reactjs

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!

Related

calling function in React SetState gives error that userName is unlabelled why?

import React,{Component} from 'react'
class Formhandler extends Component {
constructor(props) {
super(props)
this.state = {
userName:""
}
}
changer=(event)=>{
this.setState(()=>{
userName : event.target.value
})
}
render()
{
return(
<div>
<label>UserName</label>
<input type="text" value={this.state.userName} onChange={this.changer}/>
</div>
)
}
}
export default Formhandler
You are getting the error because of invalid syntax.
Update changer function
changer = (event) => {
this.setState({ userName: event.target.value });
};
You need to return an object inside the setState function but you are not that's the source of issue(syntax error).
use a function inside setState when your new state value would depend on your previous state value, where the function passed inside the setState will receive previous state as argument
changer = (e) => {
this.setState((prevState) => ({
userName : e.target.value
})
);
}
pass an object to update the state, use this when it doesn't depend on your previous state value.
changer = (e) => {
this.setState({ userName: e.target.value });
};
import React from "react";
class Formhandler extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: "",
};
}
changer(event) {
this.setState(() => ({
userName: event.target.value,
}));
}
render() {
return (
<div>
<label>UserName</label>
<input
type="text"
value={this.state.userName}
onChange={this.changer.bind(this)}
/>
</div>
);
}
}
export default Formhandler;
It will work, compare your version and this

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

How to setProps in ReactJS

I have 2 Components one called NodeWidget and another called PopupWidget. In the NodeWidget it has a Model assigned to it which looks like the following:
PopupModel
export class PopupModel {
question: string;
model: string;
constructor(question: string, model: string) {
this.question = question;
this.model = model;
}
}
The parent Component is NodeWidget which passes in the Model to the PopupWidget with data in.
NodeWidget
{ this.state.showComponent ?
<PopupWidget model={this.props.popupModel} /> :
null
}
Then finally in the child Component we have this code:
export interface PopupWidgetProps {
model: PopupModel;
}
export interface PopupWidgetState { }
export class PopupWidget extends React.Component<PopupWidgetProps, PopupWidgetState> {
constructor(props: PopupWidgetProps) {
super(props);
this.state = { };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.props);
}
render() {
return (
<div className="popup">
<div className="popup_inner">
<h1>TEST</h1>
<input type="text" value={this.props.model.question} placeholder="Write a question..." />
<button onClick={this.handleClick}>close me</button>
</div>
</div>
);
}
}
I want to be able to bind the value of the input to the model and then for it to update the original model in the Parent Component, am i doing this correctly as it does not seem to work.
You can do this to pass the input result to parent component on the button click:
PopupWidget :
export class PopupWidget extends React.Component<PopupWidgetProps, PopupWidgetState> {
constructor(props: PopupWidgetProps) {
super(props);
this.state = { question: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.inputResult(this.state.question)
}
render() {
return (
<div className="popup">
<div className="popup_inner">
<h1>TEST</h1>
<input type="text" value={this.state.question} onChange={(question) => { this.setState({ question })}} placeholder="Write a question..." />
<button onClick={this.handleClick}>close me</button>
</div>
</div>
);
}
}
NodeWidget :
constructor(props) {
super(props);
this.getInputResult = this.getInputResult.bind(this);
}
getInputResult(question) {
this.props.inputResult(question);
this.setState({ showComponent: false });
}
...
{ this.state.showComponent ?
<PopupWidget inputResult={this.getInputResult} /> :
null
}
Finally in PopupModel (i assume this is a react component, i don't know if you can work with simple es6 class in react):
export class PopupModel extends React.Component {
constructor(props) {
this.state = { question: '', model: '' }; // set your initial state
this.getInputResult = this.getInputResult.bind(this);
}
getInputResult(question) {
this.setState({ question }); // here's our result from the input
}
render(){
return(<NodeWidget inputResult={this.getInputResult} />);
}
}
This can be pretty boring to handle if you have multiple components between the two which have to communicate.
You can use a HOC like Redux or MobX to handle an app state that can be passed in any component, and any component can dispatch actions to update the app state, you should go for it if you have multiple cases like this.

Why does the received state disappear in a moment?

I'm trying to dispatch actionC from TestClass so that Labelclass can receive the state change from reducer as below.
TestClass
class Test extends React.Component {
constructor(props){
super(props)
this.state = {text:props.text,onClick:props.onClick}
this.onInput = this.onInput.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onInput(e){
this.setState({text:e.target.value})
}
onSubmit(e){
this.state.onClick(this.state.text)
}
render(){
return <div>
<form onSubmit={this.onSubmit}>
<input value={this.state.text} onChange={this.onInput} />
<button type="submit">Add Todo</button>
</form>
</div>
}
}
function mapDispatchToProps_Test(dispatch,ownProps){
return {onClick:(id)=>dispatch(actionC(id))}
}
Test.propTypes = {
text:PropTypes.string.isRequired,
onClick:PropTypes.func.isRequired
}
Test = connect(null,mapDispatchToProps_Test)(Test)
LabelClass and Entry
class Label extends React.Component {
constructor(props){
super(props)
this.state = {text:props.text}
}
render(){
return <div> Hello<label>{this.props.text}</label></div>
}
}
function mapStateToProps_Label(state,ownProps){
return {
text:state.text
}
}
Label = connect(mapStateToProps_Label)(Label)
Label.propTypes = {
text:PropTypes.string.isRequired
}
const App = () =>(
<div>
<Test text="" onSubmit onClick />
<Label text=""/>
</div>
)
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
Action and Reducer
const CDD_TODO = 'CDD_TODO'
const {PropTypes} = React;
const {createStore} = Redux;
const { Provider, connect } = ReactRedux;
let store = createStore(reducer)
//action
function actionC(text) {
console.log(CDD_TODO)
return { type: CDD_TODO, text:text }
}
function reducer(state = {}, action) {
switch (action.type) {
case CDD_TODO:
console.log("action",action.text)
return Object.assign({}, state, {
text:action.text
})
default:
return state
}
}
The trouble is the output from LabelClass render() becomes invisible at once
after displayed in a moment.
I want it not to disappear. What is the cause?
You didn't map the value text from your reducer you created but you mapped the reducer it self. In your case, you have to map the text value from the reducer named text:
function mapStateToProps_Label(state,ownProps){
// state.text is the state of your reducer
// state.text.text is one of the state value
return {
text:state.text.text
}
}
Besides, from what I see, you needn't a state in Label:
class Label extends React.Component {
render(){
return <div> Hello<label>{this.props.text}</label></div>
}
}
Same thing in Test: for onClick on this.state is useless:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = { text: props.text }
this.onInput = this.onInput.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onInput(e) {
this.setState({ text: e.target.value });
}
onSubmit(e) {
this.props.onClick(this.state.text);
}
render() {
return <div>
<form onSubmit={this.onSubmit}>
<input value={this.state.text} onChange={this.onInput} />
<button type="submit">Add Todo</button>
</form>
</div>;
}
}
I think you should put a breakpoint in mapStateToProps to see if text si modified after having been set. You should put a break point in the reducer to see if an action dispatch an action that erase the text data.

Props in state anti pattern

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

Resources