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 =|
Related
Hello everyone, I am trying to passing a method through a context api component to another component which, i have a map function there. I want my showInfo state changes to true or false depending on the button clicking, when i clicked the button, all the showInfo's of my states is changes, so thats not what i want, I want that specific item to change when i press to it. Can someone explaine where is the mistake that i've made?
MY CONTEXT APİ
import React from "react";
export const ToursContext = React.createContext();
class ToursContextProvider extends React.Component {
constructor(props) {
super(props);
this.changeState = this.changeState.bind(this);
this.state = {
tours: [
{
id: 0,
imageURL:
"https://images.unsplash.com/photo-1524231757912-21f4fe3a7200?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1351&q=80",
title: "İstanbul'un Güzelliğinin Sadece Bir Parçası Galata Kulesi",
showInfo: true,
info: "LOREM İPSUM AMET 1",
},
{
id: 1,
imageURL:
"https://images.unsplash.com/photo-1541432901042-2d8bd64b4a9b?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1319&q=80",
title: "Tarihi Süleymaniye Camii",
showInfo: true,
info: "LOREM İPSUM AMET 2",
},
],
};
}
changeState(itemdelete) {
this.setState({
showInfo: !this.state.showInfo,
});
console.log(itemdelete);
}
render() {
return (
<ToursContext.Provider
value={{ ...this.state, changeState: this.changeState }}
>
{this.props.children}
</ToursContext.Provider>
);
}
}
export default ToursContextProvider;
MY MAP LIST COMPONENT
import React from "react";
import { ToursContext } from "../contexts/Tours";
function Tours() {
return (
<div className="container">
<div className="row">
<ToursContext.Consumer>
{(value) => {
const { changeState } = value;
return value.tours.map((item) => (
<div className="col-md-4" key={item.id}>
<div className="card bg-dark text-white">
<img src={item.imageURL} className="card-img" alt="..." />
<div className="card-img-overlay">
<h5 className="card-title">{item.title}</h5>
<button
type="button"
onClick={changeState.bind(this, item)}
className="btn-sm btn-primary"
>
Bilgiyi Göster!
</button>
</div>
{value.showInfo ? "true" : "false"}
</div>
</div>
));
}}
</ToursContext.Consumer>
</div>
</div>
);
}
export default Tours;
You state is atomic. This means that it is treated as a single value. With classes, you have option to modify state object partially. For example, you have object with fields a and b. You can change both fields at once, only a or only b. But there is no option to modify state deeply. Let's imagine that you have state object like this:
{
"a": { "subfield_1": [], "subfield_2": "some string"},
"b": 3
}
You again, can modify a or b, but if you want to add item into array a.subfield_1 or change a.subfield_2, you will have to modify whole a, like this:
setState({
a: {
...a,
subfield_1: this.state.a.subfield_1.concat("new item"),
},
});
In you case, to change something inside tours key, you will have to modify whole tours key. It would be something like this:
changeState(itemdelete) {
this.setState({
tours: tours.map((item) =>
item.id !== itemdelete.id ? item : { ...item, showInfo: !item.showInfo }
),
});
}
I'm currently working on an accordion component in react version 16.3.2, that receives props from another component and displays the accordion accordingly. Unfortunately I cannot use hooks.
This works pretty smoothly, what I want to implement now is a way so that only one accordion can be opened at the same time on the same page. So, once you open one accordion (while another is already opened) it should automatically close the already opened one.
I have an Id (string which describes the current section, e.g 'contact', 'info', etc.) and the state of an accordion gets saved in the state (set to true when you toggle the accordion).
I'm not quite sure on how I could implement this mechanism and am looking for tips on how I could solve this in a smart way. Any pointers?
example:
https://codesandbox.io/s/reactjs-accordion-automatic-close-mechanism-6dys1
(I didn't add all of the styling, animations since this is more about functionality)
You could do something like this, using the state hook in the App component
export default function App() {
const items = [
{ id: 1, title: 'First Accordion', content: 'Hello' },
{ id: 2, title: 'Click me', content: 'Hello 2' },
{ id: 3, title: 'Third Accordion Accordion', content: 'Hello 3' },
]
const [selectedItem, setSelectedItem] = useState(1)
const handleClick = id => {
setSelectedItem(id)
}
return (
<div className="App">
{items.map(x => {
return (
<Accordion
key={x.id}
id={x.id}
title={x.title}
open={x.id === selectedItem}
onClick={handleClick}
>
<p>{x.title}</p>
</Accordion>
)
})}
</div>
);
}
Then your accordion component is a bit simpler
class Accordion extends React.Component {
accToggle() {
this.props.onClick(this.props.id);
}
sectionClasses() {
let classes = "accordion";
classes += this.props.open ? " sec-on" : "";
classes += "sec-underway";
return classes.trim();
}
render() {
return (
<section className={this.sectionClasses()} id={this.props.id}>
<div className="acc-title" onClick={this.accToggle.bind(this)}>
<h3 className="acc-text">{this.props.title}</h3>
<div className="acc-nav">
<span className="acc-toggle" />
</div>
</div>
<div className="acc-content">{this.props.children}</div>
</section>
);
}
}
Accordion.defaultProps = {
open: false
};
Accordion.propTypes = {
id: PropTypes.number.isRequired,
children: PropTypes.any,
onFocus: PropTypes.func,
progress: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
PropTypes.bool
]),
title: PropTypes.string,
open: PropTypes.bool
};
export default Accordion;
The accordion calls a function on the app component, which updates the state and the display logic is passed down in the props
You can find solution for your problem in the following codesandbox
https://codesandbox.io/s/reactjs-accordion-automatic-close-mechanism-yejc0
Change prop names as it fits your code base, but the logic is solid
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.
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
Below is my code...
<ul className="no-style board__list">
{Object.keys(today.books).map(function(id) {
var refBook = today.books[id][0];
return (
<li key={refBook._id} className="board__list-item">
<div className="container flexrow">
<div className="flexrow__fit-2">{refBook.book_no}</div>
<div className="flexrow__org">
<span className="board__icon-wrap">
{refBook.memo
? (<i className="fa fa-flag" style={{color:"#F9AB9F"}}></i>)
: null
}
</span>
{refBooking.memo
? (<div className="memo_dialog">{refBook.memo}</div>)
: null
}
</div>
</div>
</li>
);
})}
</ul>
I have a object books array and I create a fa-flag icon for each book.
What I want is to show different memo dialog when mouse hover on each flag icon.
I know how to do it with query but how can I do this in react way not using jquery?
I'm not sure what are you trying to achieve but this example might be useful for you
class Book extends React.Component {
constructor(props){
super(props);
this.handleOver = this.handleOver.bind(this);
}
handleOver(name){
this.props.over(this.props.name)
}
render(){
return <div onMouseOver={this.handleOver}>{this.props.name}</div>
}
}
class BookList extends React.Component {
constructor(props){
super(props);
this.mouseOver = this.mouseOver.bind(this);
this.state = {
books: ['hello', 'amazing', 'world'],
memo: ''
}
}
mouseOver(name){
this.setState({memo: name})
}
render(){
const bookList = this.state.books.map((book, index)=>{
return <Book key={index} name={book} over={this.mouseOver}/>
});
return <div>
{bookList}
<hr/>
<div>{this.state.memo}</div>
</div>
}
}
React.render(<BookList />, document.getElementById('container'));
Also fiddle example.
I hope it will help you. Thanks
I suggest you to use isHovered state variable, to store hover state.
We are displaying some component(in your case it would be dialog box), if isHovered is true and hide it when this variable is false.
When we will hover on link element, we will trigger handleEnter function to set isHovered variable to true.
Similarly, when we are moving cursor out of link element, we are triggering handleLeave function to set isHovered variable to false.
Example:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
isHovered: false,
};
}
handleEnter() {
this.setState({
isHovered: true
});
}
handleLeave() {
this.setState({
isHovered: false
});
}
render() {
return (
<div>
<a
onMouseEnter={this.handleEnter.bind(this)}
onMouseLeave={this.handleLeave.bind(this)}
>Link</a>
{this.state.isHovered ? (
<div className="box">A component</div>
) : (
<div />
)}
</div>
);
}
}
Also, you can see demo at CodePen.