I am fairly new to React and was wondering if anybody could give me an insight on a problem I am stuck with.
Right now I have a parent(Hello.js) component and two children(Mixer.js and renderCont.js) at the same level.
I am trying to render a list in the Mixer.js and display its corresponding objects in the Hello.js through by passing the values into RenderCont.js. I've gotten to a point where nothing is displayed before I click on any of the list to pass on a object.
From here is where I am stuck: I want the first object of the list to be displayed as a default, at the same time bold the first in the list. And then execute the as I have below.
This is my first time posting a question on stackoverflow so I'm not sure if my question makes sense with the attached codes but I will greatly appreciate any kind of support.
Parent Hello.js:
import React, { Component } from 'react';
import RenderCont from './renderCont.js';
import Mixer from './Mixer';
class Hello extends Component{
constructor(props) {
super(props);
this.state = {
items: [{
id: 0,
name: "First",
background: "white"
}, {
id: 1,
name: "Second",
background: "yellow"
}, {
id: 2,
name: "Third",
background: "blue"
}],
selectedItem: 0
}
this.handle = this.handle.bind(this)
}
handle(value) {
// console.log(this.state.selectedItem);
this.setState({
selectedItem: value
})
}
render() {
const list = this.state.items.map((item) => {
return(item);
})
return (
<div>
<Mixer item={list} onClick={this.handle} selected={this.state.selectedItem}/>
<ul id = "todo" >
<RenderCont item={this.state.selectedItem}/>
</ul>
</div>
)
}
}
export default Hello;
Mixer.js Child1:
import React, { Component } from 'react';
class Mixer extends Component{
constructor(props) {
super(props);
this.state = {
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(item){
this.props.onClick(item);
}
renderTodos(propItems) {
return (
<div>
{propItems.map((item) => (
<li className={this.props.selected === item ? 'media clicked' : 'media'}
key={item.id} onClick = {() => this.handleClick(item)}>
{item.name}
</li>
))}
</div>
)
}
render() {
return (
<div className="yoyoyo">
{this.renderTodos(this.props.item)}
</div>
)
}
}
export default Mixer;
Second Child Comp renderCont.js :
import React, { Component } from 'react';
class RenderCont extends Component{
constructor(props) {
super(props);
}
renderBox(item){
return(
<div style={{color:item.background}}>
{item.id}
{item.name}
</div>
)
}
render() {
return (
<div className="yoyo">
{this.renderBox(this.props.item)}
</div>
)
}
}
export default RenderCont;
and the CSS:
.yoyo{
left: 500px;
background-color:red;
width:500px;
height:500px;
}
.media{
color: black;
}
.clicked{
font-weight: 900;
}
.yoyoyo{
background-color:lightblue;
width:200px;
height:200px;
}
I think the problem is some mismatch between the initial and eventual value of this.props.selected in Mixer.js. You initially set this.state.selectedItem = 0, and this is what is initially passed as the selected prop to Mixer. But the test you apply in that component is
this.props.selected === item ?
While there is one item.id that === 0, there is never an item that === 0. So no items are highlighted at first. But then, once an item is clicked and selectedItem is actually set to an item, the entry is made bold.
So it looks like you need to either make your initial selection equal to the item.id === 0 reference, or consistently refer to items within your components by their id's.
Related
I just started to learn React. I'm trying to write a Todo list and so far it looks like:
However when I check the box of a Todo, the count of things left to do won't change even when the state of a list of Todos changes (the 'checked' property of a Todo that I just checked change to true)
My App.js:
import React, {Component} from 'react';
import TaskComponent from "./TaskComponent";
class App extends Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
export default App;
My TaskComponent.js file:
import React, {Component} from 'react'
class TaskComponent extends Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
export default TaskComponent;
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
class TaskComponent extends React.Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Inside the TaskComponent class I add the function for the event of changing the "checked" state of the check box but somehow the 'taskList' state in my App does not change at all when I try to console.log it. What is my problem here? Be gentle since I'm new to React and Javascript in general.
You are setting state in TaskComponent and expecting it to change the prop in App.js.
Instead of setting a TaskComponents's state when it is checked, I would recommend calling a function passed in as a prop when it is checked, which has its id and new value. Something along the lines of:
App.js:
// somewhere in your class:
handler(id, value) {
// set state to reflect changes
}
// in your render()
<ul>{this.state.taskList.map((task) => {
<TaskComponent onChange={this.handler} id={task.id} key={task.id} task={task.content} checked={task.checked} />})}</ul>
In TaskComponent.js:
changeHandler({target: {checked}}) {
this.props.onChange(this.props.id, checked);
}
I would also recommend making TaskComponent not have state at all, because it seems unnecessary to me.
I have an location app which can save name of locations.
I am trying to get each saved location a red border by clicking on it.
What it does is changing the border color of all the categories.
How can I apply that?
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
categories: [],
selectedCategories: [],
hidden: true,
checkboxState: true
};
}
toggle(e) {
this.setState({
checkboxState: !this.state.checkboxState
})
}
onChange = (event) => {
this.setState({ term: event.target.value });
}
addCategory = (event) => {
if (this.state.term === '') {
alert('Please name your category!')
} else {
event.preventDefault();
this.setState({
term: '',
categories: [...this.state.categories, this.state.term]
});
}
}
render() {
return (
<div className="categories">
<h1>Categories</h1>
<div className='actions'>
<button className="delete" onClick={this.deleteCategory}>Delete</button>
<button className="edit" onClick={this.editCategory}>Edit</button>
</div>
<p>To add new category, please enter category name</p>
<form className="App" onSubmit={this.addCategory}>
<input value={this.state.term} onChange={this.onChange} />
<button>Add</button>
</form>
{this.state.categories.map((category, index) =>
<button
key={index}
style={this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' }}
checked={this.state.isChecked}
onClick={this.toggle.bind(this)}>
{category}</button>
)}
</div >
);
}
}
I want to be able to control each selected category seperatly, to be able to delete and edit theme as well.
You can set the state based on index and retrieve the similar way,
Code:
{this.state.categories.map((category, index) =>
<button
key={index}
id={`checkboxState${index}`}
style={!this.state[`checkboxState${index}`] ?
{ borderColor: '' } : { border: '2px solid red' }}
checked={this.state.isChecked}
onClick={this.toggle}>
{category}</button>
)}
You can see how I am checking the state dynamically this.state[`checkboxState${index}`] and also I have assigned an id to it.
In toggle method:
toggle = (e) => {
const id = e.target.id;
this.setState({
[id]: !this.state[id]
})
}
FYI, this is a working code, you can see it
https://codesandbox.io/s/vy3r73jkrl
Let me know if this helps you :)
Here's a really bad example using react. I'd more than likely use this.props.children instead of just cramming them in there. This would allow it to be more dynamic. And instead of using state names we could then just use indexes. But you'll observe, that the parent container decides which child is red by passing a method to each child. On click, the child fires the method from the parent. How you implement it can vary in a million different ways, but the overall idea should work.
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
render() {
let color = this.props.backgroundColor;
return(
<section
className={'child'}
style={{backgroundColor: color}}
onClick={this.props.selectMe}
>
</section>
)
}
}
class Parent extends React.Component
{
constructor(props)
{
super(props)
this.state = {
first : 'Pink',
second : 'Pink',
third : 'Pink',
previous: null
}
this.updateChild = this.updateChild.bind(this);
}
updateChild(name)
{
let {state} = this;
let previous = state.previous;
if(previous)
{
state[previous] = 'Pink';
}
state[name] = 'Red';
state.previous = name;
this.setState(state);
}
render()
{
console.log(this)
return(
<section id={'parent'}>
<ChildContainer
selectMe={() => this.updateChild('first')}
backgroundColor = {this.state.first}
/>
<ChildContainer
selectMe={() => this.updateChild('second')}
backgroundColor = {this.state.second}
/>
<ChildContainer
selectMe={() => this.updateChild('third')}
backgroundColor = {this.state.third}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props)
}
render()
{
return(
<section>
<Parent/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
You need to track the state of every checkbox, possibly have an array with all currently checked checkboxes.
Then instead of this.state.checkboxState in this.state.checkboxState ? { borderColor: '' } : { borderColor: 'red' } you need to check if current category is in the currently checked categories array.
Hope this helps
I am new to React and have been trying to figure out how to control an array of components from a parent component. My job is to create a site where I can add or subtract names to a list, but have gotten stuck on the best way to do it. In this case, I created an array of react components, each with controlled input for title boxes and each with a delete button that would call the parent function's remove function through the prop system. However, I noticed that when doing so, the array in the parent function would remain correct, while the id's of the children components would not change to be reordered, thereby ruining subsequent removals. I am sure I am doing this wrong and would like to find a better and more efficient way of doing this. Thanks!
import React, {Component} from 'react';
import Music from './music'
import axios from 'axios';
var childrenComponents = [];
class Selection {
constructor(){
this.music = '';
this.beginning = 0;
this.the_end = 0;
}
setTitle=(title)=>{
this.music = title;
}
setStart=(start)=>{
this.beginning = start;
}
setEnd=(end)=>{
this.the_end = end;
}
}
class Practice extends React.Component{
constructor(props){
super(props);
this.state = {
number_music: 0,
number: 0,
selections: Array(0).fill(null),
deletions: 0,
}
this.addAnotherSong = this.addAnotherSong.bind(this);
this.removeSong = this.removeSong.bind(this);
this.renderMusicPlayed = this.renderMusicPlayed.bind(this);
}
removeSong(index){
if((this.state.number_music-1) >= 0){
alert(index);
for(var i = 0; i < (this.state.selections.length-1); i++){
console.log(this.state.selections[i].music);
}
childrenComponents.splice(index, 1);
this.setState({selections: this.state.selections.filter((_, i) => i !== index),
number_music: this.state.number_music - 1,
deletions: this.state.deletions += 1});
console.log("========================");
for(var i = 0; i < (this.state.selections.length-1); i++){
console.log(this.state.selections[i].music);
}
console.log("///////////////////////////////////////////////////");
}
}
addAnotherSong(){
this.state.selections.push(new Selection());
var i = this.state.number_music;
childrenComponents.push(
<Music key={i} number={i} subtract={this.removeSong}
Title={this.state.selections[i].music} Start={this.state.selections[i].beginning}
End={this.state.selections[i].the_end} changeTitle={this.state.selections[i].setTitle}
changeStart={this.state.selections[i].changeStart} changeEnd={this.state.selections[i].changeEnd}/>
);
this.setState({ number_music: this.state.number_music += 1, number: this.state.number += 1});
}
renderMusicPlayed(){
return (
<div>
{childrenComponents}
</div>
);
}
render(){
return(
<div>
<button onClick={()=> this.props.practice()}>Log Practice Session</button>
<h1>{this.props.time}</h1>
<form >
Description: <input type="form" placeholder="How did it go?" name="fname"/><br/>
</form>
{this.renderMusicPlayed()}
<button onClick={()=>this.addAnotherSong()}>Add Another Piece</button>
{this.state.number_music}
</div>
);
}
}
export default Practice;
That is the parent.
This is the Child:
import React, {Component} from 'react';
import InputBox from './input';
class Music extends React.Component{
constructor(props){
super(props);
this.state = {
title: null,
start: null,
end: null
}
}
componentWillReceiveProps(props){
this.setState({ title: this.props.Title});
}
render(){
return(
<div>
<InputBox initialValue={this.props.number} cValue={this.props.Title} identity={this.props.number} updateInput={this.props.changeTitle} />
<InputBox initialValue="Starting Measure" cValue={this.props.Start} identity={this.props.number} updateInput={this.props.changeStart} />
<InputBox initialValue="Ending Measure" cValue={this.props.End} identity={this.props.number} updateInput={this.props.changeEnd} />
<button onClick={()=> this.props.subtract(this.props.number)}>Delete</button>
</div>
)
}
}
export default Music;
And this is the grand child so to speak:
import React,{Component} from 'react';
class InputBox extends React.Component{
constructor(props){
super(props);
this.state = { value: this.props.initialValue, text: "" }
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
this.setState({value: event.target.value});
this.props.updateInput(this.state.value, this.props.identity);
}
render(){
return(
<input type="text" onChange={this.handleChange} value={this.state.cValue}></input>
)
}
}
export default InputBox;
I guess my main question is what is the ideal way for handling this kind of problem.
The reason your IDs are not changing is because you're pushing fully formed components to the array.
Imagine we have 3 components - formatting will be a little weird, but hopefully it illustrates the point:
[ Music: { id: 0 }, Music: { id: 1 }, Music: { id: 2 } ]
When we click the delete button, say on Music with id: 1, we end up with this:
[ Music: { id: 0 }, Music: { id: 2 } ]
We spliced the right Music out, but we now have a wrong index - we never actually changed the Music with id: 2. It would be much easier (in my opinion) to just dynamically construct your Music components in the render function.
Realistically, your childrenComponents array isn't all that useful - the Music components created in it are all created with the index i in mind:
Title={this.state.selections[i].music}
Start={this.state.selections[i].beginning}
End={this.state.selections[i].the_end}
and so on and so forth.
We could simplify this pretty easily, and consolidate all of this into one array.
Imagine we had an array field state.children, which looked something like this:
[
{ title: _____, start: _____, end: ____, ... },
{ title: _____, start: _____, end: ____, ... },
{ title: _____, start: _____, end: ____, ... },
]
This is a lot more clear in a huge way: our data is consolidated in one, singular place, and we aren't tying them together by some arbitrary index. You've done this in a sense with your selections array, but because you are also using childrenComponents, you're double managing what is essentially the same data.
We can pretty easily render it, too, with something along the lines of:
render() {
{
this.state.children.map((child, index) => (
<Music key={index}
number={index}
subtract={this.removeSong}
Title={this.state.children[index].title}
...
/>
);
}
}
That helps us decouple the actual meat of our objects (title, beginning, ending, etc) from their position in the array, which don't really mean anything and are just getting in the way here. That lets us splice up our array however we see fit, and be certain that we're not breaking any relationships between our components and their indexes.
I've been searching for a few days and can't seem to get this to work and it's driving me insane!
The aim is an accordion with the addition of an "Expand All"/"Collapse All" button. I managed to get this to work at one point by passing an expandAll property to the child component through props and updating the expand state of the child in componentWillReceiveProps, but if the parent component is set to auto-fetch data on a timer, then it meant that the child state was updated to collapse or expand all the panels after the second fetch which isn't the required behaviour.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
"teams": [{ Name: "Liverpool", Players: ["Lovren", "Coutinho"]}, {Name:"Arsenal", Players: ["Ozil", "Welbeck"]},{Name: "Chelsea", Players: []}],
toggleAll: false,
toggleText: "Expand All"
}
}
_toggleAll(event){
event.preventDefault();
let text = "Expand All";
if (!this.state.toggleAll === true){
text = "Collapse All"
}
this.setState({toggleAll: !this.state.toggleAll, toggleText: text});
console.log("toggleAll", this.state.toggleAll);
}
render() {
return (
<div className="headers">
<button onClick={this._toggleAll.bind(this)}>{this.state.toggleText}</button>
{this.state.teams.map((team, index) =>
<Header className="" key={index} title={team.Name} players={team.Players} />
)}
</div>
);
}
}
class Header extends React.Component {
constructor(props){
super(props);
this.state = {
expanded: false
}
}
componentWillMount(){
console.log(this.props.title);
console.log(this.props.players);
}
_expand(event){
event.preventDefault();
this.setState({expanded: !this.state.expanded});
}
render() {
let players;
if(this.state.expanded)
{
players= <ul>
{this.props.players.map((player, index) => <li key={index}>{player}</li>)}
</ul>
}
return (
<div>
<div className="header" >
<p onClick={this._expand.bind(this)}>{this.props.title}</p>
</div>
{players}
</div>
);
}
}
React.ReactDOM.render(
<App />,
document.getElementById('container')
);
Fiddle provided below
https://jsfiddle.net/aamirjaffer/x52bhdq6/
You need to pass a prop from the the App component to give the header it's "collapseAll" state. I updated the rendering of the header in App like so:
<Header
className = ""
key={index}
title={team.Name}
players = {team.Players}
allExpanded={this.state.toggleAll}
/>
Then the Header component needs to listen for that prop change on conponentWillReceiveProps and set it's own state accordingly like so:
componentWillReceiveProps(nextProps) {
if (nextProps.allExpanded !== this.props.allExpanded) {
this.setState({ expanded: nextProps.allExpanded });
}
}
I forked your fiddle made, those changes and it all works just fine: https://jsfiddle.net/xkp4snmx/
You could store collapsed state of each team.
this.state = {
teams: [
{
Name: "Liverpool",
Players: ["Lovren", "Coutinho"],
collapsed: true
},
{
Name: "Arsenal",
Players: ["Ozil", "Welbeck"],
collapsed: true
},
{
Name: "Chelsea",
Players: ["Hazard", "Oscar", "Willian"],
collapsed: true
}
]
}
js fidds
ps: I would probably render the players list regardless, and just hide it via css - less computation and with css you can give it neat animations for a true accordion feel.
pss: want to throw it in that your spacing & indentation made it that much harder to read your code =|
I'm having a bit of a head ache trying to figure out the React way of implementing this.
I have a Searches component which houses SearchItems, when an item is clicked among other things I need to set it's state to active to that it gets the correct CSS, I managed to get this working fine but how would I go about removing the active state from the others?
I was thinking that I could pass down a function from the top level component that would take the ID of the search, when clicked it'd zip through SearchItems and change their state to either true/false depending on which ID it was?
Code below!
Top level component:
import React from "react";
import {Link} from "react-router";
import Search from "./Search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
]
};
}
render() {
const { searches } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
Search items component
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
this.setState({selected: true});
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}
I think you don't need the state in the child component at all. In fact is a good idea to avoid having state in most components so they are easy to reason and reuse.
I would leave all the state only on the parent component in this case.
TOP Component:
import React from "react";
import Search from "./search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : null
};
}
_onSearchSelect(searchId) {
this.setState({'activeElement': searchId})
}
render() {
const { searches, activeSearchId } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}
isActive={search.id === activeElement}
onSelect={this._onSearchSelect.bind(this)} />
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD Component:
import React from "react";
export default class Search extends React.Component {
_getPanelClassNames() {
const { isActive } = this.props
return 'row panel panel-success ' + (isActive ? 'active' : 'default')
}
_onSelect() {
const { id, onSelect } = this.props;
onSelect(id)
}
render() {
const { searchName, matches } = this.props;
const panelStyle = { height: '90px', marginBottom: '6px', boxShadow: '' }
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={panelStyle} className={this._getPanelClassNames()}>
<div className="col-xs-4">
Search Name: {searchName}
</div>
<div className="col-xs-3">
Must Have: PHP, MySQL
</div>
<div className="col-xs-3">
Could Have: AngularJS
</div>
<div className="col-xs-2">
<button type="button" onClick={this._onSelect.bind(this)}
style={buttonStyle} className="btn btn-default btn-lg"
>
{matches}
</button>
</div>
</div>
);
}
}
You can also see it running in Plunker: https://plnkr.co/edit/sdWzFedsdFx4MpbOuPJD?p=preview
Ok it turns out this is simpler than I thought and is simply a case of understanding how react works(and not getting confused) .
When you have a top level component you pass it's state via props to children, when you update the state in the top level component it'll pass that down to the children and you can use componentWillReceiveProps to take action.
I added a function to my top level component called updateActiveSearch which simply sets the state of the TOP level component I then passed the activeElement state as a prop to the child Elements along with the function. When a child element calls this function to set itself as active all of them will fire componentWillReceiveProps, they simply just need to check their own ID against the one they've received, if it matches they're active, if it doesn't they're not!
So my top level component now looks like this:
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : 0
};
}
// This function gets passed via a prop below
updateActiveSearch(id){
//console.log(id);
this.setState({activeElement : id});
}
render() {
const SearchItems = this.state.searches.map((search) => {
return <Search activeElement={this.state.activeElement} goFunction={this.updateActiveSearch.bind(this)} key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD COMPONENTS
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
// This happens right before the props get updated!
componentWillReceiveProps(incomingProps){
if(incomingProps.activeElement == this.props.id){
this.setState({selected: true});
} else {
this.setState({selected: false});
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
//this.state.panelStyle.boxShadow = '-2px 3px 20px 5px rgba(255,198,0,1)';
this.setState({selected: true});
this.props.goFunction(this.props.id);
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}