Next js - Setting DOM element scrollHeight in parent component from child component - javascript

I have created an accordion within an accordion
/pages/profile.js passes all the data the first component /components/loved_one.js which renders the first DOM container. When the container button is clicked, the toggleAccordion function sets the scrollheight of the ref content (the container of the accordion within). Which works great as none of the elements within are expanded, and so the height is calculated.
The accordion within /components/event.js does the same thing. However this now expands and the parent accordion container above is now too small.
If I collapse and expand the parent accordion, it works, as the inner elements are expanded and so can set the correct height again.
So I'm trying to find a way, when I set the height of the inner accordion container, I also trigger the parent accordion container to re-address it's height.
Anyone know how I could do this? Appreciate any help.
Here's the code: -
/pages/profile.js
export const Profile = ({ loved_ones, events, messages, mementos }) => {
const [session, loading] = useSession();
if (loading) return <div>loading...</div>;
if (!session) return <div>no session</div>;
return (
<Layout>
{session && (
<>
<h1>{session.user.name}</h1>
</>
)}
{loved_ones.map((loved_one, index) => (
<LovedOne
key={index}
id={loved_one.id}
firstname={loved_one.firstname}
surname={loved_one.surname}
email={loved_one.email}
events={events}
messages={messages}
mementos={mementos}
/>
))}
<style jsx>{`
.avatar {
width: 220px;
border-radius: 10px;
}
`}</style>
</Layout>
);
};
export async function getServerSideProps(req, res) {
const session = await getSession(req);
const prisma = new PrismaClient({
log: ["query", "info", "warn"],
});
if (!session) {
return {
props: {},
redirect: {
destination: "/login",
permanent: false,
},
};
}
const loved_one = await prisma.loved_one.findMany({
where: {
user_id: session.user.user_id,
},
});
const events = await prisma.events.findMany({
where: {
user_id: session.user.user_id,
},
});
const messages = await prisma.messages.findMany({
where: {
user_id: session.user.user_id,
},
});
const mementos = await prisma.mementos.findMany({
where: {
user_id: session.user.user_id,
},
include: {
package_size: true,
},
});
const stringifiedData = JSON.stringify(loved_one);
const loved_ones_data = JSON.parse(stringifiedData);
const stringifiedDataEvents = safeJsonStringify(events);
const events_data = JSON.parse(stringifiedDataEvents);
const stringifiedDataMessages = safeJsonStringify(messages);
const messages_data = JSON.parse(stringifiedDataMessages);
const stringifiedDataMementos = safeJsonStringify(mementos);
const mementos_data = JSON.parse(stringifiedDataMementos);
return {
props: {
loved_ones: loved_ones_data,
events: events_data,
messages: messages_data,
mementos: mementos_data,
},
};
}
export default Profile;
/components/loved_one.js
export const LovedOne = (props) => {
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("0px");
const [setIcon, setIconState] = useState("fas fa-plus");
const content = useRef();
function toggleAccordion() {
setActiveState(setActive === "" ? "active" : "");
setHeightState(
setActive === "active" ? "0px" : `${content.current.scrollHeight}px`
);
setIconState(setActive === "active" ? "fas fa-plus" : "fas fa-minus");
}
return (
<>
<div className="item-container teal lighten-5">
<div className="row item teal darken-1">
<div className="col m1">
<button className={`expand ${setActive}`} onClick={toggleAccordion}>
<i className={`${setIcon} white-text small`}></i>
</button>
</div>
<div className="col m4 item-title">
<span className="teal-text text-darken-4">Loved One: </span>
{props.firstname} {props.surname}
</div>
<div className="col m5 item-title">
<span className="teal-text text-darken-4">Email: </span>
{props.email}
</div>
<div className="col m1 offset-m1">
<button className="btn-floating btn-small waves-effect waves-light">
<i className="fas fa-trash-alt white-text small"></i>
</button>
</div>
</div>
<div
className="item-content"
ref={content}
style={{ maxHeight: `${setHeight}` }}
>
{props.events.map((loved_one_event, index) => {
if (props.id === loved_one_event.loved_one_id)
return (
<Event
key={index}
id={loved_one_event.id}
loved_one_id={loved_one_event.loved_one_id}
date={loved_one_event.date}
anniversary={loved_one_event.anniversary}
messages={[props.messages]}
mementos={props.mementos}
/>
);
})}
</div>
</div>
</>
);
};
export default LovedOne;
/components/events.js
export const Event = (props) => {
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("0px");
const [setIcon, setIconState] = useState("fas fa-plus");
const content = useRef();
const [startDate, setStartDate] = useState(new Date());
function toggleAccordion() {
setActiveState(setActive === "" ? "active" : "");
setHeightState(
setActive === "active" ? "0px" : `${content.current.scrollHeight}px`
);
setIconState(setActive === "active" ? "fas fa-plus" : "fas fa-minus");
}
return (
<>
<div className="item-container teal lighten-4">
<div className="row event teal lighten-1">
<div className="col m1">
<button className={`expand ${setActive}`} onClick={toggleAccordion}>
<i className={`${setIcon} white-text small`}></i>
</button>
</div>
<div className="col m4 item-title">
<span className="teal-text text-darken-4">Event Date: </span>
</div>
<div className="col m3 item-title">
<span className="teal-text text-darken-4">Anniversary: </span>
{props.anniversary}
</div>
<div className="col m1 offset-m2">
<button className="btn-floating btn-small waves-effect waves-light">
<i className="fas fa-trash-alt white-text small"></i>
</button>
</div>
</div>
<div
className="event-content"
ref={content}
style={{ maxHeight: `${setHeight}` }}
>
{props.messages[0].map((message, index) => {
if (props.id === message.event_id)
return (
<Message
key={index}
id={message.id}
event_id={message.event_id}
content={message.content}
/>
);
})}
</div>
<div
className="event-content"
ref={content}
style={{ maxHeight: `${setHeight}` }}
>
{props.mementos.map((memento, index) => {
if (props.id === memento.event_id)
return (
<Memento
key={index}
id={memento.id}
event_id={memento.event_id}
package_size={memento.package_size}
/>
);
})}
</div>
</div>
</>
);
};
export default Event;

Found what to do. I needed to set the height to auto, rather than the maxHeight
.item-content {
overflow: hidden;
height: 0px;
transition: max-height 0.2s ease-out;
}
function toggleAccordion() {
setActiveState(setActive === "" ? "active" : "");
setHeightState(setActive === "active" ? "0px" : `auto`);
setIconState(setActive === "active" ? "fas fa-plus" : "fas fa-minus");
}
return (
<>
<div className="item-container teal lighten-5">
<div className="row item teal darken-1">
<div className="col m1">
<button className={`expand ${setActive}`} onClick={toggleAccordion}>
<i className={`${setIcon} white-text small`}></i>
</button>
</div>
<div className="col m4 item-title">
<span className="teal-text text-darken-4">Loved One: </span>
{props.firstname} {props.surname}
</div>
<div className="col m5 item-title">
<span className="teal-text text-darken-4">Email: </span>
{props.email}
</div>
<div className="col m1 offset-m1">
<button className="btn-floating btn-small waves-effect waves-light">
<i className="fas fa-trash-alt white-text small"></i>
</button>
</div>
</div>
<div
className="item-content"
ref={content}
style={{ height: `${setHeight}` }}
>

Related

how can I set a class in a specific item in react?

When I click on the text in 'to do app completed' task applied for all tasks while I wanna set this class (completed).
to a target when I click on it
const [status, setStatus] = useState(false);
const toggleClass = (value) => {
setStatus(!status);
};
return (
<div>
<button
className={styles.addBtn}
onClick={addTask}
style={{ padding: "0 .5rem" }}
>
{plusIcon}
</button>
{tasks.map((task, id) => (
<div key={id} className={styles.todo}>
<span
className={status ? styles.completed : null}
onClick={toggleClass}
>
{id + 1} : {task}
</span>
<i className={styles.trashIcon} onClick={() => removeTask(task)}>
{faTrashs}
</i>
</div>
))}
<i className={styles.hint}>{`you have ${tasks.length} tasks to do`}</i>
</div>
);

React forcing a full re-render on button click

Whenever I try to click on a button which essentially populates a modal and displays it, the whole page re-renders and everything is set back to default. I tried to use React dev tools profiler but it wasn't working. Can somebody tell me what I'm doing wrong? I haven't been able to figure out what's wrong here.
Here's my code
import React, { useState } from "react";
import Box from "#mui/material/Box";
import Modal from "#mui/material/Modal";
import "./rewards.css";
const json //some api response
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
border: "2px solid #000",
boxShadow: 24,
p: 4,
};
const Rewards2 = () => {
const [showModal, setModal] = useState(false);
const [modalContent, setModalContent] = useState({
modalHeader: "",
modalImg: "",
modalContent: { content: "", terms: [], redemptionSteps: [] },
});
//const [showModal, setModal] = useState(false);
//const [s]
// onClick={handleOpen(d.name, d.img)}
const handleClose = () => setModal(false);
const handleOpen = () => setModal(true);
return (
<div id="content">
<div className="content-top">
<ul>
{json.reward_partners.map(
(partner, index) => {
// eslint-disable-next-line
return (
<div
className="row"
key={index}
>
<div className="col-md-3 col-xs-3">
<div className="widget-thumb coupon_desc">
<img
src={
partner.img
}
className="mainpnplogo1"
alt="partnership-logo"
/>
</div>
</div>
<div className="col-md-6 col-xs-6">
<div className="coupon_desc">
<div>
{
partner.content
}
</div>
</div>
</div>
<div className="col-md-3 col-xs-3">
<div className="text-center coupon_desc">
THIS IS THE BUTTON LOGIC
<a
href=""
className="theme-button theme-button-small theme-red w-btn-nocase w-btn-white knowmore-btn"
data-toggle="modal"
data-target="#couponModel"
onClick={() => {
setModalContent(
partner.modal
);
handleOpen()
console.log(
"inside onCLick",showModal
);
}}
></a>
</div>
</div>
</div>
);
}
)}
</ul>
{/* <Modal open={showModal} onClose={handleClose}>
<Box sx={style}>
<h1>{modalContent.modalHeader}</h1>
<img
src={modalContent.modalImg}
alt={modalContent.modalHeader}
/>
<p>{modalContent.modalContent.content}</p>
</Box>
</Modal> */}
<Modal
className="modal fade"
id="couponModel"
tabIndex="-1"
role="dialog"
aria-labelledby="exampleModalLabel"
open={showModal}
onClose={handleClose}
>
<Box sx = {style}>
<div
className="modal-dialog model-lg"
role="document"
>
<div
className="modal-content"
id="modalBody"
>
<div className="modal-header">
<button
type="button"
className="close"
data-dismiss="modal"
>
×
</button>
<h4 className="modal-title">
<span className="">
<b>
{
modalContent.modalHeader
}
</b>
</span>
</h4>
</div>
</div>
</div>
</Box>
</Modal>
</div>
</div>
);
};
export default Rewards2;
P.S please do ignore the redundant divs, I cut out a lot of content to show only the relevant parts.
EDIT: Highlighted the Button Logic
The issue here is with <a /> element. You can use the button instead of <a /> as on click anchor tag execute its basic behavior.
Here's a comparison between <a/> and <button /> element. Codesandbox.
export default function App() {
const handleClick = () => {
console.log("clicked");
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<a href="" onClick={handleClick}>
Anchor
</a>
<button onClick={handleClick}>button</button>
</div>
);
}
EDIT
If you have to use the <a /> element then you can add event.preventDefault() to prevent the default behaviour of <a/> tag.
Codesandbox
export default function App() {
const handleClick = (e) => {
e.preventDefault();
console.log("clicked");
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<a href="" onClick={handleClick}>
Anchor
</a>
<button onClick={handleClick}>button</button>
</div>
);
}

".map is not a function" error in react.js

I have developed a basic todo app using react-dnd. I am trying to get the details from the database and store it in the state. And by using the state I want to map the values retrieieved from the database.
I tried looking into the output using console.log statement, and it was something like this.
However when I try to use the map function like this:
{state.map((item, index) => (
<Draggable
key={item._id}
draggableId={item._id + ""}
index={index}
>
{console.log(item._id, "KEY")}
{(provided) => (
<li
className="list-group-item d-flex justify-content-between align-items-start align-items-center"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div className="ms-2 me-auto">
<span className="ml-3 mr-2">
<input
type="checkbox"
className="form-check-input"
id={index}
value={index}
checked={item.selected}
onChange={(e) => {
const target = e.target.value;
const val = state[target].selected;
// console.log(val);
let tmp = Array.from(state);
tmp[target].selected = !val;
// console.log(tmp[target]);
setState(tmp);
// console.log(state);
}}
/>
</span>
<span className="font-weight-bold">{item.title}</span>
</div>
<span>
{/* <i className="bi bi-trash text-danger" /> */}
<button
className="btn btn-sm"
onClick={(e) => {
e.preventDefault();
handleDelete(item._id, index);
}}
>
<i className="bi bi-trash text-danger" />
</button>
</span>
</li>
)}
</Draggable>
Its showing an error as "state.map" is not a function
However I also tried using "state.stage.map()" . Afterwards its showing an error as state.stage is undefined. Is there any proper way of using this .map function?
I have attached the full code here below.
function ProjectsDashboard() {
useEffect(() => {
preload();
}, []);
const [state, setState] = useState([]);
const [todo, setTodo] = useState("");
const [loading, setLoading] = useState(false);
const preload = async () => {
setLoading(true);
try {
const res = await axios.get("/stages");
setState(res.data);
console.log(state, "STATE")
console.log(res.data,"DATA")
setLoading(false);
} catch (error) {
alert("Couldn't find any Todos! ");
setLoading(false);
}
};
console.log(state, "STATE")
const lists = () => (
<div className="row">
<div className="col-sm-12 col-md-4 offset-md-2 col-lg-4 offset-lg-2 border border-danger p-4">
<h3 className="text-center mb-4">Draggable List</h3>
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="todos">
{(provided) => (
<ul
className="list-group"
{...provided.droppableProps}
ref={provided.innerRef}
>
{state.stage.map((item, index) => (
<Draggable
key={item._id}
draggableId={item._id + ""}
index={index}
>
{console.log(item._id, "KEY")}
{(provided) => (
<li
className="list-group-item d-flex justify-content-between align-items-start align-items-center"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div className="ms-2 me-auto">
<span className="ml-3 mr-2">
<input
type="checkbox"
className="form-check-input"
id={index}
value={index}
checked={item.selected}
onChange={(e) => {
const target = e.target.value;
const val = state[target].selected;
// console.log(val);
let tmp = Array.from(state);
tmp[target].selected = !val;
// console.log(tmp[target]);
setState(tmp);
// console.log(state);
}}
/>
</span>
<span className="font-weight-bold">{item.title}</span>
</div>
<span>
{/* <i className="bi bi-trash text-danger" /> */}
<button
className="btn btn-sm"
onClick={(e) => {
e.preventDefault();
handleDelete(item._id, index);
}}
>
<i className="bi bi-trash text-danger" />
</button>
</span>
</li>
)}
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
<div
className="col-sm-12 col-md-4 col-lg-4 border border-info p-4"
style={{ marginLeft: "10px" }}
>
<h3 className="text-center mb-4">NON-Draggable List</h3>
<ul className="list-group">
{state.map(
(item, index) =>
item.selected && (
<li className="list-group-item" key={index}>
{item.title}
</li>
)
)}
</ul>
</div>
</div>
);
const handleDragEnd = (result) => {
// console.log(result);
if (!result.destination) return;
const items = Array.from(state);
const [recordedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, recordedItem);
setState(items);
// console.log(state);
};
const handleDelete = async (id, index) => {
try {
await axios.delete(`/api/todo/${id}`);
// preload();
const temp = Array.from(state);
temp.splice(index, 1);
setState(temp);
} catch (error) {
if (`Error! ${error.response.data.err}`) {
alert(error.response.data.err);
} else {
alert(`Error ${error.response.status}: ${error.response.statusText}`);
}
}
};
const addNewTodoForm = () => (
<div className="row">
<div className="col-md-8 offset-md-2 mb-4 form-group">
<label htmlFor="newtodo">Add a New Todo Item</label>
<input
type="text"
className="form-control"
name="newtodo"
id="newtodo"
value={todo}
placeholder="Add New Todo"
required
onChange={handleChange}
/>
<button
className="btn btn-block btn-outline-primary mt-2"
onClick={handleSubmit}
>
Submit
</button>
</div>
</div>
);
const handleChange = (e) => {
e.preventDefault();
setTodo(e.target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (todo === "" || todo === null) {
alert(
"To add a New Todo item, please fill out the 'Add New Todo' field."
);
return;
}
const temp = {
title: todo,
};
try {
const res = await axios.post("/api/todo", temp);
setTodo("");
const tmp = Array.from(state);
tmp.push(res.data);
setState(tmp);
// preload();
} catch (error) {
if (error.response.data && error.response.data.err) {
alert(`Error! ${error.response.data.err}`);
} else {
console.log(error);
alert(`Error ${error.response.status}: ${error.response.statusText}`);
}
}
};
return (
<>
<div className="container">
<h1 className="text-center m-4 font-weight-bold text-uppercase">
DRAG TO DO{" "}
<span className="text-lowercase font-weight-normal">V1.0.1</span>
</h1>
{/* {addNewTodoForm()}*/}
{lists() }
</div>
</>
);
};
export default ProjectsDashboard;
Try this
{state.stage && state.stage.map(.........
This is because upon first render, state is initialized as an empty array. You could either initialize state as:
const [state, setState] = useState({ stage: [] });
Or write a conditional check before calling lists()
{state && state.stage ? lists() : null}

onClick of button triggering all the components to open - Reactjs

I implemented a Card component and basically generating a bunch of cards on some input data. I binded a setter function on button click on every card which basically expands and collapse it. Even after putting unique keys to the div is sort of triggering all the cards to open at once.
Here is the code piece:
import React, { useState } from 'react';
import PrettyPrintJson from './PrettyPrintJson';
import './Card.scss';
import '../App.scss';
const Card = (props) => {
const { data } = props;
const [collapse, toggleCollapse] = useState(true);
return (<div className="card-group">
{data.map((obj, idx)=>{
return <div className="card" key={`${idx}_${obj?.lastModifiedOn}`}>
<div className="card-header">
<h4 className="card-title">{`fId: ${obj?.fId}`}</h4>
<h6 className="card-title">{`name: ${obj?.name}`}</h6>
<h6 className="card-title">{`status: ${obj?.status}`}</h6>
<div className="heading-elements">
<button className="btn btn-primary" onClick={() => toggleCollapse(!collapse)}>Show Json</button>
</div>
</div>
<div className={`card-content ${!collapse ? 'collapse show' : 'collapsing'}`}>
<div className="card-body">
<div className="row">
<PrettyPrintJson data={ obj } />
</div>
</div>
</div>
</div>
})}
</div>
);
}
export default Card;
Create a component that manages it's own state and render that component.
const CardItem = ({ obj }) => {
const [collapse, toggleCollapse] = useState(true);
return (<div className="card">
<div className="card-header">
<h4 className="card-title">{`fId: ${obj?.fId}`}</h4>
<h6 className="card-title">{`name: ${obj?.name}`}</h6>
<h6 className="card-title">{`status: ${obj?.status}`}</h6>
<div className="heading-elements">
<button className="btn btn-primary" onClick={() => toggleCollapse(!collapse)}>Show Json</button>
</div>
</div>
<div className={`card-content ${!collapse ? 'collapse show' : 'collapsing'}`}>
<div className="card-body">
<div className="row">
<PrettyPrintJson data={ obj } />
</div>
</div>
</div>
</div>)
}
then render it like
{data.map((obj, idx)=> (<CardItem obj={obj} key={idx} />))}
I think you can declare a state which is a type of int. After then, you can use the if-statement of index(idx) and state.
Like this:
const [collapsedCardNumbers, toggleCollapseCard] = useState([]);
const addCardNumber = (idx, prevState) => {
const arr_cardNum = prevState
!arr_cardNum .includes(idx) && arr_cardNum .push(idx)
return arr_cardNum
}
...
{data.map((obj, idx)=>{
return <div className="card" key={`${idx}_${obj?.lastModifiedOn}`}>
<div className="card-header">
<h4 className="card-title">{`fId: ${obj?.fId}`}</h4>
<h6 className="card-title">{`name: ${obj?.name}`}</h6>
<h6 className="card-title">{`status: ${obj?.status}`}</h6>
<div className="heading-elements">
<button className="btn btn-primary" onClick={() => toggleCollapseCard(prevState => addCardNumber(idx, prevState))}>Show Json</button>
</div>
</div>
<div className={`card-content ${collapsedCardNumbers.includes(idx) ? 'collapse show' : 'collapsing'}`}>
<div className="card-body">
<div className="row">
<PrettyPrintJson data={ obj } />
</div>
</div>
</div>
</div>
})}

How to build a simple chart in React based on data from API response

I would like to build a simple chart in React using Nivo library to represent percentage values from data in API response.
The data from the response looks like this: https://run.mocky.io/v3/7c46c4ce-09c0-4f95-b4e1-84ae10bd24ab
I need to represent this data in a really simple line chart.
So far I have this code in my component:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import {useParams} from 'react-router-dom';
import Loader from '../Components/Loader';
import { Bar } from "#nivo/bar";
function ArtistDetail() {
const {artist_uuid} = useParams()
const url = `https://run.mocky.io/v3/${artist_uuid}`;
const [artist, setArtist] = useState({
loading: false,
data: null,
error: false
})
let content = null;
useEffect(() => {
setArtist({
loading: true,
data: null,
error: false
})
axios.get(url)
.then(response => {
setArtist({
loading: false,
data: response.data,
error: false
})
})
.catch(() => {
setArtist({
loading: false,
data: null,
error: true
})
})
}, [url])
if(artist.error) {
content = <p>There was an error loading an artist.</p>
}
if(artist.loading) {
content = <p><Loader/></p>
}
if(artist.data) {
content =
<main className="main">
<section className="section section-artist-detail trending claimed">
<div className="page">
<div className="col visual">
<figure>
<img src= {artist.data.data.image}/>
<figcaption>
<button className="btn btn-claim-music-id">Claim music_id</button>
</figcaption>
</figure>
</div>
<div className="col-wrapper">
<div className="col info">
<div className="col-content">
<div className="info-wrapper">
<div className="title-wrapper">
<button className="btn btn-solid border btn-booking-request">Booking Request</button>
<h1 className="title">
{artist.data.data.name}
<div className="tooltip-wrapper">
<span className="profile-claimed">Profile claimed</span>
<div className="tooltip">
<h3>Vote for subgenres</h3>
<p>Don’t agree with the subgenres? Add the ones you think are missing or vote for existing to get yours on top!</p>
<div className="stats-sheet">
{artist.data.data.subgenres.map(subgenre => {
const {name, score} = subgenre;
return (
<div className="row" key={name, score}>
<h5>{name}</h5>
<div className="graph-line">
<span className="line" style= {{width: score + '%'}}>{score}%</span>
</div>
</div>
);
})}
</div>
<p>
<button className="btn btn-shadow">Vote now</button>
</p>
</div>
</div>
<span className="trending-icon">Trending</span>
</h1>
</div>
<div className="row">
<button className="btn btn-save long">Follow</button>
<button className="btn btn-share">
Share
<span>Link copied to clipboard</span>
</button>
</div>
<div className="row">
<label>Origin</label>
<a className="btn btn-filter-tag">{artist.data.data.country.name}</a>
</div>
<div className="row">
<label>Genre</label>
<span className="btn btn-filter-tag">{artist.data.data.genre.name}</span>
</div>
<div className="row">
<label>Subgenres</label>
{artist.data.data.subgenres.map(subgenre => {
const {name} = subgenre;
return (
<span key={name} className="btn btn-filter-tag">{name}</span>
);
})}
<div className="tooltip-wrapper">
<button className="btn btn-add">Add subgenre</button>
<div className="tooltip">
<h3>Vote for subgenres</h3>
<p>Don’t agree with the subgenres? Add the ones you think are missing or vote for existing to get yours on top!</p>
<div className="stats-sheet">
{artist.data.data.subgenres.map(subgenre => {
const {name, score} = subgenre;
return (
<div className="row" key={name, score}>
<h5>{name}</h5>
<div className="graph-line">
<span className="line" style= {{width: score + '%'}}>{score}%</span>
</div>
</div>
);
})}
</div>
<p>
<button className="btn btn-shadow">Vote now</button>
</p>
</div>
</div>
</div>
</div>
<div className="footer-detail">
<ul className="social-list">
{artist.data.data.social_links.map(item => {
const {channel, link} = item;
return (
<li key={channel, link}>
<a href= {link} className= {`btn social-icon ${channel}`}>{channel}</a>
</li>
);
})}
</ul>
<div className="tooltip-wrapper">
<button className="btn btn-add">Add links</button>
<div className="tooltip">
<h3>Got more info?</h3>
<p>Add Place's links so everyone can see their social media highlights.</p>
<p>
<button className="btn btn-shadow">Add links</button>
</p>
</div>
</div>
</div>
</div>
</div>
<div className="col stats">
<div className="col-content">
<Bar
width={600}
height={400}
margin={{ top: 60, right: 80, bottom: 60, left: 80 }}
data={artist.data.data.popularity}
indexBy="city"
keys={["percentage"]}
labelTextColor="inherit:darker(1.4)"
enableGridX={false}
layout="horizontal"
maxValue={10}
axisTop={null}
axisRight={null}
axisBottom={null}
axisLeft={null}
enableGridX={false}
enableGridY={false}
isInteractive={false}
/>
<div className="stats-sheet">
<label>Most popular in</label>
{artist.data.data.popularity.map(popular => {
const {city} = popular;
return (
<div className="row" key={city}>
<h5>{city}</h5>
<div className="graph-line">
<span className="line" style={{width: 47 + '%'}}>47%</span>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
<button className="btn btn-scroll-down">Scroll down</button>
</div>
</section>
</main>
}
return (
<div>
{content}
</div>
)
}
export default ArtistDetail;
I have no idea how to display the percentage data inside this div:
<div className="graph-line">
<span className="line" style={{ width: 47 + "%" }}>
47%
</span>
</div>;
It looks like this
For the chart I used the code from here
Good work!
Based on Nivo documentation, I'd try with this
<ResponsiveBar
data={data.popularity}
keys={[ 'city', 'percentage' ]}
maxValue={100}
...
>
..setting maxValue in or to 100 for percentages. Passing data.popularity or artist.data.data.popularity to data attribute and setting up appropriate keys.
{artist.data.data.popularity.map(popular => {
const {city} = popular;
Here might be the problem - destructure both 'city' and 'percentage' from 'popular'.
const { city, percentage } = popular
then
<span>{percentage}%</span>
Looking at the design instead of Nivo you can actually use simple css .Below is the code in which I have used flex to achieve that.
CodeSandbox Link
<div className="App">
<h1>Most Popular in</h1>
{popularity.map((popular) => (
<div>
<div>{popular.city}</div>
<div className="linear-graph">
<div
style={{
background: "black",
flex: popular.percentage
}}
/>
<div
style={{
background: "#c5c5c56b",
flex: 10 - popular.percentage
}}
/>
</div>
</div>
))}
</div>
and the css for that
.App {
font-family: sans-serif;
color: black;
}
.linear-graph {
width: 200px;
display: flex;
position: relative;
height: 6px;
margin-bottom: 10px;
}
Cheers

Categories