Array of components not rerendering when new ones added - reactjs - javascript

So I have this code:
import React, { Component } from 'react';
import StartMultiple from './Start.multiple.js';
export default class Start extends Component {
constructor()
{
super();
this.state = {count: 4};
this.count = 4;
this.repeats = [
<StartMultiple key="1"/>,
<StartMultiple key="2"/>,
<StartMultiple key="3"/>
];
}
add_repeat(e)
{
this.repeats.push(<StartMultiple key={this.count}/>);
this.count = this.count + 1;
console.log(this.repeats);
}
render()
{
var count = 1;
return(
<div>
{this.repeats}
<button onClick={ () => this.add_repeat(event)}>clickable</button>
</div>
);
}
}
However, whenever I press the button, the component is definitely added in (as shown from the console.log) however the dom does not rerender the new ones. Am I missing something?

It is because you are not using state.. Instead you are creating your own variable...
Put repeates variable inside state.. and use this.setState to set new values

Related

How to update state of a component through a button click in another component?

I have 2 components in my react application. On first time page load, the first component is supposed to make a query and display data(buttons) accordingly. The state of second component till now is empty. When the user clicks on any of the button, another request should be made to the sever and state of the second component should be changed and should be reflected on the web page.
These are my files..
Apps.js
import React, { Component } from 'react';
import './App.css';
import OrgList from "./orgList"
import OrgDetails from "./orgDetails"
class App extends Component {
render() {
return [
<OrgList/>,
<OrgDetails/>
];
}
}
export default App;
orgList.js
import React, { Component } from 'react'
import OrgDetails from "./orgDetails"
var posts =[]
class OrgList extends Component {
constructor(props){
super(props);
this.state={
mainpost: [],
devices:[],
}
}
componentDidMount(){
fetch(someURL)
.then(res => res.json())
.then(function (data){
for (let i = 0; i < 3; i++){
posts.push(data.orgs[i].name)
}
}).then(mainpost => this.setState({mainpost:posts}));
}
render() {
var token =new OrgDetails();
const postItems =this.state.mainpost.map((post) => (
console.log(post),
<button
data-tech={post}
key={post}
className="org-btn"
onClick={() => token.dispatchBtnAction(post)}
>
<h3>{post}</h3>
</button>
)
)
return (
<div>
<h3> Organisations!!!! </h3>
<h5>{postItems}</h5>
</div>
)
}
}
export default OrgList;
orgDetails.js
import React, { Component } from 'react'
var list =[]
const orgname = org =>
`someURL/${org}`
class OrgDetails extends Component {
state={
devices:[],
}
constructor(props){
super(props);
this.state={
devices: [],
}
this.dispatchBtnAction=this.dispatchBtnAction.bind(this)
}
dispatchBtnAction=(str) => {
list =[]
fetch(orgname(str))
.then(res => res.json())
.then(function (data){
for (let i = 0; i < 3; i++){
//console.log("123")
list.push(data.devices[i].location)
console.log(list)
}
}).then(devices => this.setState({
devices : list,
}));
}
render() {
const devices=this.state.devices.map((dev,i)=>(
<div key={dev}>
<li>{dev}</li>
</div>
))
return (
<div>
<p>{devices}</p>
</div>
)
}
}
export default OrgDetails;
But I am getting this warning...
Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the OrgDetails component.
Because of this, the state is not getting changed and the component is not rerendering.
How to eliminate this warning and if any better method is there please do suggest.
As these 2 component are not parent-child components, perhaps you should implement all the logic in the App and than pass state-handlers as props to each component.
Then your components will look something like this:
class App extends Component {
state = { clicks: 0 }
incrementState = () {
const prev = this.state.clicks;
this.setState({ clicks: prev + 1 })
}
render() {
return [
<DisplayComponent counter={this.state.clicks} />,
<ControlComponent onIncrement={this.incrementState} />
];
}
}
Component that displays state
class DisplayComponent extends Component{
render() {
return (<h3>this.props.counter</h3>);
}
}
Component that handles state
class ControlComponent extends Component {
render() {
return (<button onClick={this.props.onIncrement}>click me</button>)
}
}
Well the whole issue is this line var token =new OrgDetails(); This just creates the object. But doesn't mount it in the DOM. It also doesn't reference to the component <OrgDetails/> created in App. So when you try to use token.dispatchBtnAction(post), you are trying to setState on a component that is not mounted in the DOM, hence the error.
This is a really questionable way of making communication in between two components. You are better off using a Parent-Child relationship in between component. Also you can have a look at making Presentational Component and Container components differentiation to make the workflow easy. Have a read at the this link.

Move function in React from component to referenced library

I'm learning React and I'm not sure how to setup this pattern. It could be something really easy I'm just missing.
I have a main component that controls state. It has all of the functions to update state and passes these down to child components via props. I've simplified the code to focus on one of these functions.
Here's the component now, all works as it should:
ManageMenu.js
import React from 'react'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = this.toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
render() {
return (
...
)
}
}
export default ManageMenu
The toggleEditing is passed via props to a child component that uses it to render an editing form if the edit button is clicked.
I have about 10 of these different functions in this component and what I would like to do is move them to an external lib/methods.js file and then reference them. Below is the code I would like to have, or something similar, but React doesn't like what I'm doing. Throws a syntax error:
Failed to compile.
Error in ./src/components/ManageMenu.js
Syntax error: Unexpected token
toggleEditing(id, menuSectionId, this.state, this)
Here is what I would like to do...
lib/methods.js
const toggleEditing = function(id, state, that) {
const menuSections = state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
that.setState({ menuSections })
}
module.exports = {
toggleEditing
}
And then in my component:
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu
Any help is appreciated, thanks!
Thanks to #Nocebo, the answer on how to externalize functions is here:
Externalise common functions in various react components
In my particular situation,
I need to remove the “floating” toggleEditing(id, this.state, this) call in the middle of nowhere. Update: This error happens “because it is invoking a method within a class definition.” (see Pineda’s comment below)
Remove the leading this. on the right side of the this.toggleEditing statement in constructor()
Update the function in lib/methods.js to remove the state and that variables since its bound to this in the constructor()
See updated code below.
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
render() {
return (
...
)
}
}
export default ManageMenu
lib/methods.js
const toggleEditing = function(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
module.exports = {
toggleEditing
}
You're error arises because you are invoking toggleEditing in your ManageMenu.js class definition rather than defining a function.
You can achive what you want by setting a local class member this.toggleEditing to the bound function returned by the .bind method and do so within the constructor:
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.state = {
menuSections: []
}
// bind external function to local instance here here
this.toggleEditing = toggleEditing.bind(this);
}
// don't invoke it here, bind it in constructor
//toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu

ReactJS - Updating parent state when child state is changed

I apologize for the lack of working code, but I'm not sure how to go about doing this, so non-working code it is. I am looking to update the this.state.count in the App class when the state of a ToggleBox is altered. I'm sure this has been asked before, thanks in advance.
import React, { Component } from 'react';
import ToggleBox from '../components/ToggleBox';
class App extends Component {
constructor(props) {
super(props);
this.state = {
total : 60,
count: 0
};
}
getToggles() {
let toggles = [];
for (let i = 0; i < this.state.count; i++) {
toggles.push(<ToggleBox checked={false} key={i} />);
}
return toggles;
}
render() {
let toggles = this.getToggles();
return (
<div className="App">
{{this.state.count}} - {{this.state.total}}
<div className="container-toggle-box">
{toggles}
</div>
</div>
);
}
}
export default App;
...and the component:
import React, {Component} from 'react';
class ToggleBox extends Component {
constructor(props) {
super(props);
this.state = {
active = this.props.checked
};
this.handleClick= this.handleClick.bind(this);
}
handleClick() {
this.setState({active: (this.state.active) ? false : true}
}
render() {
let mark = (this.state.active) ? 'x' : 'o'
return (
<span>
{mark}
</span>
);
}
}
export default ToggleBox;
You need to pass ToggleBox a function that updates the count.
For example:
toggles.push(<ToggleBox
checked={false}
key={i}
incrementCount={() => this.setState({count: this.state.count + 1})}
/>);
Then you just call that method in your child component:
handleClick() {
this.setState({active: (this.state.active) ? false : true};
this.props.incrementCount();
}
This pattern is often referred to as "Flux" (or, to be more accurate, it's a piece of the overall Flux pattern), and it's a core part of how React was designed to be used. By passing the function in in this way your child component doesn't have to know anything about how count works or how it's incremented. This makes things easy for the child, but more importantly it makes it a lot easier when you want to change how the count works, because there's only a single place (the parent component) which controls it.

Is there a React lifecycle method to do something only when component receive props the first time?

I'm new to React so thank you for your patience in advance. Also using Redux.
I have a list of content pulled from the API, I display the text and a hidden text box and on a state change associated that alternates the visibility of the two. Essentially user can click on the text and edit the text, achieved by inverting the boolean and swapping the display. They can then save it and PUT to server etc.
Since my list length varies, I must initialize a number of state.isVisible[n]. equivalent to the number of content being displayed each time. This number must be counted, after the props come in. I am using Redux so the content is retrieved, stored, then given to props. It's done as the following:
constructor(props){
super(props);
this.state = {
isVisibleObj: {}
}
}
componentWillReceiveProps(){
const { isVisibleObj } = this.state
// set visibility of text box
let obj = {}
Object.keys(this.props.questions).forEach(key => obj[key] = false)
this.setState({isVisibleObj: obj})
}
My initial implementation was that in componentWillReceiveProps I do all the setState() to initialize the isVisible properties to a boolean.
The challenge I am having with this implementation is that, if a user open up multiple items for edit, and if she saves one of them, the PUT request on success would send back the edited content, now updating the store and props. This will trigger componentWillReceiveProps and reset all the visibilities, effectively closing all the other edits that are open.
Any suggestion on how to proceed?
I think you should make two components
List (NamesList.react)
import React, {PropTypes} from 'react';
import NameForm from './NameForm.react';
import Faker from 'Faker'
export default class NamesList extends React.Component {
constructor(){
super();
this.addItem = this.addItem.bind(this);
}
addItem(){
var randomName = Faker.name.findName();
this.props.addName(randomName);
}
render() {
let forms = this.props.names.map((name,i) => {
return <NameForm updateName={this.props.updateName} index={i} key={i} name={name} />
});
return (<div>
<div>{forms}</div>
<button onClick={this.addItem}>Add</button>
</div>);
}
}
NamesList.propTypes = {
names: PropTypes.arrayOf(PropTypes.string).isRequired
};
Form (NameForm.react)
import React, {PropTypes} from 'react';
export default class NameForm extends React.Component {
constructor(props) {
super(props);
this.updateName = this.updateName.bind(this);
this.state = {
showTextBox:false
}
}
updateName(){
this.setState({showTextBox:false});
this.props.updateName(this.props.index,this.refs.name.value);
}
render() {
if(this.state.showTextBox){
return (<div>
<input ref="name" defaultValue={this.props.name} />
<button onClick={this.updateName}>Save</button>
</div>);
}
return (<div onClick={() => {this.setState({showTextBox: !this.state.showTextBox})}}>
{this.props.name}
</div>);
}
}
NameForm.propTypes = {
name:PropTypes.string.isRequired
};
Invoke (App.js)
import React, { Component } from 'react';
import NamesList from './NamesList.react';
class App extends Component {
constructor(){
super();
this.addName = this.addName.bind(this);
this.updateName = this.updateName.bind(this);
this.state = {
names:['Praveen','Vartika']
}
}
addName(name){
let names = this.state.names.concat(name);
this.setState({
names: names
});
}
updateName(index,newName){
let names = this.state.names.map((name,i) => {
if(i==index){
return newName
}
return name;
});
this.setState({names:names});
}
render() {
return (
<NamesList names={this.state.names} updateName={this.updateName} addName={this.addName} />
);
}
}
export default App;
Now if your store changes after user saves something. React wont re-render Child component that didn't change

Reactjs, parent component, state and props

I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.

Categories

Resources