Masonry layout with Next.js - next.js

I am developing a multi-page website using Next.js for the frontend and Strapi for the backend. On the blog page, the data is dynamically fetched from the database. I am trying to display this data in an adaptive layout using Masonry.
The problem I am having is that on first load, the page displays every grid-item in a single vertical column. On reload, the Masonry layout then takes effect, but with some overlapping items. On a second reload, everything then displays properly and is even responsive. However, if I navigate away from the page and come back, it is back to square one.
Here is the code for the blog page :
import React from 'react';
import Layout from '../components/StaticLayout';
import Link from 'next/link';
import fetch from 'isomorphic-unfetch';
import '../../static/styles.min.css';
let Masonry = '';
export default class Blog extends React.Component {
constructor(props) {
super(props);
this.state = {
appIsMounted: false
}
}
static async getInitialProps() {
const res = await fetch('http://localhost:1337/blogposts');
const data = await res.json();
return {
posts: data
};
}
async componentDidMount() {
let masonry = await import('masonry-layout');
this.setState({
appIsMounted: true
})
}
render() {
return (
<Layout>
<h1 className="blogHeader">Blog</h1>
{this.state.appIsMounted ? (
<div className="grid js-masonry" data-masonry-options='{ "itemSelector": ".grid-item", "columnWidth": 400 }'>
{this.props.posts.map(
post => (
<div key={post.id} className="grid-item">
<Link href="/p/[id]" as={`/p/${post.id}`}>
<img src={`http://localhost:1337${post.Image.url}`} alt={post.Image.name} className="postImage" />
</Link>
<div className="text">
<Link href="/p/[id]" as={`/p/${post.id}`}>
<a className="postLink">{post.Title}</a>
</Link>
<p className="blurb">{post.Content.substring(0, 300) + '...'}</p>
<Link href="/p/[id]" as={`/p/${post.id}`}>
<button className="viewPost">Read More!</button>
</Link>
</div>
</div>
)
)}
</div>
) : null}
</Layout>
)
}
}
To be noted, the StaticLayout script only ensure the header is at the top of each page.
EDIT
So I've solved the overlapping part of the problem. Instead of initializing Masonry in the html, I've added a js file to './static'. On my blog page, I've added the following :
import Head from 'next/head'
...
return(
<Head>
<script type="text/javascript" src="/static/runMasonry.js"></script>
</Head>
...
)
And the runMasonry.js file looks like this :
window.onload = function () {
var elem = document.querySelector('.grid');
var msnry = new Masonry(elem, {
// options
itemSelector: '.grid-item',
columnWidth: 160,
gutter: 20
});
}
But when I first arrive on the page or navigate away and come back, the elements are still displayed in a column and require a reload.

Came up with a non-ideal fix, but it works.
Added the following code to the runMasonry.js file :
window.addEventListener("DOMSubtreeModified", function () {
var elem = document.querySelector('.grid');
if (elem) {
var msnry = new Masonry(elem, {
// options
itemSelector: '.grid-item',
columnWidth: 160,
gutter: 20
});
}
});
So it detects when new page content is loaded and, if it finds my .grid element, re-runs masonry.
Leaving this open in case someone can provide a better, long-term solution.

Related

Changing body background image with React

So, I'm trying to create a React App that changes the background image of the body. I did this by giving the body in my index.html an id of "body." I can get this to work with changing the background COLOR just fine. When I try to reassign the background IMAGE, though, I can't seem to get it to work no matter what I try.
This works:
document.getElementById("body").style.backgroundColor = "blue";
This doesn't:
import explosion from "./explosion.png";
function Boom(){
document.getElementById("body").style.backgroundImage = "url('" + {explosion} +
"')";
Why? I've tried writing this many different ways.
this worked for me :
import { useEffect } from "react";
import img from './img.png';
export default function App(){
useEffect(()=>{
document.getElementById('body').style.backgroundImage = `url('${img}')`;
})
return <>
<div id="body"
style={{height:'300px'}}
>
</div>
</>
}
or you can use inline css style :
import img from './img.png';
export default function App(){
return <>
<div id="body"
style={{
height:'300px',
backgroundImage: `url('${img}')`,
}}
>
</div>
</>
}
you need to pass the URL of the image as a string, without wrapping it in curly braces {}
You can try this code
import { useEffect } from "react";
export default function App() {
const bgUrl =
"https://images.unsplash.com/photo-1605106250963-ffda6d2a4b32?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80";
/*useEffect Hook allows you to perform side effects in your components. Such as fetching data, directly updating the DOM, and timers*/
useEffect(() => {
Boom();
}, []);
const Boom = () => {
document.getElementById("body").style.backgroundImage = `url(${bgUrl})`;
};
return (
<div className="App">
<h1>Hello World!</h1>
</div>
);
}
here's a link to the working demo Change Background Image

Images not showing after adding const slider ... so confused

So i've been following a tutorial and trying to teach myself next.js with lazy loading slider and tailwindcss.. I am a beginner but i have made ecommerce sites and stuff to teach myself.. but this tutorial was going great until this!but Ive tried everything i know and googled it every which way to try to fix this issue!! My images were showing perfectly before i added the - const setCurrent up to the if !Array
if i delete that the images show back up... im so confused ive gone through everything - also the "Gallery" even disapeared once i added that section
and i put it all on github incase someone can look at it to help me?? https://github.com/Jessica19882/firebird
i have deleted the
const current up to the !Array part and images showed back up i have tried rewriting it as another post said but that didnt work ive checked and zoomed into the video tutorial to make sure everything was right and it is... i have checked all the other pages index.js app.js and stuff to make sure i had it just like his and it is!!
slider.js
import { SliderData } from './SliderData'
import React, { useState } from 'react'
import Image from 'next/image'
const Slider = ({ slides }) => {
const [current, setCurrent] = useState(0)
const length = slides?.length
const nextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1)
}
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1)
}
if (!Array.isArray(slides) || slides.length <= 0) {
return null
}
return (
<div id='gallery'>
<h1>Gallery</h1>
<div>
{SliderData.map((slide, index) => {
return (
<div
key={index}
className={
index === current
? 'opacity-[1] ease-in duration-1000'
: 'opacity-0'
}>
<Image
src={slide.image}
alt='/'
width='1440'
height='600'
style={{ objectFit: 'cover' }}
/>
</div>
)
})}
</div>
</div>
)
}
export default Slider
index.js
import Head from 'next/head'
import Hero from '../components/Hero'
import Slider from '../components/Slider'
import SliderData from '../components/SliderData'
export default function Home() {
return (
<div>
<Head>
<title>Firebird Sounds</title>
<meta name='description' content='firebird sounds' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Hero
heading='Firebird Sounds - Audio & Video Distribution'
message='Audio and Video Distribution with a menu of
Services: Marketing, PR, Global rights management and creativity'
/>
<Slider Slides={SliderData} />
</div>
)
}
SliderData.js
export const SliderData = [
{
image:
'https://images.unsplash.com/photo-1466428996289-fb355538da1b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTl8fG11c2ljJTIwZGlzdHJpYnV0aW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60',
},
{
image: '/images/FirebirdSounds.jpg',
},
{
image:
'https://images.unsplash.com/photo-1460667262436-cf19894f4774?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NjZ8fG11c2ljJTIwZGlzdHJpYnV0aW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60',
},
{
image: '/images/guitar.jpg',
},
{
image: '/images/firebird.png',
},
]
app.js
import Navbar from '../components/Navbar'
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<>
<Navbar />
<Component {...pageProps} />
</>
)
}
https://www.youtube.com/watch?fbclid=IwAR2jl5qYoIsaFZQ26MumbFcCYv5t3eYxcqVKgm4xAsxilhEkAzSNoUB0fzE&v=HVyct9EUNP8&feature=youtu.be
I figured it out I needed to put the
Slider slides={SliderData}/>
into _app.js not index.js or both but its working again!

Testing Style on React component

I have the following class with a function, that opens a modal (open_modal(...)) in a separate file to a component as I have a large number of modals that use this functionality.
import open from "open";
import $ from "jquery";
class ReactHelpers {
static open_webpage(page_url) {
open(page_url);
}
static open_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "block");
$(modal_id).css("display", "block");
}
static close_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "none");
$(modal_id).css("display", "none");
}
}
export default ReactHelpers;
I am trying to assert that the open_modal function has added css to the divs in question as below:
it('should close the modal', function () {
const wrapper = shallow(
<div id="overlay_id">
<div id="modal_id">
<p>modal</p>
</div>
</div>
)
const overlay = wrapper.find('#overlay_id')
const modal = wrapper.find('#modal_id')
ReactHelpers.open_modal(overlay, modal);
console.log('OVERLAY ', overlay);
expect(overlay.prop('style')).toHaveProperty('display', 'block');
expect(modal_style).toHaveProperty('display', 'block');
});
Further, I'm sure to how the open_webpage function would be tested as this is a library function. In my other tests in my other components, I'm mocking this so it's never actually been tested.
Any help is greatly appreciated.
Thanks
To test style of dom elements:
You should mount the component (using mount), instead of just creating it (using shallow).
Since you're changing the style of dom element directly, You should test the style of the dom element (component.getDOMNode().style.display), instead of testing the react style property (component.prop.style).
example:
import $ from "jquery";
it("should create a div and changes its color to red", () => {
const wrap = mount(
<div id="red_el"></div>
);
const el = wrap.find("#red_el").getDOMNode()
$(el).css("color", "red");
expect(el.style.color).toEqual("red");
});
In your case:
it("should open modal", () => {
const wrapper = mount(
<div>
<div id="overlay" style={{ display: "none" }}>
<div id="modal" style={{ display: "none" }}>
overlay
</div>
</div>
</div>
);
const overlay = wrapper.find("#overlay").getDOMNode();
const modal = wrapper.find("#modal").getDOMNode();
ReactHelpers.open_modal(overlay, modal);
expect(overlay.style.display).toEqual("block");
expect(modal.style.display).toEqual("block");
});
See it live on codesandbox (switch to the tests tab to run the tests .)

Cant overwrite the custom stylesheet

I am trying to overwrite the CSS of react range slider.It uses the custom style sheet of which i need to add in the head section.My project is built on next.js
<link rel="stylesheet" href="https://unpkg.com/react-rangeslider/umd/rangeslider.min.css" />
Otherwise, the slider doesn't show anything if i don't add the link in head even though I installed the library. It's not even overwriting the CSS. I want to change the background color.This is my code:
import React, { Component } from 'react'
import 'react-rangeslider/lib/index.css';
import './slider.css';
import Slider from 'react-rangeslider'
class Horizontal extends Component {
constructor (props, context) {
super(props, context)
this.state = {
value: 850
}
}
handleChangeStart = () => {
console.log('Change event started')
};
handleChange = value => {
this.setState({
value: value
})
};
handleChangeComplete = () => {
console.log('Change event completed')
};
render () {
const { value } = this.state
return (
<div>
<div className='slider' style={{ marginTop:'165px',marginLeft:'319px',width:'700px',backgroundColor:'EF5350'}} >
<div style={{ textAlign:'center',color:'gray',fontSize:'35px',marginBottom:'82px'}}>
<p> What is the size of your property?</p>
</div>
<Slider
min={850}
max={5000}
value={value}
onChangeStart={this.handleChangeStart}
onChange={this.handleChange}
onChangeComplete={this.handleChangeComplete}
/>
<div className='value'>{value}</div>
</div>
</div>
)
}
}
export default Horizontal
I tried to change the background color in slider.css.
.rangeslider-horizontal .rangeslider__fill {
background-color: red;
}
The library needs to be installed first:
npm install react-rangeslider --save
It doesn't work as the slider stylesheet overwrite yours. Include the style like
// To include the default styles
import 'react-rangeslider/lib/index.css'
// import your css
import './style.css';
Demo
always make your own CSS stylesheet file the last file to import after any other CSS stylesheet files to make overwrite you need
otherwise, you can always use the console in the browser to auto-detect any error by pressing F12 in the browser then go to the tab called (console)
I think you can style element you want to live in the console to know the detail of how to nesting element
you also can open the CSS file in the editor and press Ctrl+F then find the line of code you want to style then copy its property and value to your own CSS file and then you can edit it so easy

How can I use bootstrap grid mapping an array of images?

I'm building a portfolio website with gatsby.js. All photos are posted in wordpress, fetched by graphQL and rendered to the website.
I'm trying to use bootstrap grid to organize the photos and make it responsive, but because graphQL return an array with all images fetched from wordpress posts, I can't set a div with class='row' as I'm using array.map. And I don't know how to solve it.
From graphQL i'm setting resolution to width=300px and height=300px.
That's the only way I found to organize sizes, as long as I can't use class row and all images are considered in one row. The problem is that the photo size is not responsive, so it will always be 300X300...
I'd like a way to make it a grid system as it's suppose to work... So when I resize the window, all photos are organized and resized.
const IndexPage = () => {
const data = useStaticQuery(graphql`
query {
allWordpressPost {
edges {
node {
title
featured_media {
localFile {
childImageSharp {
resolutions(width: 300, height: 300) {
src
width
height
}
}
}
}
}
}
}
}
`);
const imagesResolutions = data.allWordpressPost.edges.map(
(edge) => edge.node.featured_media.localFile.childImageSharp.resolutions
);
return (
<Layout>
<Jumbotron />
<div className="container">
<h1 className="my-5 text-center">Portfolio</h1>
{imagesResolutions.map((imageRes) => (
<Img className="col-sm-6 col-lg-4 img-rounded img" resolutions={imageRes} key={imageRes.src} />
))}
</div>
</Layout>
);
};
If you split your data.allWordpressPost.edges array into a chunked array, you can loop through the outer array to render rows, and each of the inner arrays to render cols.
For a 3 column bootstrap grid, you want to pass in a size value of 3 (it's the 2nd param of lodash.chunk in this example). This ensures the length of each chunk is 3.
Here is a simple example ignoring the use of graphql, childImageSharp, and gatsby-image.
import ReactDOM from 'react-dom';
import React, { Component } from 'react';
import arrayChunk from 'lodash.chunk';
import 'bootstrap/dist/css/bootstrap.min.css';
const IndexPage = () => {
const rawData = [1, 2, 3, 4, 5, 6];
const chunkedData = arrayChunk(rawData, 3)
return (
<div>
<div className="container">
{chunkedData.map((row, rowIndex) => {
return (<div key={rowIndex} className="row">{
row.map((col, colIndex) => {return (<div key={colIndex} className="col-sm">{col}</div>)})
}</div>)
}
)}
</div>
</div>
);
};
ReactDOM.render(<IndexPage />, document.getElementById('root'));
stackblitz

Resources