Evanss
Evanss

Reputation: 23593

Force React to reload an image file?

My React app allows users to upload profile images. This is done by uploading the image to an Amazon S3 bucket and storing the path to the image in my database, along with the date and time the image was uploaded. The filename of the image is set to the user's ID.

Im having a problem when a user uploads a new image. As the image path is the same React doesn't know anything has changed, meaning I have to refresh the page to see the change.

As I have a date field I can use componentWillReceiveProps to know when a new image has been uploaded. The following console.log does fire at the correct time:

componentWillReceiveProps(nextProps) {
    if (this.state.picDate.getTime() !== nextProps.user.pic.date.getTime()) {
        console.log('image date has changed');
        // this.forceUpdate();
    }
}

Can I use this to re-render the image? I tried this.forceUpdate() but it doesn't work.

Upvotes: 43

Views: 47360

Answers (11)

Asfar Horani
Asfar Horani

Reputation: 21

The solution working for me is to add a bypassCache like this:

image.src = url + "?" + Math.random().toString(36); 

Upvotes: 2

amarse
amarse

Reputation: 39

What worked for me was changing the image name with every new upload, for example:

const new_file_name = Date.now() . '/' . file_extension

This new file name needs to be stored on some database, relating it to the user. It's also a good idea that each user has a folder, to avoid that two users have the same image name.

When loading the app, request the user image name from the api and you will get the name of the file. As you know the user, you know in which folder to search for this.

In case you are uploading the file and want to see the image change immediately, store this file name into a react or redux state. The Image element will re-render on change.

To get the image from your api do something like:

<Image
  source={{ 
    uri: your_localhost_ip + ':' + your_port +'/'+ your_folder_containing_all_user_pics + '/' + user_id + '/' + user_picture,
    cache:'reload' //only works for ios
  }}
/>

Upvotes: 0

hoima
hoima

Reputation: 99

Better solution may be to change the structure of your code and API

Solution 1 Add a updatedDate to the object and use that. It needs to be a full Date value.

const bypassCache = new Date(updatedDate).getTime()
const imageUrl = `${image.url}?${bypassCache}`

// imageUrl = https://example.com/userId/imageId?16504372055

You'd need a value that's unique for the new entry compared to the previous. If you need to compare, you may use useRef to hold the initial value and then compare to the new updatedDate.

Solution 2 Store the bypassCache in the pathname itself https://example.com/userId/imageId/bypassCache

Thereby handling it from "backend". For the users privacy you may use a different unique value that cannot be parsed/reversed into a real timestamp.

Upvotes: 2

Imran
Imran

Reputation: 103

This works on functional components (I haven't tried it on class based components but it should work too):

const MyComponent = () => {
  const [source, setSource] = useState('some_image.png');

  const changeImage = (newSource) => {
    setSource(null);
    setTimeout(() => setSource(newSource));
  };

  return (
    <img 
      src={source}
    />
  );
};

Note: I have tried using random hash and also changing key, they didn't work. This might work for you

Upvotes: 0

Pratap07
Pratap07

Reputation: 51

Add current date as query parameter to the image url

<img src={`${image_url}?${global.Date.now()}`} />

Upvotes: 1

N N K Teja
N N K Teja

Reputation: 435

(Enhancement to Eric Wiener answer), set key (ie. timestamp) upon upload to s3 to get a cleaner refresh.

const [timestamp, setTimestamp] = React.useState(Date.now());

axios.post("/api/upload/s3", formData)
    .then(res => {
        setTimestamp(Date.now())
    })

<Avatar key={timestamp} className={classes.avatar} src={user_info.avatar} onClick={handleOpenPicture}></Avatar>

Upvotes: 3

Binit Ghetiya
Binit Ghetiya

Reputation: 1979

You can try doing something like first set state to null so image wont display and again set state to particular image with path.

constructor(props){
    super(props);
    this.state = {
         image_path : 'before update image path'
    };
}    

componentWillReceiveProps(nextProps) {
    if (this.state.picDate.getTime() !== nextProps.user.pic.date.getTime()) {
        console.log('image date has changed');
        //now here set state
        this.setState({
          image_path : 'your new image path or older as you explained both are same' + '?' + Math.random()
        });
    }
}

Upvotes: 5

Eric Wiener
Eric Wiener

Reputation: 5947

I was having this issue with a local image. The image was being modified, but the URL was staying the same.

The trick was just to add a key property to the image that changed whenever the image changed. For instance:

<Image key={Date.now()} source={{uri: <your local uri>}/>

Source: this GitHub comment

Upvotes: 20

Emad Emami
Emad Emami

Reputation: 6629

I think that's because of the browser cache. try adding some hash like date.now() after image URL changes. for example:

setState({
   imageSrc: '...',
   imageHash: Date.now()
})

and in render:

render(){
  return(
      <img src={`${imageSrc}?${imageHash}`} />
  )
}

Upvotes: 73

Sagar
Sagar

Reputation: 4957

Its working fine for me.

<img src={`${props.src}?${new Date().getTime()}`} />

Upvotes: 7

Salman Ahmed
Salman Ahmed

Reputation: 11

Just set image path blank and then set to whatever your path was, react will assume its a change and new pic will be loaded.

this.setState({image_path : ''});               
this.setState({image_path : 'Actual Image Source'});

Like I am doing in my FileUpload function

this.setState({userimage:''});  
this.setState({userimage:"http://localhost:5000/auth/getProfilePic/" + this.state.username,});

Upvotes: 1

Related Questions