Conditional output in dropdown component - javascript

I want to build a dropdown menu showing teachers who teach certain instruments (see image below)
The dropdown component is this:
import * as React from 'react'
import { useState, useEffect } from "react"
import { useStaticQuery, graphql } from 'gatsby'
import { BiChevronDown } from "react-icons/bi";
import StaffList from "./StaffList"
const rows = [
{
id: 1,
title: "Verantwortliche",
},
{
id: 2,
title: "Lehrende der Streichinstrumente",
instrument: "streichinstrumente"
},
{
id: 3,
title: "Lehrende der Zupfinstrumente",
},
{
id: 4,
title: "Lehrende des Tasteninstruments",
},
{
id: 5,
title: "Lehrende des Gesangs",
},
{
id: 6,
title: "Lehrende des Schlagzeugs",
},
{
id: 7,
title: "Lehrende des Akkordeons",
},
{
id: 8,
title: "Lehrende der Musiktheorie",
},
{
id: 9,
title: "Lehrende der Früherziehung",
}
]
class DropDownRows extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: false};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div className="dropdown-rows">
{rows.map(row => (
<div key={row.id}>
<div className="row">
<div className="col">{row.title}</div>
<div className="col">
<BiChevronDown
onClick={this.handleClick}
style={{float: "right"}}/>
</div>
<div>
</div>
</div>
{this.state.isToggleOn ? <StaffList /> : ''}
</div>
))}
</div>
)
}
}
export default DropDownRows
it uses this StaffList component:
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { MDXProvider } from "#mdx-js/react"
function StaffList({ data }) {
return(
<StaticQuery
query={graphql`
query staffQuery {
allMdx {
edges {
node {
excerpt(pruneLength: 900)
id
body
frontmatter {
title
description
featuredImage {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
)
}
}
}
}
}
}
}
`}
render={data => (
<div className="staff-container">
{data.allMdx.edges.map(edge => (
<article>
<div className="staff-image-container">
<GatsbyImage key={edge.node.id} alt='some alt text' image={getImage(edge.node.frontmatter.featuredImage)} style={{margin: "0 auto", padding: "0"}} />
</div>
<div style={{margin: "0 2em"}}>
<div>
<h4 key={edge.node.id} style={{margin: "0"}}>{edge.node.frontmatter.title}</h4>
<h5>{edge.node.frontmatter.description}</h5>
</div>
<p><MDXProvider>{edge.node.excerpt}</MDXProvider></p>
</div>
</article>
))}
</div>
)}
/>
)
}
export default StaffList
this is one of the .mdx files i source my data from:
---
title: Diana Abouem à Tchoyi
featuredImage: Foto_07.jpg
description: Violine, Streicherklassen
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Right now the component looks like this:
How would I conditionally render the content for each bar. so not all bars opening with the same content but just the one clicked showing only those teachers who fall in the selected category. My idea is to have some sort of conditional statement, comparing the selected bar title with maybe a field in the teachers markdown file? Like
title: Hanna
category: Streichinstrumente
and then if (allMdx.edges.node.frontmatter.category === rows.title) {...}
That's as far as I can wrap my head around this. Maybe someone can help? Thank you in advance.

I would have one array of objects storing teachers' datas. Example below.
const teacherData = [
{ id: 'piano',
name: "Alex",
otherData: ...
},
... (similarly for the rest)
]
Then another array of objects/records for instrument-teachers. Such as:
const records = [
{
id: 'piano',
teacher: []
}
]
After this I would loop through each element in the teacherData array to get the finalized record Array with id being the type of instrument and teachers being all the teachers that play that type of instruments.
Now, getting into the display part. I would write my component as below:
const Dashboard = () => {
const [show, setShow] = React.useState([])
function handleClick(id) {
if show.includes(id) {
const newShow = show.filter(a => a !== id)
setShow(newShow)
} else {
const newShow = show.push(id)
setShow(newShow)
}
}
return (
<div className="dashboard">
{records.map((record) =>
(
<div className="instrument-wrapper">
<p onClick={() => handleClick(record.id)} >{record.id}</p> //The name of the instrument
{show.includes(record.id) ? (
<div>
... all the teachers here by mapping through the record.teachers
</div>
) : (null)}
<div>
)
)}
</div>
)
}

Related

React.js: how do i render a particular category of data in a component from an array?

i am building a restaurant website in which i have an array of all the items in their menu, looks like this -
const CardData = [
{
index: 1,
imagesrc: "require(./assets/menu/nonveg)",
title: "Tandoori Chicken",
group: "non-veg",
discprice: 400,
price: 450,
},
{
index: 2,
imagesrc: "require(./assets/menu/nonveg)",
title: "Momos Steamed",
group: "veg",
discprice: 80,
price: 100,
},
{
index: 3,
imagesrc: "require(./assets/menu/nonveg)",
title: "Non-Veg Momos Fried",
group: "non-veg",
discprice: 100,
price: 150,
}]
In a parent component i have different child "menu" components. In each menu component i want to render a different category of data.
The parent component looks like-
import React, { useState } from "react";
import CardMenu from "./CardMenu";
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu/>
<CardMenu/>
<CardMenu/>
</div>
);
};
export default Menu;
and the child component looks like -
'''
import Tag from "./Tag";
import { useState } from "react";
const CardMenu = ({ menudata }) => {
return (
<>
<div className="menu">
<Tag />
<div className="scroll-menu">
{
menudata.map((curElem) => {
const { index, title, group, imagesrc, price, disprice } = curElem;
return (
<>
<div className="card" key={index}>
<div className="card-upper">
<div className="card-image">
<img
src={require("../assets/Duggarhutz/nonveg/nonvegfriedmomos.jpeg")}
alt=""
/>
</div>
<div className="card-info">{index}</div>
</div>
<div className="card-mid">
<div className="card-title">{title}</div>
<div className="card-icons"></div>
<div className="card-desc">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Aliquam, vero. Provident nesciunt magni aliquid omnis eveniet
architecto optio reprehenderit at itaque aut?
</div>
</div>
<div className="card-bottom">
<div className="card-price">${price}</div>
<div className="card-descprice">${disprice}</div>
<div className="card-button">
<button>Order</button>
</div>
</div>
</div>
</>
)
})
}
</div>
</div>
</>
)
};
export default CardMenu;
---for each group i want to render a new Cardmenu with cards related to it.---
Pass in the cardData to your cardMenu like this <CardMenu cardData />
Or <CardMenu cardData=cardData />
As you are destructuring the props in CardMenu with a property menudata you need to pass a property with the same name as this:
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu menudata={CardData}/>
</div>
);
};
i write your solution and give a suggestion to this situation:
solution- filter data from js methods:
const data = [
{
categoryName: "Banking",
categoryId: "B1",
description: "Financial sector"
},
{
categoryName: "Retail",
categoryId: "R1",
description: "Retail and customer experience"
}
];
function groupByProperty(arrayOfObjects, property) {
return arrayOfObjects.reduce((acc, curr) => {
const key = curr[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(curr);
return acc;
}, {});
}
const dataByCategoryName = groupByProperty(data, "categoryName");
console.log(dataByCategoryName);
/* Output
{
Banking: [{
categoryId: "B1",
categoryName: "Banking",
description: "Financial sector"
}],
Retail: [{
categoryId: "R1",
categoryName: "Retail",
description: "Retail and customer experience"
}]
}
*/
suggestion- you can write a search bar and filter cards by client search (realtime filtering).
you can use my sample:
enter link description here
Since you are already mapping the data inside the CardMenu component, you should:
Import CardData from the corresponding file (I suppose it is in the same directory, so probably:
import CardData from "./CardData";
Remove the extra calls to CardMenu:
<div className="group">
<h1>Our Collection</h1>
<CardMenu />
</div>
Pass CardData as a prop menudata to CardMenu:
<CardMenu menudata={CardData} />
The final code of Menu would look something like this:
import React, { useState } from "react";
import CardMenu from "./CardMenu";
import CardData from "./CardData";
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu menudata={CardData} />
</div>
);
};
export default Menu;

What causes this "maximum call stack size exceeded" error?

I am working on a Vue 3 and Bootstrap 5 app. I needed a date-picker and I choose Vue 3 Datepicker.
In components\Ui\Datepicker.vue I have:
<template>
<datepicker
#selected="handleSelect"
v-model="dateSelected"
:upper-limit="picked_to"
:lower-limit="picked_from"
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
In components\Ui\Navigation.vue I have:
import Datepicker from './Datepicker'
export default {
inject: ['$apiBaseUrl'],
name: 'Navigation',
components: {
Datepicker,
},
data() {
return {
// more code
}
},
methods: {
setDate() {
this.$emit('setDate');
}
},
}
In components\Content.vue I have:
<template>
<div class="main">
<div class="d-sm-flex>
<h1>{{ title }}</h1>
<Navigation
#setDate='setDate'
/>
</div>
<div class="content">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
</div>
</div>
</template>
<script>
import Navigation from './Ui/Navigation'
export default {
inject: ['$apiBaseUrl'],
name: 'Content',
components: {
Navigation,
},
props: {
title: String,
},
emits: ['setDate'],
data() {
return {
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
},
from: '',
to: '',
}
},
methods: {
sendData() {
this.axios.post(`${this.$apiBaseUrl}/submit`, this.fields, {options: this.headers}).then((response) => {
if (response.data.code == 200) {
this.isReport = true;
}
}).catch((errors) => {
this.errors = errors.response.data.errors;
});
}
},
setDate() {
console.log('Date');
},
}
}
</script>
The problem
Although I select a date from the datepicker, the setDate() method is not executed. The Chrome console shows instead:
Maximum call stack size exceeded
Where is my mistake?
As comments have mentioned previously, the error usually occurs when having an infinite loop.
As Kissu pointed out this can easily happen if you have an event that is emitted to a parent component which then changes data that is passed as props to a child component which then triggers an event to the parent and so on.
In the code you showed I can't really find any loops, they might be in the parts you omitted.
Edit:
The infinite loop could actually be caused by the naming of your component.
You're using <datepicker ...> inside Datepicker.vue without registering the Datepicker of vue3-datepicker there explicitly.
That probably causes vue to recursively try to mount the component in itself causing the maximum stack trace error, but that's just a guess.
But there's a couple issues in your code still.
First off:
<template>
<datepicker
#selected="handleSelect" // <-- according to the docs there are no events that the datepicker emits
v-model="dateSelected"
:upper-limit="picked_to" // <-- where does the picked_to value come from?
:lower-limit="picked_from" // <-- where does the picked_from value come from?
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
// do not mix options & composition api
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
You're mixing options and composition API, which is never a good idea.
There's also a couple values that seemingly come from nowhere and you're listening to a #selected event from the datepicker, which according to the docs doesn't exist, so you'll have to watch for changes in the selected date yourself.
The same component in the composition API would look like this:
<template>
<Datepicker v-model="initialDate" class="datepicker text-center" />
</template>
<script>
import Datepicker from "vue3-datepicker";
import { ref, watch } from "vue";
export default {
components: { Datepicker },
setup(props, { emit }) {
const initialDate = ref(new Date());
watch(initialDate, (newDate) => {
emit("setDate", newDate);
});
return { initialDate };
},
};
</script>
I tried to recreate the example you gave in a sandbox and I do not encounter any infinite loop issues.
You might wanna check it out and compare with your code and possibly fix all the other issues first and see if that helps your situation :)

Check if string has unicode '\u25CF'

I want to detect a unicode and format it to next line. To make it look bulleted.
Here's the sample text that renders from the database.
I'm using AntD and ReactJS.
I'm thinking if I can use this toString.replace()
const renderColumn = (text, record, index, rowKey, cardKey) => {
return (
<Form.Item>
{getFieldDecorator(`${rowKey}-${cardKey}-${index}`, {
initialValue: text, // THIS IS WHERE THE TEXT RENDER
rules: [
{
required: true,
message: '*Please fill out this field!',
},
],
}
</Form.Item>
)
}
In this example I used split() and join() methods.
I hope this code works for you.
Example:
var mssg = document.querySelector('div').innerHTML;
mssg = mssg.split('●').join("<br>●");
document.querySelector('div').innerHTML = mssg;
<div>
● Lorem ipsum dolor ● Sit amet consectetur adipisicing elit ● Sapiente eum, ut quas accusantium quasi
</div>

How to get section to become sticky until the user has scrolled a specific length

I'm trying to replicate something similar to the Postmates fleet website, where they use position: sticky on a section and change elements within that section, until the user has scrolled through all the content.
Here's an example of what I mean:
So far I have set up a ref on the section I want to make sticky:
ref={(r) => this.ref = r}
...
/>
And then I get the height of the container on page load:
componentDidMount() {
if (this.ref) {
this.setState({ targetY: this.ref.getBoundingClientRect().bottom },
// tslint:disable-next-line:no-console
() => console.log(this.state, 'state'))
}
window.addEventListener('scroll', this.handleScroll)
}
After which I detect the scrolling of the page and know when the section is in view:
handleScroll(e) {
const scrollY = window.scrollY + window.innerHeight;
const { lockedIntoView, targetY } = this.state;
// tslint:disable-next-line:no-console
console.log(scrollY, "scrollY");
if (scrollY >= targetY) {
this.setState({ lockedIntoView: true }, () => {
// tslint:disable-next-line:no-console
console.log(`LockedIntoView: ${lockedIntoView}`);
});
} else {
this.setState({ lockedIntoView: false }, () => {
// tslint:disable-next-line:no-console
console.log(`LockedIntoView: ${lockedIntoView}`);
});
}
}
Before setting the container to sticky position:
<section
...
style={{position: lockedIntoView ? "sticky" : ""}}
...
</>
I would like to know how to make it like the postmates website, where the section just remains in full view of the screen until the content has been scrolled (or for right now, until the user has scrolled a specified height)?
Or just an idea of how they've done it, and what steps I need to take in order to replicate it?
Here's my Codesandbox
Few things to consider:
1) When you set the target to position: fixed, you are removing it from the dom flow so you need to compensate by adding some height back to the dom - example below does this on the body.
2) You need to factor in the height of the target when checking scrollY.
3) When you pass your target height, you add the target back into the dom flow and remove the additional height added in step 1.
4) You need to keep track if we scrolling up or down - this is done but comparing the last scroll position and the current.
See comments inline below for a rough example.
styles.css:
.lockedIntoView {
position: fixed;
top: 0;
}
index.js
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
interface IProps {
title: string;
content: string;
}
interface IHeroState {
targetY: number;
lockedIntoView: boolean;
}
let lastScrollY = 0;
class App extends React.Component<IProps, IHeroState> {
ref: HTMLElement | null;
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
this.state = {
lockedIntoView: false,
targetY: 0
};
}
componentDidMount() {
if (this.ref) {
this.setState(
{ targetY: this.ref.getBoundingClientRect().bottom },
// tslint:disable-next-line:no-console
() => console.log(this.state, "state")
);
}
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll(e) {
const scrollY = window.scrollY + window.innerHeight;
const { lockedIntoView, targetY } = this.state;
if (lockedIntoView) {
console.info("we locked");
// update the padding on the doc as we now removing the target from normal flow
window.document.body.style.paddingBottom = `${this.ref.getBoundingClientRect().height}px`;
// we passed the taret so reset - we have to factor the target height in the calc
if (scrollY > targetY + this.ref.getBoundingClientRect().height) {
window.document.body.style.paddingBottom = "0px";
this.setState({ lockedIntoView: false });
}
} else {
// if we scrollign down and at the target, then lock
if (
scrollY > lastScrollY &&
(scrollY >= targetY &&
scrollY < targetY + this.ref.getBoundingClientRect().height)
) {
console.info("we locked");
this.setState({ lockedIntoView: true });
}
}
// update last scroll position to determine if we going down or up
lastScrollY = scrollY;
}
render() {
const { lockedIntoView, targetY } = this.state;
const fixed = lockedIntoView ? "lockedIntoView" : "";
return (
<div className="App">
<div className="bg-near-white min-vh-100 ">
<h1>First section</h1>
</div>
<div
ref={r => (this.ref = r)}
className={`vh-100 bg-blue pa0 ma0 ${fixed}`}
>
<h2>
When this is in full view of the window, it should remain fixed
until the window has scrolled the full length of the window
</h2>
</div>
<div style={{ height: "300vh" }} className="bg-near-white min-vh-100 ">
<h2>The next section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
codesandbox demo

what does bind(this) means?

I already know that what bind do, it bound your given object or function to the function you want, but bind(this) is really confusing me.What does this in bind really means.
Below is the code from my react app with firebase Database.
componentWillMount: function() {
this.firebaseRef = firebase.database().ref('todos');
this.firebaseRef.limitToLast(25).on('value', function(dataSnapshot) {
var items = [];
dataSnapshot.forEach(function(childSnapshot) {
var item = childSnapshot.val();
item['key'] = childSnapshot.key;
items.push(item);
}.bind(this));
this.setState({
todos: items
});
}.bind(this));
},
bind(this) here binds the context of your function inside forEach() to the scope of the componentWillMount().
this here refers to the the scope of componentWillMount().
With bind(this), this keyword inside the inner function will refer to the outer scope.
This is essential because in this case this.setState inside the forEach function can be called as its scope is limited to componentWillMount().
According to the docs:
The bind() method creates a new function that, when called, has its
this keyword set to the provided value, with a given sequence of
arguments preceding any provided when the new function is called.
Check out this demo which illustrates the usage of bind(this).
class App extends React.Component {
constructor(){
super();
this.state = {
data: [{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}]
}
}
componentDidMount() {
this.state.data.forEach(function(item) {
console.log('outer scope ', this);
}.bind(this))
this.state.data.forEach(function(item) {
console.log('Inner scope ', this);
})
}
render() {
return (
<div>Hello</div>)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
<div id="app"></div>
You didn't show full React component definition, but it most probably refers to React component instance where your componentWillMount is defined.
You can play nice with this inside forEach - according to the https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach - instead of binding pass this as second argument for forEach statement.
class App extends React.Component {
constructor() {
super();
this.state = {
data: [{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}]
}
}
componentDidMount() {
this.state.data.forEach(function(item) {
console.log('outer scope ', this);
}, this) // be nice for context - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
}
render() {
return ( <div> Hello < /div>)
}
}
ReactDOM.render( < App / > , document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
<div id="app"></div>

Categories

Resources