Saving incoming multiple data in list in React.js - javascript

I would really appreciate if you could help me with that one. Namely, I have a parent component and it gets new data from a child component. In child component array-data is mapped so incoming data into parent component is not one but multiple. I would like to save it inside the list tags to get as many list items as incoming values from child component.
Here's the code:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: '',
};
}
updateParent(val) {
this.setState({
incomingData: <li> {val} </li> // here I would like to save every incoming mapped data
});
}
render() {
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={(val) => this.updateParent(val)}
/>
<ul className="listContainer">
{this.state.incomingData}
</ul>
</div>
);
}
}
export default Terminal;

I don't really like to store React Elements (created with <li> in a jsx file) as state. I'd rather store the list, and leave how it is rendered to the render() function. I would change it to something like:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: [],
};
}
updateParent(val) {
this.setState({
incomingData: this.state.incomingData.concat(val)
});
}
render() {
const listItems = this.state.incomingData.map(item => {
return <li>{ item }</li>
});
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={this.updateParent}
/>
<ul className="listContainer">
{listItems}
</ul>
</div>
);
}
}
export default Terminal;
Notice that I also pass this.updateParent directly instead of wrapping it in an anonymous function which should give the same result. You'll get a key warning from react, but I don't know what unique identifier you have access to.

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.

Mutating child components based on parent component's state, without re-rendering

Goal
I'm trying to manage mouseenter and mouseleave events from a parent component, for a collection of child components that keep getting re-rendered.
I'm building a reusable component for the collection of listings, that does several things like pagination, and a few other things when a listing is hovered.
So to make this reusable, I have to maintain the state of the hovered listing from the parent CollectionComponent, and mutate each individual listing component based on the state of the parent.
Code
Here are the components I'm using (I stripped them all down to their most basic forms):
Listings Component:
import React from 'react'
import $ from 'jquery'
import CollectionComponent from './CollectionComponent'
import Listing from './Listing'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {
listings: this.props.listings,
}
}
render() {
return (<section className="listing-results">
{this.state.listings.map( listing =>
<CollectionComponent results={this.state.listings} IndividualResult={Listing} perPage={this.props.perPage} options={options}/>
)}
</section>)
}
}
Collection Component:
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
componentDidMount() {
this.$listings = $('.result-card')
$(this.$listings).mouseenter(this.toggleInfoIn).mouseleave(this.toggleInfoOut)
}
toggleInfoIn = e => {
var { target } = e
var infoId = $(target).data('id')
this.setState({hoveredId: infoId})
}
toggleInfoOut = e => {
this.setState({hoveredId: null})
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} result={result} options={options}/>
)}
</div>
)
}
}
Individual Listing Component:
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props)
}
render() {
const { listing, hoveredId } = this.props
return (
<div className="result-card" data-id={listing.id}>
<div className={hoveredId === listing.id ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
I know I can probably structure the CollectionComponent a little cleaner with a higher order component, but I'll leave that for refactoring later once I get it working properly with this basic setup.
Problem
My problem is that every time I hover and change the state of the parent component, it re-renders the child components, because their props are dependent on the parent's state. Once this happens, the reference to my jQuery collection of listings is no longer valid. So the mouse events are attached to old DOM elements that no longer exist.
How can I structure this differently, so that either:
the child elements' props update without re-rendering, or
the jQuery collection reference doesn't change
I'd really like to avoid getting a new the jQuery collection every time the component updates.
The behavior of hover should be confined to the individual listing component and not the Collections component.
As the Collections component maintains the state of currently hovered item, it is good idea to pass an handler as part of props and then render the list again based on the change in state set by the Collections component.
Use react based event handlers where ever necessary which makes it for a controlled component. It is not a good idea to put state in the DOM where react can take care of it for you.
Listings
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}
onMouseEnter() {
this.props.onMouseEnter({ listingId: this.props.listing.id });
}
onMouseLeave() {
this.props.onMouseLeave();
}
render() {
const { listing, hoveredId } = this.props
const listingId = listing.id;
const isHovered = this.props.hoveredId === listing.id;
return (
<div className="result-card" onMouseEnter={this.onMouseEnter} onMouseLeave={onMouseLeave}>
<div className={isHovered ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
Collections
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
onMouseEnter({ listingId }) {
this.setState({ listingId });
}
onMouseLeave() {
this.setState({ listingId: null });
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} hoveredId={this.state.hoveredId} result={result} options={options} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}/>
)}
</div>
)
}
}

React is not changing the state of the parent element

I'm building simple todo app in react and I have made input field as part of inputForm element which is child element.
I can pass functions as props from parent to child without problem, but I can't update parent state to store value on input field. When I type in input field, passed function is executing normally but currentTodo state is not updating.
I have found that this problem can be avoided by using single data flow pattern (like Flux or Reflux) but as this is my first project I want to understand how to work with basics.
Code for parent element:
import React, { Component } from 'react';
import './App.css';
import InputForm from '../components/InputForm'
import {Task} from '../components/Task'
class App extends Component {
constructor(){
super();
this.state = {
tasks: ["Todo", "Toda"],
currentToDo: "",
};
}
//makes copy of task array, pushes current to do to copy and setsState
//with new values
addTodo = () => {
console.log("addTodo")
let copy = this.state.tasks.slice();
console.log(this.state.currentToDo)
copy.push(this.state.currentToDo);
this.setState({tasks: copy});
}
//gets input value from input field and updates current todo
onInputChange = e => {
console.log(e.target.value);
this.setState({ currentTodo: e.target.value })
}
render() {
let drawTask = this.state.tasks.map(e => {
return <Task todo={e}/>
})
return (
<div className="container">
<InputForm onInputChange={() => this.onInputChange} add={this.addTodo}/>
{drawTask}
</div>
);
}
}
export default App;
Code for child element:
import React, { Component } from 'react';
import './component.css';
import {AddButton} from './Buttons.js'
class InputForm extends Component{
constructor(){
super();
this.state = {
}
}
render(){
return(
<div className='taskHeader'>
{/*Value of current todo is send as props from parent element*/}
<input value = {this.props.currentToDo} onChange={this.props.onInputChange()} type="text"/>
<AddButton add = {this.props.add}/>
</div>
)
}
}
export default InputForm;
You are calling the function during the render rather than passing a reference.
Parent owns the function and needs to pass it to the child:
<InputForm onInputChange={this.onInputChange} add={this.addTodo}/>
Now that the child has a prop called onInputChange, you pass it to the onChange callback as a reference.
<input value={this.props.currentToDo} onChange={this.props.onInputChange} type="text"/>

Passing data from child to parent in React

Hello I am trying to implement a very simple functionality that would update my state based on the value passed into a function. The function is declared in my parent component, is passed to my child component via props and it is being called on a child component.
I keep getting this error on the console:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
Here is my code:
Parent component
import React, {Component } from "react";
import Sidebar from './Sidebar';
import Content from './Content';
class Tabs extends Component {
constructor(props){
super(props);
this.state={
message:'Select a name from the tabs menu'
};
this.handleName = this.handleName.bind(this);
}
componentWillMount () {
if ('pluginLoaded' in window) {
(window).pluginLoaded('tabs', function (port: any, context: any) {
// Future work should interact with the message channel here
});
}
}
handleName(value){
if (value === 'Vanessa'){
console.log(`${value} in da house `)
this.setState({
message: 'Vanessa means "butterfly"'
})
}
}
render() {
return (
<div className="Tabs">
<Sidebar
handleName = {this.handleName}
/>
<Content
message = {this.state.message}
/>
</div>
)
}
}
export default Tabs;
Child component
import React from 'react';
const Sidebar = (props) =>{
let Vanessa= 'Vanessa';
let Paola = 'Paola';
return(
<div className="Sidebar">
<h1>Tabs</h1>
<ul>
<li><a onClick={props.handleName(Vanessa)}>Vanessa</a></li>
<li><a onClick={props.handleName(Paola)}>Paola</a></li>
</ul>
</div>
)
}
export default Sidebar;
Instead of:
<li><a onClick={props.handleName(Vanessa)}>Vanessa</a></li>
try:
<li><a onClick={() => props.handleName(Vanessa)}>Vanessa</a></li>

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

Categories

Resources