Best way to create expandable menu? - reactjs

What's the best way to create menu like in attached photos ?
I tried to make it with:
const [firstParentActive, setFirstParentActive] = useState(false) // idea is to make useStates for all category parents and category children
const activeHandler = () => {...} // all handlers for setting 'true' in such category parents
<div
className={classes[`FullServices__parent-${active}`]}
onClick={() => activeHandler()}
>
Кузов
</div>
This way seemed to me very uncomfy and not universal. Furthermore I will refactor it all using redux-toolkit
initial state
expanded

There are many ways to use accordion menus
Add following code in src/App.js
import React from 'react';
const App = () => {
const accordionData = {
title: 'Section 1',
content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente
laborum cupiditate possimus labore, hic temporibus velit dicta earum
suscipit commodi eum enim atque at? Et perspiciatis dolore iure
voluptatem.`
};
const { title, content } = accordionData;
return (
<React.Fragment>
<h1>React Accordion Demo</h1>
<div className="accordion">
<div className="accordion-item">
<div className="accordion-title">
<div>{title}</div>
<div>+</div>
</div>
<div className="accordion-content">{content}</div>
</div>
</div>
</React.Fragment>
);
};
export default App;

Related

Next.JS - Gist's embedded in markdown rendered via dangerouslySetInnerHTML only show after full page load, not React route load

I have a series of blog posts stored in MD files, some of these contain multiple Gist embeds in the form of script tags.
The MD contents are rendered to the page via dangerouslySetInnerHTML, and all is fine when the page is navigated to directly. However when the app's routing is used and a full page refresh doesn't happen the script tag is included in the markup, but not executed.
The following example is using the default Next.JS blog example here: https://github.com/vercel/next-learn-starter/tree/master/basics-final.
Markdown:
---
title: "Example of GIST embedding"
date: "2020-02-20"
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut id arcu at arcu pretium porta. Nam feugiat est ut lectus imperdiet venenatis. Ut tempus vitae lectus id vestibulum. Sed tristique est metus. Ut pretium malesuada risus. Maecenas eget diam tristique, sagittis velit ac, efficitur nisi. Quisque lectus lorem, vehicula at mi vitae, dapibus volutpat augue. Sed dignissim pharetra ligula a efficitur. In ultrices imperdiet libero. Quisque ornare erat eu elit ullamcorper faucibus. Maecenas mattis sem a mauris posuere iaculis.
<script src="https://gist.github.com/robearlam/aec15c65aaffbd5ec00a826c5cbe57ad.js"></script>
Etiam sed interdum ligula, nec tincidunt justo. Aliquam erat volutpat. Fusce in scelerisque nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin venenatis lectus at ligula mollis dapibus. Praesent condimentum metus fringilla, commodo enim non, fringilla dui. Vivamus nec ligula lacinia ante semper rhoncus eu sed nisi. In ac dolor vel lorem tincidunt lacinia. Praesent quis mattis mi, at finibus velit. Etiam auctor, magna fermentum tincidunt interdum, nulla augue porttitor enim, ac lobortis felis eros id dui. Suspendisse dignissim, dui sit amet pulvinar iaculis, nisi tellus rhoncus dolor, eu gravida risus massa accumsan magna.
Page
import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'
import Head from 'next/head'
import Date from '../../components/date'
import utilStyles from '../../styles/utils.module.css'
export default function Post({ postData }) {
return (
<Layout>
<Head>
<title>{postData.title}</title>
</Head>
<article>
<h1 className={utilStyles.headingXl}>{postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={postData.date} />
</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
</Layout>
)
}
export async function getStaticPaths() {
const paths = getAllPostIds()
return {
paths,
fallback: false
}
}
export async function getStaticProps({ params }) {
const postData = await getPostData(params.id)
return {
props: {
postData
}
}
}
Lib function
export async function getPostData(id) {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents)
// Use remark to convert markdown into HTML string
const processedContent = await remark()
.use(html)
.process(matterResult.content)
const contentHtml = processedContent.toString()
// Combine the data with the id and contentHtml
return {
id,
contentHtml,
...matterResult.data
}
I also have a repo showing the issue here: https://github.com/robearlam/gist-embedding-issue
Cheers!
I ran into this same problem, I found this post to be very helpful.
Basically, you create a custom code block that formats your code tags inline.
It has you use <ReactMarkdown> component to render your markdown (instead of remark) and then you use the react-syntax-highlighter library in your custom code block component.
When rendering your markdown use:
import CodeBlock from "../../components/codeblock"
<ReactMarkdown components={CodeBlock}>{your markdown data here}</ReactMarkdown>
Importing your custom codeblock component for the markdown processor.
Tom's custom code block example below:
// components/codeblock.js
import React from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import {dracula} from 'react-syntax-highlighter/dist/cjs/styles/prism';
const CodeBlock = {
code({node, inline, className, children, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
style={dracula}
language={match[1]}
PreTag="div" {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}
export default CodeBlock

React JS - JSX returns [object Object] instead of string

Why this JSX hook returns [object Object] [object Object] instead of HTML string?
export default function Article (props) {
const {
content,
} = props;
console.log({content});
return (
<>
<div>
{content.replace(/<p>(.*?)<\/p>/g, (match) => {
return (
<p className="text-black">{match}</p>
);
})}
</div>
</>
)
}
Console.log print:
{
content: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dignissim ac ante nec hendrerit.</p>\n' +
'\n' +
'<p>Morbi rhoncus diam quis sodales iaculis. Morbi ullamcorper consectetur ultricies. Nam vel efficitur erat.</p>'
}
Issue
Why this JSX hook returns [object Object] [object Object] instead of
HTML string?
You are attempting to render the returned of content.replace but the function you use to replace is returning a JSX object instead of a new string. You are also copying the entire match instead of just the captured group.
Solution
It is very unclear to me what you are trying to do, or why, but if you want to modify the raw HTML you can do a string replace, but you need to then use standard HTML (not JSX!!) and set the innerHTML of the div. Here you need to specify class instead of className.
<div
dangerouslySetInnerHTML={{
__html: content.replace(/<p>(.*?)<\/p>/g, (match, p1) => {
return `<p class="text-black">${p1}</p>`;
})
}}
></div>
or a bit more simple without the updater function
<div
dangerouslySetInnerHTML={{
__html: content.replace(/<p>(.*?)<\/p>/g, '<p class="text-black">$1</p>')
}}
></div>
Demo

Called a component on button press which returns modal but it is not visible on screen

I called a component on button press to display modal on screen but it is not working properly.
const handleCardClick=(cardObject)=> {
this.state={
"show":true
}
console.log("pressed")
return (<Modal
show={true}
onHide={() => this.setState({"show":false})}
dialogClassName="modal-90w"
aria-labelledby="example-custom-modal-styling-title"
>
<Modal.Header closeButton>
<Modal.Title id="example-custom-modal-styling-title">
Custom Modal Styling
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
Ipsum molestiae natus adipisci modi eligendi? Debitis amet quae unde
commodi aspernatur enim, consectetur. Cumque deleniti temporibus
ipsam atque a dolores quisquam quisquam adipisci possimus
laboriosam. Quibusdam facilis doloribus debitis! Sit quasi quod
accusamus eos quod. Ab quos consequuntur eaque quo rem! Mollitia
reiciendis porro quo magni incidunt dolore amet atque facilis ipsum
deleniti rem!
</p>
</Modal.Body>
</Modal>
);
}
"pressed" is displayed but I can't see modal on screen.
Thanks in advance
Your code doesn't look much like a standard react component IMO, but I guess you won't need a function to display the component. The component should be present in your render method and it's "open" property is what makes it to be displayed or not.
Also, you have not specified which library you are using, but "show" doesn't sound familiar to me. MUI for instance has it as "isOpen={modalOpen}". Anyway I hope the snippet bellow can help.
class MyButtonModalComponent extends React.Component {
state = {
modalOpen: false
}
render(){
const { modalOpen } = this.state;
return (
<div>
<Button onClick={e=>this.setState({ modalOpen: true })}>Click here to open a modal window</Button>
<Modal
show={modalOpen}
onHide={() => this.setState({ modalOpen: false })}
dialogClassName="modal-90w"
aria-labelledby="example-custom-modal-styling-title"
>
<Modal.Header closeButton>
<Modal.Title id="example-custom-modal-styling-title">
Custom Modal Styling
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Ipsum ... </p>
</Modal.Body>
</Modal>
</div>
)
}
}

Change position of objects inside an array

I have a functional component which has a useState that contains the information i want to display and the images.
const [images, setImages] = useState(
[
{
"index": 0,
"img": Volleyball,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
},
{
"index": 1,
'img': Gaming,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
},
{
'index': 2,
'img': Coding,
"text": ' Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero ut dolorem dolore modi esse reprehenderit assumenda error aut ducimus, aliquam repudiandae aliquid deserunt voluptatum placeat fugit explicabo dignissimos cum tempore.'
}
]
)
And this is the jsx that I have in the return body with a left button that prevImage function.
return (
<div className={classes.gallary}>
<div className={classes.direction}>
<button className={classes.leftArrow} onClick={prevImage}>Left</button>
<button className={classes.rightArrow} >Right</button>
</div>
<div className={classes.images}>
{images.map(current => {
return (
<div key={current.index}>
<img src={current.img} alt="" className={classes.image} />
</div>
)
})}
</div>
</div>
);
Here is the prevImage function :
const prevImage = () => {
const currentImgArray = [...images];
const sendToBack = currentImgArray.push(currentImgArray.pop());
setImages(sendToBack)
}
This is what it looks like before clicking the button and what I am trying to do is to place the first object as the last object in the array . If the function is called I get : "TypeError: images.map is not a function"
As has been pointed out, array::push returns the new length of the array, not an array. Basically you need to rotate the array left/right.
const prevImage = () => {
// moves last image to front of array
setImages(images => [images[images.length - 1], ...images.slice(0, -1)]);
};
const nextImage = () => {
// moves first image to end of array
setImages(images => [...images.slice(1), images[0]]);
};
Array.push returns new length of the array, so concat function is to be use on here:
const prevImage = () => {
const currentImgArray = [...images];
const sendToBack = currentImgArray.concat(currentImgArray.pop());
setImages(sendToBack)
}
Updated ans:
const prevImage = () => {
setImages(prevState => {const popArr = prevState.pop();
return prevState.concat(popArr);
})
}

React, CSS: How to make corresponding modal window open for each card

Let me explain the situation. Currently I am doing a project to practice SCSS and React. It is a website dedicated to soccer team. The problem is that I am creating a custom modal window using CSS and HTML. What I want is that whenever I click the button "see pics" a modal window related to this card should open, while in my case all modal windows open at once.
All modal windows open at once stacked one on each other. Like that.
There are four modal windows like that overall, but only the top modal shows.
The HTML structure of these modal windows is the following:
They have got the same classes and ids.
In React part I have a component called PopUp, which gets information from db.json as props.
import React from 'react';
import Button from './button-round-dark';
const generateAdditionalInfo = ({info}) => {
if (info) {
return info.map((item) => {
const addInfo = item.additionalInfo;
console.log(addInfo);
return (
<div className="popup" id="popup">
<div className="popup__content" key={addInfo.id}>
<div className="popup__left">
<img className="popup__img" src={`../../${process.env.PUBLIC_URL}/images/additionalInfoPics/${addInfo.images[0]}`}/>
<img className="popup__img" src={`../../${process.env.PUBLIC_URL}/images/additionalInfoPics/${addInfo.images[1]}`}/>
</div>
<div className="popup__right">
x
<h2 className="heading-secondary u-margin-bottom-small">{addInfo.title}</h2>
<p className="popup__description">{addInfo.description}</p>
</div>
</div>
</div>
)
})
}
}
const PopUp = (props) => {
return (
<div>
{generateAdditionalInfo(props)}
</div>
)
}
export default PopUp;
db.json
"legendaryTeam":[
{
"id":"p1",
"name": "buffon",
"image":"buffon.jpg",
"years":"2001-2018",
"number":"18",
"position":"goalkeeper",
"physicalFeatures":{
"weight":"86",
"height":"194",
"speed":"7",
"power":"250"
},
"additionalInfo":{
"id":"1",
"images":["buffon_1.jpg","buffon_2.jpg"],
"title":"Buffon title",
"description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Etiam dignissim diam quis enim lobortis scelerisque. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla. Lorem sed risus ultricies tristique nulla aliquet enim. Ultrices in iaculis nunc sed augue lacus viverra vitae congue."
}
},
{
"id":"p2",
"name": "Ravanelli",
"image":"ravanelli.jpg",
"years":"1992-1996",
"number":"10",
"position":"forward",
"physicalFeatures":{
"weight":"75",
"height":"176",
"speed":"9",
"power":"300"
},
"additionalInfo":{
"id":"2",
"images":["ravanelli_1.jpg","ravanelli_2.jpg"],
"title":"Ravanelli title",
"description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Etiam dignissim diam quis enim lobortis scelerisque. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla. Lorem sed risus ultricies tristique nulla aliquet enim. Ultrices in iaculis nunc sed augue lacus viverra vitae congue."
}
}
Please pay attention to additionalInfo object, it is where information goes to PopUp component.
Custom CSS code for modal window looks like this.
.popup {
height: 100vh;
width:100%;
position:fixed;
top:0;
left:0;
background-color: rgba($color-black,0.8);
z-index:99999;
opacity: 0;
visibility: hidden;
transition:all .3s;
&:target {
opacity: 1;
visibility: visible;
}
&__content {
#include centrify;
width:75%;
background-color: $color-white;
box-shadow: 0 2rem 4rem rgba($color-black, .2);
border-radius: 3px;
display: table;
overflow: hidden;
}
&__left {
width:33.33333333%;
display: table-cell;
}
&__right {
width:66.666666666%;
display: table-cell;
vertical-align: middle;
padding: 3rem 5rem;
}
&__img {
display: block;
width:100%;
}
&__description {
font-size: 1.4rem;
margin-bottom: 4rem;
}
}
Modal window open with help of target selector. Now I am scratching head over how to make implement what I want. Please help me out. Do you have any ideas?
By the way other modal windows look like this. But they are lower in stack so they are not shown.
If you want to tinker with my project click here
To run react see picture below
To run db.json you need to install it globally
$ npm install -g json-server
And then run it, see pic below
To run SCSS see pic below
Your problem is not in your description, you bind the ButtonRoundDark to all of the modal. You need to add onClick event to your 'see pics' button. After click, fetch the data, show the modal.

Resources