React.js - componentWillReceiveProps only updates every other this.props update - javascript

I am building an app with React v0.14.6 . The desired functionality is to click on the GoalItem to show the ModalItem. The goal-item has an 'a' tag with attribute onClick that sets {this.state.showModal: true}. The ModalItem is passed GoalItem's this.state.showModal via a showModal attribute.
To change the state of the ModalItem component such that this.state.showModal: this.props.showModal, I use setState() in componentWillReceiveProps.
The strange behavior is that the 'a' tag in GoalItem needs to be clicked twice to make the modal appear.The goal is for just one click to suffice.
Thank you in advance for your help. Below is the relevant code.
//goal-item.jsx
var React = require('react');
var ModalItem = require('./modal-item');
module.exports = React.createClass({
getInitialState() {
return {
name: this.props.goal.name,
nameChanged: false,
showModal: false
}
},
open() {
this.setState({ showModal: true });
},
render() {
return <a className="list-group-item"
key={this.props.goal.id}
onClick={this.open}>
<ModalItem goal={this.props.goal} showModal={this.state.showModal} />
</a>
}
});
//modal-item.jsx
var React = require('react');
var Modal = require('react-bootstrap/lib/modal');
var Button = require('react-bootstrap/lib/button');
module.exports = React.createClass({
getInitialState() {
return {showModal: false };
},
componentWillReceiveProps() {
this.setState({ showModal: this.props.showModal });
},
close() {
this.setState({ showModal: false });
},
render() {
return (
<div>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>{this.props.goal.name}</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>{this.props.goal.description}</p>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
</Modal.Footer>
</Modal>
</div>
);
}
});

In the componentWillReceiveProps you will get new props as argument.
So it should be:
componentWillReceiveProps(newProps) {
this.setState({ showModal: newProps.showModal });
}
Basically this is a place where you can compare new props from argument with old prop by accessing them using this.props, and then perform needed updates of state.
See documentation: componentWillReceiveProps

Related

Object is null in JSX tag

So I'm writing an application with a spring boot backend and react.js frontend. I am having an extremely annoying, and basic problem with react, and I'm not a very experienced JS developer...I'm a Java dev.
render() {
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>{libs}</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
I get the following output:
{
"libs": [
{
"name": "videos",
"library": null
}
]
}
So then I add
console.log(this.videoAreaData.libs)
and get this following output
[
{
"name": "videos",
"library": null
}
]
So to test printing the first element I should use this.videoAreaData.libs[0] obviously right? Apparently not
TypeError: this.videoAreaData.libs is undefined[Learn More]
What I want to do is iterate over the array in my JSX code using .map, but the object is literally always undefined! I've tried using setState with a const and all kinds of stuff! The data is passed from my parent app.js class which uses this:
componentDidMount() {
this.setState({isLoading: true})
libraryAccessor.listLibraries().then(libs => {
this.setState({ isLoading: false, show: false, libraries: libs })
})
}
libs is then pasted as a parameter into my code using a property like this
<LibraryLister libs={libraries} ref="libraries"></LibraryLister>
then it goes to the constructor of LibraryLister here
videoAreaData
constructor(videoAreaData) {
super(videoAreaData)
this.videoAreaData = videoAreaData
}
Now I assume all of that is done correctly, as it's non-null in my render method. If I put console.log(videoAreaData) within the JSX tags in a {} it's not null either, so it's definitely not supposed to be!
Here is what I finally want to do:
<Modal.Body>
{(this.videoAreaData.libs.map((library)=> {
return <p className="libname"> library.name </p>
}))}
</Modal.Body>
I feel like I'm doing something very very wrong here. That being said I have another project using the exact same stack, but made in typescript, and it works fine doing almost exactly this. Typescript is super irritating to use though, so I'd uh...prefer not to. Thanks in advance for any help
EDIT: Full code https://pastebin.com/daipM2gY
Also this alternate version of my render method prints the data as expected, so it...should not be undefined
render() {
console.log("PRINTING VID DATA: "+this.videoAreaData)
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
{console.log("PRINTIN BODY")}
{console.log(this.videoAreaData)}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
Output:
PRINTIN BODY
{
"libs": [
{
"name": "videos",
"library": null
}
]
}
So I uh figured out what I was doing wrong actually. Nothing to do with the state (the isLoading stuff on componentLoad worked fine as it did in another project) but the POJO I was sending.
So here's what happened: this.state.libraries[x] was in fact undefined, because this.state.libraries was actually an object. Containing another object called libraries which was the array from my POJO.
My POJO I was sending had "libraries" as the root object with an array inside of it. It was then being packed into the state as libraries, and so I had to use this.state.libraries.libraries[x]
Really stupid problem, but glad it's solved.
Thanks for your help guys!
Final code for those interested:
import React from 'react';
import { Modal, Button } from 'react-bootstrap';
import LibraryAccessor from '../accessors/LibraryAccessor'
import './navigator.css'
var libraryAccessor = new LibraryAccessor()
var librarylist = []
export default class LibraryLister extends React.Component {
state = { loadModal: false, libs: [] }
constructor(props) {
super(props)
this.state = {
...this.state,
libs: props
}
}
render() {
console.log(this.state.libs.libs[0])
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.state.libs.libraries.map(item=> {
console.log("ITEM")
})}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
close = () => {
this.setState({ ...this.state, show: false });
}
open = () => {
this.setState({ ...this.state, show: true });
}
}
You have to study react
React’s props don’t work your post.
First you have to edit your videoAreaData constructor.
constructor(props) {
super(props)
this.videoAreaData = this.props.videoAreaData;
}
I recommend to read props in http://reactjs.org/docs
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { Button, Popover, Tooltip } from 'react-bootstrap';
// My components
import Navigator from './components/Navigator';
import LibraryLister from './components/LibraryLister';
import LibraryAccessor from './accessors/LibraryAccessor';
var loadingChildren = {};
var libraryAccessor = new LibraryAccessor();
var jsAccessor = new LibraryAccessor();
export default class App extends Component {
// React Component constructor parameter is `props`
constructor(props) {
super(props);
this.state = {
isLoading: true
}
}
open = () => {
this.refs.navigator.open()
}
openLibs = () => {
this.refs.libraries.open();
}
setLoading(refName, value) {
loadingChildren[refName].delete(refName);
if (loadingChildren.size < 1) {
// this.state = { isLoading: value }
// if you change state, you have to use setState; it's very very important.
// setState function is asynchronous because it relate performance.
this.setState({
isLoading: value
})
}
}
render() {
const { isLoading, libraries } = this.state;
console.log(libraries) // check please~!
if(isLoading) return <pre>Loading...</pre>
return (
<div className="App">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Your Server</h1>
</header>
<Button onClick={this.open}>Show Navigator</Button>
<Button onClick={this.openLibs}>Show Libraries</Button>
<Navigator ref="navigator"></Navigator>
<LibraryLister libs={libraries} ref="libraries"></LibraryLister>
</div>
);
}
getLibraries = (libraries) => {
this.setState({
isLoading: false,
libraries
});
}
componentDidMount() {
libraryAccessor
.listLibraries()
.then(this.getLibraries);
}
}
import React, { Component } from 'react';
import { Modal, Button } from 'react-bootstrap';
import LibraryAccessor from '../accessors/LibraryAccessor';
import './navigator.css';
var libraryAccessor = new LibraryAccessor();
var librarylist = [];
export default class LibraryLister extends Component {
constructor(props) {
super(props);
this.state = {
loadModal: false
};
this.videoAreaData = props.videoAreaData
};
render() {
console.log("PRINTING VID DATA: "+this.videoAreaData)
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
close = () => {
this.setState({ ...this.state, show: false });
}
open = () => {
this.setState({ ...this.state, show: true });
}
}

How to change this.props in child when parent state changes in React JS?

new to React JS here. I have a setup where I an App parent component that has a initialFormInput state holding a recipe object. A function is passed from the parent component to a child component called EditRecipeButton that can change this state to a specific recipe that is called from an edit button.
This state in the parent is mapped to a state in a child AddRecipe component with a via this.props and my reasoning is that whenever the parent component state changes this state in AddRecipe changes as well. But that doesn't happen, what am I doing wrong here?
Here is my code:
var App = React.createClass({
getInitialState(){
return{
showModal:false,
recipeKeys: [ ],
recipes: [ ],
initialFormInput: {name: "", ingredients: []}
}
},
open: function(){
this.setState({showModal:true});
},
close: function(){
this.setState({showModal:false});
},
editRecipe: function(recipe){
console.log(recipe);
this.setState({initialFormInput: recipe}, function(){
this.open();
});
},
render: function(){
return(
<div className="container">
<h1>Recipe Box</h1>
<RecipeList recipes = {this.state.recipes} deleteRecipe = {this.deleteRecipe} editRecipe={this.editRecipe} />
<AddRecipeButton openModal = {this.open}/>
<AddRecipe closeModal = {this.close} showModal={this.state.showModal} addRecipeKey = {this.addRecipeKey} initialFormInput = {this.state.initialFormInput}/>
</div>
)
}
var RecipeList = function (props) {
return (
<ul className="list-group">
{
props.recipes.map( (item,index) => <RecipeItem recipe={item} deleteRecipe = {props.deleteRecipe} editRecipe={props.editRecipe}/> )
}
</ul>
);
};
var RecipeItem = React.createClass({
getInitialState: function(){
return {displayIngredients: false}
},
toggleRecipe: function() {
this.setState({displayIngredients: !this.state.displayIngredients})
},
render: function() {
return(
<li className="list-group-item" >
<h4 onClick={this.toggleRecipe}>{this.props.recipe.name}</h4>
<div style={{display: this.state.displayIngredients ? 'block' : 'none'}}>
<h5 className="text-center">Ingredients</h5>
<hr/>
<ul className="list-group" >
{this.props.recipe.ingredients.map((item) => <IngredientItem ingredient={item} />)}
</ul>
<ButtonToolbar>
<DeleteRecipeButton deleteRecipe = {this.props.deleteRecipe} recipeName={this.props.recipe.name}/>
<EditRecipeButton editRecipe = {this.props.editRecipe} recipe={this.props.recipe}/>
</ButtonToolbar>
</div>
</li>
)
}
});
var IngredientItem = function(props){
return (
<li className="list-group-item">
<p>{props.ingredient}</p>
</li>
)
};
var EditRecipeButton = React.createClass({
render: function(){
return (
<Button bsStyle="default" bsSize="small" onClick={() => this.props.editRecipe(this.props.recipe)}>Edit</Button>
)
}
});
var AddRecipe = React.createClass({
//Form in modal to add recipe
getInitialState(){
return {
name: this.props.initialFormInput.name,
ingredients: this.props.initialFormInput.ingredients
};
},
getValidationStateName(){
var length = this.state.name.length;
if(length > 0) {
return "success";
} else {
return "error";
}
},
getValidationStateIngredients(){
var length = this.state.ingredients.length;
if(length > 0){
return "success";
} else {
return "error";
}
},
handleInput: function(key,e){
var input = e.target.value;
if(key === "ingredients"){
input = e.target.value.split(",");
}
var update = {};
update[key] = input;
this.setState(update, function(){
console.log(this.state);
});
},
handleSubmit(){
var recipe = JSON.stringify({name: this.state.name, ingredients: this.state.ingredients});
localStorage.setItem(this.state.name, recipe);
var recipeObject= JSON.parse(recipe);
this.props.addRecipeKey(recipeObject);
this.props.closeModal();
this.setState({name: "", ingredients: []});
},
render: function(){
return (
<div>
<Modal show={this.props.showModal} onHide={this.props.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Add a Recipe Here</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormGroup controlId="formNameText" validationState = {this.getValidationStateName()}>
<ControlLabel>Recipe</ControlLabel>
<FormControl
type="text"
placeholder="Give your recipe a name"
value={this.state.name}
onInput={this.handleInput.bind(this,'name')}
/>
<FormControl.Feedback />
</FormGroup>
<br/>
<FormGroup controlId="formIngredientsTextarea" validationState = {this.getValidationStateIngredients()}>
<ControlLabel>Ingredients</ControlLabel>
<FormControl
componentClass="textarea"
placeholder="Insert your ingredients, separated by a comma"
value={this.state.ingredients}
onInput={this.handleInput.bind(this,'ingredients')}
/>
<FormControl.Feedback />
<hr/>
</FormGroup>
<Button bsStyle="primary" onClick={this.handleSubmit}>Submit</Button>
</form>
</Modal.Body>
</Modal>
</div>
)
}
});
ReactDOM.render(<App />, document.getElementById('app'));
});
So this does not change when the parent state changes:
getInitialState(){
return {
name: this.props.initialFormInput.name,
ingredients: this.props.initialFormInput.ingredients
};
},
As the name suggests, getInitialState only provides the initial state of a component. Subsequent update won't trigger that function.
You need to to implement componentWillReceiveProps to update the state in when props change. From the docs:
componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
If you want to share state across components use redux isntead. Also maintain a separate file for each component.
This link might help you
Step by Step Guide To Building React Redux Apps

How to open or close a Modal defined in one Class from a Button defined in another Class

I'm writing a simple webapp with React and react-bootstrap. I have two Buttons that should open two different Modals. I want to separate the Class containing the Buttons from the two Modal Classes, e.g.
var React = require('react');
var ReactDOM = require('react-dom');
import { Button, Modal, closeButton } from 'react-bootstrap';
var Jumbo = React.createClass ({
render() {
return (
<div className="container">
<Button onClick={Modal1.open}>Modal1</Button>
<Button onClick={Modal2.open}>Modal2</Button>
<Modal1 />
<Modal2 />
</div>
);
}
});
var Modal1 = React.createClass ({
getInitialState() {
return {
showModal: false
};
},
close() {
this.setState({
showModal: false
});
},
open() {
this.setState({
showModal: true
});
},
render() {
return (
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Modal1</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Modal1</p>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
</Modal.Footer>
</Modal>
);
}
});
var Modal2 = React.createClass ({
getInitialState() {
return {
showModal: false
};
},
close() {
this.setState({
showModal: false
});
},
open() {
this.setState({
showModal: true
});
},
render() {
return (
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Modal2</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Modal2</p>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
</Modal.Footer>
</Modal>
);
}
});
ReactDOM.render(<Jumbo />, document.getElementById('modal'));
But onClick={ModalN.open} doesn't work. Normally I'd put the Button inside the Modal Class (as per the documented example) and do onClick={this.open}, but I want to put the two Button elements together, not in separate <div>s. What's the correct way to pass in the reference to the Modal?
onClick={modalN.open} wont work, you need to include the ModalN component in Jumbo:
var Jumbo = React.createClass ({
getInitialState() {
return {
activeModal: null
}
},
openModal(id){
this.setState({
activeModal: id
})
}
render() {
return (
<div className="container">
<Button onClick={this.openModal.bind(this,1)}>Modal1</Button>
<Button onClick={this.openModal.bind(this,2)}>Modal2</Button>
{this.state.activeModal === 1 ? <Modal1 /> :null}
{this.state.activeModal === 2? <Modal2 /> : null}
</div>
);
}
});
You also need to change the intial showModal of each Modal to true, or else they won't ever show (since open() is never called):
var Modal1 = React.createClass ({
getInitialState() {
return {
showModal: true
}
},
...
});

How do I dynamically change the content of a React Bootstrap modal?

I'm trying to change the content of the modal after it has mounted, but I'm not able to find the correct nodes to change. I've attached refs to the nodes I'm interested in and try to alter them in componentDidMount(). But the nodes are not found -- comes up as null.
var Modal = ReactBootstrap.Modal;
const MyModal = React.createClass({
getInitialState() {
return { showModal: false };
},
close() {
this.setState({ showModal: false });
},
open() {
this.setState({ showModal: true });
},
componentDidMount() {
var theNode = ReactDOM.findDOMNode(this.refs.bigPic);
var theOtherNode = ReactDOM.findDOMNode(this.refs.bigPicInfo);
theNode.src = 'http://big-pic.png';
theOtherNode.innerHTML = "<strong> Something here</strong>";
},
render() {
return (
<div>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title></Modal.Title>
</Modal.Header>
<Modal.Body>
<div><img ref="bigPic" src="" /></div>
</Modal.Body>
<Modal.Footer>
<p ref="bigPicInfo"></p>
</Modal.Footer>
</Modal>
</div>
);
}
});
ReactDOM.render(<MyModal/>, document.getElementById("my-modal"));
Dynamic content in React is driven by component state, the same way you're using this.state.showModal to dynamically make the modal appear or not. Anything that can possibly change should have a default setting in getInitialState, then call this.setState() with your new values.. this will trigger your component to re-render.
const MyModal = React.createClass({
getInitialState() {
return {
showModal: false,
bigPicSrc: '',
infoContent: ''
}
},
...
componentDidMount() {
this.setState({
bigPicSrc: 'http://big-pic.png'
infoContent: <strong>Something here</strong> // not a string, but a component
})
},
render() {
return (
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title></Modal.Title>
</Modal.Header>
<Modal.Body>
<div><img ref="bigPic" src={this.state.bigPicSrc} /></div>
</Modal.Body>
<Modal.Footer>
<p ref="bigPicInfo">{this.state.infoContent}</p>
</Modal.Footer>
</Modal>
)
}
})
I use node and react 16, before I was learn little more of Bootstrap, and now collecting my knowledge about bout react and bootstrap. On next away I make modal: First I am put CDN links with Bootstrap css and js, and jquery from index.html from public folder. Next make folder for components from SRC folder of ny app. Next step I put bootstrap code from new file example modal.js and change bootstrap class from clssName in React. And modal worked Klick on this text for show modal . And think for change content of modal must use data some data of Json file there You must connect id field of Json data and ref or id tigger event with you calling this modal. Ever different event calling Id field from data.json. I thing for that is best use switch case for easy choose.

Component props not updating in shallow render

I have a payment component and when I click a button, it should update the component state isOpen to true. This works fine in practice but when trying test it via enzyme, it wont update the prop.
The component looks like this:
class CashPayment extends Component {
state = {
isOpen: false
}
toggleModal = () => {
this.setState({ isOpen: true })
}
render() {
return (
<Mutation>
{() => (
<Fragment>
<Button
id="cash-payment-button"
onClick={this.toggleModal}
/>
<Modal
id="confirm-payment-modal"
isOpen={isOpen}
>
...
</Modal>
</Fragment>
)}
</Mutation>
)
}
}
So clicking #cash-payment-button should toggle the state isOpen which should open the modal.
In my test, I want to check the prop of my modal isOpen is set to true. But for some reason, the prop doesn't update in the test. However if I console log in my toggleIsOpen function, I can see the function gets called and the state updates.
My test is as so:
describe("Click Pay button", () => {
it("Should open confirm modal", () => {
Component = shallowWithIntl(
<CashPayment bookingData={bookingData} refetchBooking={refetchBooking} />
)
.dive()
.dive()
const button = Component.find("#cash-payment-button")
.props()
.onClick()
expect(Component.find("#confirm-payment-modal").prop("isOpen")).toEqual(true)
})
})
and the results are:
CashPayment › Click Pay button › Should open confirm modal
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
Why does the modal component props not update?

Categories