imsky / holder

:city_sunrise: Client-side image placeholders.
http://holderjs.com
MIT License
5.84k stars 592 forks source link

holder.js does not work when the page renders twice in React #225

Closed jguo1002 closed 5 years ago

jguo1002 commented 5 years ago

I am using holder.js in React with react-bootstrap. In index.html I added in <header>: <script src="holder.js"></script>

In the component ProjectCard I wrote: <Card.Img className="proj-card-img" variant="top" data-src="holder.js/100px180" />

This component is rendered using map() function

{Object.keys(this.state.projects).map(key=>
     <ProjectCard key={key} index={key} details={this.state.projects[key]}/>)}

It works nicely if I set state in ComponentWillMount(). However, if I update the state and the page renders twice (e.g. use firebase to syncState), holder.js will not work.

I have tried

<script>
    Holder.run({
        images:"MyClassName"
    })
</script>

but it didn't work either.

Did I write anything wrong? Thanks!

imsky commented 5 years ago

@cindy100295 could you provide a fiddle or a Gist (or anything that works for you) of the page?

in the meantime, i think one thing you could try is change this:

Holder.run({
        images:"MyClassName"
    })

to this:

Holder.run({
        images:".MyClassName" // should be .proj-card-img if going by your comments
    })

note the . before the class name, as the images property needs to be a valid CSS selector

jguo1002 commented 5 years ago

My code in index.html:

<head>
  <script src="holder.js"></script>
  <script>
      Holder.run({
        images: '.proj-card-img'
      })
    </script>
</head>

In React Component:

<Card.Img className="proj-card-img" variant="top" data-src="holder.js/100px180" />

But this time it does not show anything.

Some background: I have five cards, two of them are loaded from a JSON file (local). Three of them I pulled from Firebase (remote). Every time the two local cards render first. Then the other three show up later. Holder.js work for the local two but not for the remote three.

If I wrote the code above, five cards have no placeholder images. Maybe it is because when the component updates, index.html does not refresh? Or my code is wrong...

imsky commented 5 years ago

could you try something like this instead:

componentDidMount: function () {
  requestAnimationFrame(function () {
    Holder.run({
      images: '.proj-card-img'
    });
  });
}

also, as a followup, you could try passing a Node (Element) to images which works better if you treat each placeholder as a component

jguo1002 commented 5 years ago

Thanks! I figure it out. In index.html:

<script>
    function holder() {
      Holder.run({
        images: ". MyClassName"
      })
    }
</script>

In component:

componentDidMount() {
    window.holder();

And then it works.

imsky commented 5 years ago

nice @cindy100295 i'll look into adding this to docs

Senninseyi commented 4 years ago

i used mine without the didmountComponent.... like this import 'holderjs' { img: "holder.js/100px270", textHeader: "Well handled customers", link: "Our reviews on Reviews.co.uk", }, where it is being used

nimbus2300 commented 3 years ago

I struggled to get holderjs 2.9.7 (https://www.npmjs.com/package/holderjs) working nicely in react bootstrap 1.4 (bootstrap 4.5.3).

I was trying to use it in a class component. In the end this worked:

yarn add holderjs && yarn install

Then in my component:

import { run as runHolder } from 'holderjs/holder';

class Foobar extends Component

 constructor(props) {
    ...
    this.runHolder = runHolder;
  }

 componentDidMount() {
   this.runHolder('image-class-name-no-initial-dot');
}

And on the image:

<Image src="holder.js/100x100"/>

imsky commented 3 years ago

cool @nimbus2300 - glad you got it working. will keep that in mind when there's a native react component

sersart commented 3 years ago

Hi, you can run holderjs in React function without any problem. Please check example.

import React, { Fragment, useEffect } from "react";
import { Image } from "react-bootstrap";
import { run as runHolder } from 'holderjs/holder';

export function HolderExample() {
    useEffect(() => {        
        runHolder('image-class-name');
    });
    return (
         <Fragment>
           <Image 
                  className="image-class-name"
                  roundedCircle
                  src="holder.js/20x20?text=J&bg=28a745&fg=FFF"
            /> 
          </Fragment>
    );

Have fun.

arnil6585 commented 3 years ago

yoieh:patch-1

albertorodriguezdiaz commented 2 years ago

Hi All. You need to add: 1) import { run as runHolder } from 'holderjs/holder'; 2) useEffect(() => { runHolder('image-class-name'); }); It's all 😃 imagen

nimbus2300 commented 2 years ago

Hi All. You need to add: 1) import { run as runHolder } from 'holderjs/holder'; 2) useEffect(() => { runHolder('image-class-name'); }); It's all 😃 imagen

Assuming you're working with functional components, yes.

gunslingor commented 2 years ago

It only seems to work when I go to a URL, but when I use the react router to change the content of div for example with a navbar, it doesn't load.

import Container from "react-bootstrap/Container";
import 'bootstrap/dist/css/bootstrap.min.css';
import 'holderjs/holder';

class ProductList extends React.Component {
    render() {
        return (
            <Container>
                <Row xs={1} md={2} className="g-4">
                    <Col>
                        <Card>
                            <Card.Img variant="top" src="holder.js/100px160" />
                            <Card.Body>
                                <Card.Title>Card title</Card.Title>
                                <Card.Text>
                                    This is a longer card with supporting text below as a natural
                                    lead-in to additional content. This content is a little bit
                                    longer.asdfff
                                </Card.Text>
                            </Card.Body>
                        </Card>
                    </Col>
                </Row>
            </Container>
        );
    }
}
class Routing extends React.Component {
    render() {
        return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={ <Layout /> }>
                    <Route path="Home" element={ <Home /> } />
                    <Route path="Shop" element={ <Shop /> } />
                    <Route path="About" element={ <About /> } />
                    <Route path="Contact" element={ <Contact /> } />
                </Route>
                <Route path="*" element={<Navigate to="/Home" />} />
            </Routes>
        </BrowserRouter>
        )
    }
}
gunslingor commented 2 years ago

@nimbus2300 Thanks that solve it for me eventually. I think I understand it too, learning... was confused by runHolder... it's really run holder.js not a holder of run operations, lol, was confused. One thing I can't understand and google can't find is how this image-class-name-no-initial-dot class or 'userOptions' argument works.