I have a component name dialog Box and i want to show it on click over all other components . I have a main component as
class ImageEditor extends Component {
constructor() {
super();
this.state = { images: [], redirect: 'no', imageId: '', isDialogOpen: 'no' };
}
componentDidMount() {
let promise = apiGateway.getImages();
promise.then((images) => {
this.setState({ images: images });
});
}
openDialog = () =>{
this.setState({ isDialogOpen : 'yes' });
}
closeDialog = () =>{
this.setState({ isDialogOpen: 'no' });
}
deleteImage = (id) => {
apiGateway.removeImage(id);
}
setRedirect = (id) => {
this.setState({ redirect: 'yes', imageId: id });
}
renderImage(image,index){
return(
<div key={index}>
<p>Title: {image.title}</p>
<p>Description: {image.description}</p>
<button onClick={(e)=> this.deleteImage(image._id)}>DELETE</button>
<button onClick={(e)=> this.setRedirect(image._id)}>UPDATE</button>
<button onClick={this.openDialog}>SHARE</button>
<img className='image' src={image.link} width='100' height='100' alt='nature'/>
<br/><br/>
</div>
);
}
render() {
const { redirect , isDialogOpen } = this.state;
if(redirect === 'yes'){
return <Redirect to={`/update/${this.state.imageId}`}/>
}
if(isDialogOpen === 'yes'){
<DialogBox /> ????????
}
return(
<div>
<div>{
this.state.images.map((image,index)=>{
return this.renderImage(image,index);
})
}
</div>
</div>
);
}
}
export default ImageEditor;
When a person clicks on SHARE , I want Dialog Box to Open.I donot know the way i am doing i.e loading component with dialogBox is right or wrong. So every Advice or any suggestion will definitely help me. Here is my dialogue component(Right now i am getting a error as :Expected an assignment or function call and instead saw an expression no-unused-expressions)
class DialogBox extends Component {
render() {
return(
<div>
<Dialog title="Dialog Title" modal={true} onClose={this.handleClose} buttons={ [{ text: "Close", onClick: () => this.handleClose() }] }>
<h1>Dialog Content</h1>
<p>More Content. Anything goes here</p>
</Dialog>
</div>
);
}
}
You can do this:
return (
<div>
<insert your other components>
{this.state.isDialogOpen === 'yes' && <DialogBox />}
<div>
)
I'd advise using boolean instead of string for isDialogOpen.
Related
I am working on Dynamic add field data submit with reactjs and axios. Its working fine but only on one filed when i want to add more field then the field added but when i put my data and want to post data then only one field working. i am using react and axios post for submit my form.
My code
import React from 'react';
import axios from 'axios';
import { Grid, Button } from "#material-ui/core/";
import Alert from '#material-ui/lab/Alert';
import CloseIcon from "#material-ui/icons/Close";
import TextField from "#material-ui/core/TextField";
import AddIcon from '#material-ui/icons/Add';
class FaqPage extends React.Component<{},any>{
constructor(props) {
super(props)
this.state = {
faqList:[{index: Math.random(), question:'', answer: ''}],
// question: '',
// answer: '',
questions: ['hello']
}
this.state = { values: [{question: '',response: ''}] };
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidUpdate(){
setTimeout(() => this.setState({errorMessage:''}), 40000);
setTimeout(() => this.setState({sucessMessage:''}), 40000);
}
uploadDocument = (e) => {
console.log(e);
}
handlequestionChange = event => {this.setState({ question: event.target.value })}
handleanswerNameChange = event => {this.setState({ answer: event.target.value })}
handleSubmit = event => {
event.preventDefault();
event.target.reset()
// const openDate = {userid: this.state.openDate};
// const closeDate = {fullname: this.state.closeDate};
// const listingDate = {usergroup: this.state.listingDate};
axios.post('https://api.com/api/faqs',
{
ipoId: '328',
faqList:[
{ question: this.state.question,
answer: this.state.answer,
}
]
}
)
.then(res => {
console.log(res);
console.log(res.data);
console.log('thank you')
this.setState({sucessMessage: res.data});
})
.catch(err => {
this.setState({errorMessage: err.message});
})
}
createUI(){
return this.state.values.map((el, i) =>
<div key={i} className="commonInput">
<div className="formW">
<TextField type="text" label="question" name="question" onChange={this.handlequestionChange}/>
</div>
<div className="formW">
<TextField type="text" label="answer" name="answer" onChange={this.handleanswerNameChange}/>
</div>
{
i ?
<button type="button" className="button remove" onClick={() => this.removeClick(i)}><CloseIcon /></button>
: null
}
</div>
)
}
handleChange (i,event) {
let values = [...this.state.values];
values[i][event.target.id] = event.target.value;
this.setState({ values });
}
addClick(){
this.setState(prevState => ({ values: [...prevState.values, {question: '',response: '',}]}))
}
removeClick(i){
let values = [...this.state.values];
values.splice(i,1);
this.setState({ values });
}
componentDidMount() {
axios.get(`https://api.com/api/get/all`)
.then(res => {
const posts = res.data;
this.setState({ posts });
})
}
render() {
return (
<Grid className="CoomonT">
<div className="clapse ">
<form onSubmit={this.handleSubmit}>
<br/>
{this.createUI()}
<input type='button' value='add more' onClick={this.addClick.bind(this)}/>
{/* <div className="form-btn formW">
<Button
size="small"
variant="contained"
className=""
color="primary"
onClick={this.addClick.bind(this)}
>
<AddIcon/>
</Button>
</div> */}
{/* <div>file<TradeImportComponent/></div> */}
{/* <button type="submit" color="primary">Add</button> */}
<div className="form-btn formW">
<Button
size="small"
variant="contained"
className=""
color="primary"
type="submit"
disableElevation
>
Submit
</Button>
</div>
</form>
</div>
</Grid>
)
}
}
export default FaqPage;
Screenshot
Why are you using this.state two times? Each class component in React has only 1 state. So first of all, you should correct your state definition. Moreover, Your values state is an Array. So when you want to edit your question/answer or post data via axios, you should use the whole values array.
There are also some questions that you should answer to yourself to have a cleaner code. I implemented some general changes and commented those questions in the below code. Answer them and change code based on their answer, so that your code works.
import axios from 'axios';
import React from 'react';
import { Grid, Button } from "#material-ui/core/";
import Alert from '#material-ui/lab/Alert';
import CloseIcon from "#material-ui/icons/Close";
import TextField from "#material-ui/core/TextField";
import AddIcon from '#material-ui/icons/Add';
class FaqPage extends React.Component<{},any>{
constructor(props) {
super(props)
// What is defference between faqList and values? Could'nt you use just one of them? and build the other one from another one (whenever needed)?
this.state = {
faqList:[{index: Math.random(), question:'', answer: ''}],
values: [{question: '',response: ''}]
}
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidUpdate(){
setTimeout(() => this.setState({errorMessage:''}), 40000);
setTimeout(() => this.setState({sucessMessage:''}), 40000);
}
uploadDocument = (e) => {
console.log(e);
}
handleQuestionChange = (event, index) => {
const faq = this.state.values
faq[index].question = event.target.value
this.setState(faq)
}
handleAnswerNameChange = (event, index) => {
const faq = this.state.values
faq[index].answer = event.target.value
this.setState(faq)
}
handleSubmit = event => {
event.preventDefault();
event.target.reset()
// What is ipoId? Is it different from the `index` field in `this.state.faqList`?
axios.post('https://api.com/api/faqs',
{
ipoId: '328',
faqList:this.state.value
}
)
.then(res => {
console.log(res);
console.log(res.data);
console.log('thank you')
this.setState({sucessMessage: res.data});
})
.catch(err => {
this.setState({errorMessage: err.message});
})
}
createUI(){
return this.state.values.map((el, i) =>
<div key={i} className="commonInput">
<div className="formW">
<TextField type="text" label="question" name="question" onChange=(e, i) => {this.handleQuestionChange(i)}/>
</div>
<div className="formW">
<TextField type="text" label="answer" name="answer" onChange=(i) => {this.handleAnswerNameChange(e, i)}/>
</div>
{
i ?
<button type="button" className="button remove" onClick={() => this.removeClick(i)}><CloseIcon /></button>
: null
}
</div>
)
}
addClick(){
this.setState(prevState => ({ values: [...prevState.values, {question: '',response: '',}]}))
}
removeClick(i){
let values = [...this.state.values];
values.splice(i,1);
this.setState({ values });
}
componentDidMount() {
axios.get(`https://api.com/api/get/all`)
.then(res => {
const posts = res.data;
this.setState({ posts });
})
}
render() {
return (
<Grid className="CoomonT">
<div className="clapse ">
<form onSubmit={this.handleSubmit}>
<br/>
{this.createUI()}
<input type='button' value='add more' onClick={this.addClick.bind(this)}/>
{/* <div className="form-btn formW">
<Button
size="small"
variant="contained"
className=""
color="primary"
onClick={this.addClick.bind(this)}
>
<AddIcon/>
</Button>
</div> */}
{/* <div>file<TradeImportComponent/></div> */}
{/* <button type="submit" color="primary">Add</button> */}
<div className="form-btn formW">
<Button
size="small"
variant="contained"
className=""
color="primary"
type="submit"
disableElevation
>
Submit
</Button>
</div>
</form>
</div>
</Grid>
)
}
}
export default FaqPage;
I'm implementing a Like and Dislike Button, and I wanna that when I click them will be with other colors, but just the clicked component, when I click all buttons change the state, can anybody help me?
`
const indexPost = async () => {
const data = await api.get('/api/posts')
if(data.data.length !=0){
const dataArray = data.data
if(dataArray.length === 0) {
return
}else{
return(
setPost(dataArray.map( data => (
<Post key={data._id} id={data._id} title={data.title} text={data.text}>
<Like id={data._id}></Like>
</Post>
)))
)
}
}
}
export default function Like({itemId}) {
const context = useContext(notificationContext)
const {isLoved, Like, Loved, Unlike, isLike, isUnlike, setIsLike, setIsUnlike, setIsLoved } = context
return(
<div className={styles.likeContainer} key={itemId}>
{isLike ? (
<button className={styles.likeContent} onClick={() => setIsLike(false)}><Icon.ThumbsUp className={styles.Icon} fill="#5CB0BB" ></Icon.ThumbsUp></button>) :
(<button className={styles.likeContent} onClick={() => Like() }><Icon.ThumbsUp className={styles.Icon} ></Icon.ThumbsUp></button>)}
{isLoved ?
(<button className={styles.likeContent} onClick={() => setIsLoved(false)}><Icon.Heart className={styles.Icon} fill="red" ></Icon.Heart> </button>) :
(<button className={styles.likeContent} onClick={() => Loved() }><Icon.Heart className={styles.Icon} ></Icon.Heart></button>)}
{isUnlike ? (
<button className={styles.likeContent} onClick={() => setIsUnlike(false)}><Icon.ThumbsDown className={styles.Icon} fill="#702BA6" ></Icon.ThumbsDown> </button>) :
(<button className={styles.likeContent} onClick={() => Unlike()}><Icon.ThumbsDown className={styles.Icon} ></Icon.ThumbsDown></button>
)}
</div>
)
};
I have implemented the similar one in my project, it is very basic , it shows how to update the likes , you need to handle the cases of user authentication and stuff
App.js
import { useState, useEffect, createContext, useReducer } from "react";
import { updateArrayOfObj } from "./utils";
import AllPosts from "./AllPosts";
export const PostsContext = createContext();
const initialState = {
posts: [
{
_id: "1",
name: "Browny",
image: "http://placekitten.com/200/310",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "2",
name: "Blacky",
image: "http://placekitten.com/200/320",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "3",
name: "SnowWhite",
image: "http://placekitten.com/200/300",
likes: 0,
love: 0,
dislikes: 0
}
]
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_POST":
return {
...state,
posts: updateArrayOfObj(
state.posts,
action.payload.obj,
"_id",
action.payload._id
)
};
case "CREATE_POST":
return {
...state,
posts: [...state.posts, ...action.payload.data]
};
case "DELETE_POST":
return {
...state,
posts: state.posts.filter((ele) => ele._id !== action.payload._id)
};
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<PostsContext.Provider
value={{
state,
dispatch
}}
>
<div className="App">
<AllPosts />
</div>
</PostsContext.Provider>
);
}
PostsAll.js
import Post from "./Post";
import { PostsContext } from "./App";
import { useContext } from "react";
export default function AllPosts() {
const { state } = useContext(PostsContext);
return (
<div className="allPosts">
{state.posts.map((item) => {
return (
<Post
name={item.name}
image={item.image}
likes={item.likes}
love={item.love}
dislikes={item.dislikes}
id={item._id}
key={item._id}
/>
);
})}
</div>
);
}
Post.js
import { PostsContext } from "./App";
import { useContext } from "react";
export default function Post(props) {
const { state, dispatch } = useContext(PostsContext);
const handleUserInteraction = (type, id) => {
dispatch({
type: "UPDATE_POST",
payload: {
obj: { [type]: props[type] + 1 },
_id: id
}
});
};
return (
<div className="post">
<h3>{props.name}</h3>
<img src={props.image} alt="cat" />
<br />
<button onClick={() => handleUserInteraction("likes", props.id)}>
{props.likes} Like
</button>{" "}
<button onClick={() => handleUserInteraction("love", props.id)}>
{props.love} Love
</button>{" "}
<button onClick={() => handleUserInteraction("dislikes", props.id)}>
{props.dislikes} Dislike
</button>
</div>
);
}
You can refer to this codesandbox to implement the same
You can use onClick() on each like button and attach it with a function, then you can get the value of that particular like with e.currentTarget.id and change its css/style the way you want.
const handleClick=(e)=>
{
console.log(e.currentTarget.id);
}
I did data display from json with titles from all posts and now i need to do redirect to page with title and body after click on title, any ideas? Thanks for help
import "./style.css";
class Card extends React.Component {
state = {
loading: true,
posts: [],
};
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const posts = await response.json();
this.setState({ posts, loading: false });
}
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
{post.title}
</div>
))}
</div>
)}
</div>
);
}
}
export default Card;```
I would recommend you to use Functional component as it would make your code short and much more readable.
But as per your code you can simply get the desired results by the making the following changes in your code.
import { useHistory } from 'react-router-dom';
class Card extends React.Component {
const history = useHistory();
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
<div onClick={() => history.push(post.title)}>
{post.title}
</div>
</div>
))}
</div>
)}
</div>
);
}
}
import "./style.css";
class Card extends React.Component {
state = {
loading: true,
posts: [],
};
async componentDidMount() {
const url = "https://jsonplaceholder.typicode.com/posts";
const response = await fetch(url);
const posts = await response.json();
this.setState({ posts, loading: false });
}
gotoPage=(data)=>()=>{ //write this function above render
this.props.history.push('/'+data);
}
render() {
return (
<div>
{this.state.loading || !this.state.posts.length === 0 ? (
<div> loading...</div>
) : (
<div>
{this.state.posts.map((post) => (
<div key={post.id} className="post-header">
<span onClick={this.gotoPage(post.title)}>{post.title}</span> // You can use this function, when user user click on title, navigate page to that title
</div>
))}
</div>
)}
</div>
);
}
}
export default Card;```
I have set of components where it would consist of input fields along with text rows.
As given in the image the users should be able to add categories and description. After adding them they will be rendered as a list of components. like this
Inside a category there will be tags as given in the above image and to add them i have to add a input component. This input component should be available only when the user clicks on the Add tag button below each category row. When a user clicks on it,it should enable the input(should render a input component inside the selected category row) and should be able to type the tag name on it and save it. I need to make this input field enable only when i click on the add tag button. and it should enable only in the selected category row. This is the code that i have tried.
import React, { Component, Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
class App extends Component {
state = {
category: "",
description: "",
categories: []
};
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
addCategory = () => {
let { category, description } = this.state;
this.setState(prevState => ({
categories: [
...prevState.categories,
{
id: Math.random(),
title: category,
description: description,
tags: []
}
]
}));
};
addTag = id => {
let { tag, categories } = this.state;
let category = categories.find(cat => cat.id === id);
let index = categories.findIndex(cat => cat.id === id);
category.tags = [...category.tags, { name: tag }];
this.setState({
categories: [
...categories.slice(0, index),
category,
...categories.slice(++index)
]
});
};
onKeyDown = e => {
if (e.key === "Enter" && !e.shiftKey) {
console.log(e.target.value);
}
};
tags = tags => {
if (tags && tags.length > 0) {
return tags.map((tag, i) => {
return <Header key={i}>{tag.name}</Header>;
});
}
};
enableTagIn = id => {};
categories = () => {
let { categories } = this.state;
return categories.map(cat => {
return (
<Fragment key={cat.id}>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
onKeyDown={e => {
this.onKeyDown(e);
}}
onChange={this.onChange}
/>
<Button
onClick={e => {
this.addTag(cat.id);
}}
>
Add
</Button>
{this.tags(cat.tags)}
</Fragment>
);
});
};
render() {
return (
<Fragment>
{this.categories()}
<div>
<Input name="category" onChange={this.onChange} />
<Input name="description" onChange={this.onChange} />
<Button onClick={this.addCategory}>Save</Button>
</div>
</Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is the codesandbox url.
Any idea on how to achieve this?.
I changed your code by using function components and react hooks and i created category component which has it own state like this:
import React, { Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
const App = () => {
const [Category, setCategory] = React.useState({
title: "",
description: ""
});
const [Categories, setCategories] = React.useState([]);
return (
<div>
{console.log(Categories)}
<Input
value={Category.title}
onChange={e => setCategory({ ...Category, title: e.target.value })}
/>
<Input
value={Category.description}
onChange={e =>
setCategory({ ...Category, description: e.target.value })
}
/>
<Button onClick={() => setCategories([...Categories, Category])}>
Save
</Button>
<div>
{Categories.length > 0
? Categories.map(cat => <CategoryItem cat={cat} />)
: null}
</div>
</div>
);
};
const CategoryItem = ({ cat }) => {
const [value, setvalue] = React.useState("");
const [tag, addtag] = React.useState([]);
const [clicked, setclicked] = React.useState(false);
const add = () => {
setclicked(false);
addtag([...tag, value]);
};
return (
<Fragment>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
value={value}
style={{ display: clicked ? "initial" : "none" }}
onChange={e => setvalue(e.target.value)}
/>
<Button onClick={() => (clicked ? add() : setclicked(true))}>Add</Button>
<div>{tag.length > 0 ? tag.map(tagname => <p>{tagname}</p>) : null}</div>
</Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
and here a sandbox
I have a list of Messages that you should be able to click and expand more for info. At the moment, my implementation expands/collapses all messages by clicking on any message.
I tried using the code below:
this.state = {
activeIndex:0,
isExpandable:false
}
And applying the condition as:
{!this.state.isExpandable && this.state.activeItem === i} to the map() where i was retrieving properties of each object.
image of buttons in collapsable state
image of buttons in expanded state
import React, { Component } from 'react'
import { Card, Feed } from 'semantic-ui-react'
import { Input } from 'react-input-component';
import { Collapse, Button} from 'reactstrap';
import styled from 'styled-components';
function searchingForName(search){
return function(x){
return x.firstName.toLowerCase().includes(search.toLowerCase()) || x.lastName.toLowerCase().includes(search.toLowerCase()) || !search ;
}
}
class Home extends Component {
constructor(props){
super(props);
this.state = {
results:[],
search:'',
collapse:false,
newSearch:'',
tags:[],
isExpandable:false,
activeIndex:0
}
this.onchange = this.onchange.bind(this);
this.toggle = this.toggle.bind(this);
this.inputKeyDown = this.inputKeyDown.bind(this);
// this.handleKeyPress = this.handleKeyPress.bind(this);
}
onchange = e => {
console.log(this.state.search)
this.setState({search:e.target.value});
}
// handleKeyPress = e => {
// if(e.key === 'Enter'){
// this.setState({newSearch: e.target.value});
// }
// }
inputKeyDown = (e) => {
const val = e.target.value;
if(e.key === 'Enter' && val){
if (this.state.tags.find(tag => tag.toLowerCase() === val.toLowerCase())) {
return;
}
this.setState({tags: [...this.state.tags,val]});
this.tagInput.value=null;
}
}
toggle(){
this.setState({collapse: !this.state.collapse});
}
componentDidMount(){
fetch('https://www.hatchways.io/api/assessment/students')
.then(res => res.json())
.then(data => {
console.log(data.students);
this.setState({results:data.students})
}).catch(err => {
console.log(err);
});
}
render() {
return (
<div>
<Card style={{'marginTop':'40px','width':'520px','marginRight':'auto','marginLeft':'auto'}}>
<Card.Content>
<Input
style={{'width':'519px'}}
placeholder="Search by name..."
onChange={this.onchange}
/>
<Input
style={{'width':'519px'}}
placeholder="Search by tags..."
onChange={this.onchange}
/>
{this.state.results.length ?
this.state.results.filter(searchingForName(this.state.search)).map((value,i) => (
<Feed>
<Feed.Event style={{'margin':'10px'}}>
<Image>
<Feed.Label image={value.pic} />
</Image>
<div style={{'float':'right'}}>
{!this.state.collapse ?
<Button onClick={this.toggle}>+</Button>
: <Button onClick={this.toggle}>-</Button>}
</div>
<Feed.Content style={{'textAlign':'center','marginBottom':'10px'}}>
<Feed.Summary><strong>{value.firstName.toUpperCase()} {value.lastName.toUpperCase()}</strong></Feed.Summary>
<Feed.Summary>Email: {value.email}</Feed.Summary>
<Feed.Summary>Company: {value.company}</Feed.Summary>
<Feed.Summary>Skill: {value.skill}</Feed.Summary>
<Feed.Summary>Average : {value.grades.map((x,i,arr)=> {
return x/arr.length;})
.reduce((a,b) => {
return a + b;
}) + "%"}
</Feed.Summary><br />
<Collapse isOpen={this.state.collapse}>
<Feed.Summary>
{Array.isArray(value.grades) && value.grades.map(val => {
return <div>Test {value.grades.indexOf(val)} : {parseFloat(val) + "%"}</div>
})}
</Feed.Summary><br />
{this.state.tags.map((tag,index) => (
<div>
<span className="addTag"key={index}>{tag}</span>
</div>
))}<br />
<input
type="text"
onKeyDown={this.inputKeyDown}
ref={c => { this.tagInput = c; }}
placeholder="add a tag..."
/>
{/* <div>{this.state.newSearch}</div><br />
<Input
style={{'width':'200px'}}
placeholder="add a tag..."
value={this.state.newSearch}
onKeyPress={this.handleKeyPress}
/> */}
</Collapse>
<hr/>
</Feed.Content>
</Feed.Event>
</Feed>
)) : ''}
</Card.Content>
</Card>
</div>
)
}
}
const Image = styled.div`
border: 1px solid #001;
border-radius: 60px;
overflow:hidden;
padding:18px;
height:90px;
width: 90px;
margin-top:30px;
margin-right:auto;
margin-left:auto;
margin-bottom:20px;
`
export default Home;
What is causing them all to expand/collapse at once and how can i change that to only expand/collapse the button is clicked?
Your main problem is that you don't have isOpened card for every student. You can only open all or none with one single collapse state. I have updated your code with solution here:
https://codesandbox.io/s/peaceful-kapitsa-tr9yn
I have changes toggle function, which takes index as parameter and updates single students card status - isOpened true or false.
toggle(index) {
const results = this.state.results.map((item, idx) => {
if (index === idx) {
return {
...item,
isOpened: !item.isOpened
};
}
return item;
});
this.setState({ results });
}
When you load all students data from API endpoint, you have to map through all items and add default isOpened state (by default I've added false - closed).
componentDidMount() {
fetch("https://www.hatchways.io/api/assessment/students")
.then(res => res.json())
.then(data => {
console.log(data.students);
const results = data.students.map(student => {
return {
...student,
isOpened: false
};
});
this.setState({ results });
})
.catch(err => {
console.log(err);
});
}
In render() method I have updated every item to check not this.state.collapse, but student.isOpened on Collapse component and toggle button.
Toggle button
<div style={{ float: "right" }}>
{!value.isOpened ? (
<Button onClick={() => this.toggle(i)}>+</Button>
) : (
<Button onClick={() => this.toggle(i)}>-</Button>
)}
</div>
Collapse component
<Collapse isOpen={value.isOpened}>
...
</Collapse>