0

I have a functional component with the following hook:-

const [imagePreview, setImagePreview] = useState('');

Later on, the change event, I have this:-

    if (imagePreviewUrl) {
  setImagePreview( (<div>
    <br/> <img src={imagePreviewUrl} alt="icon" width="200" /> 
    </div>));
}

Looks like it doesn't set anything. how do I set it? I could do it as:-

  let imagePreview = null;
if (imagePreviewUrl) {
  imagePreview = (<div>
    <br/> <img src={imagePreviewUrl} alt="icon" width="200" /> 
    </div>);
}

But it is bit ugly. I think useState is the recommended way to do it.

Update with more code:-

  const fileChangedHandler = event => {
setSelectedFile(event.target.files[0]);
let reader = new FileReader();
reader.onloadend = () => {
setImagePreviewUrl(reader.result);
}
reader.readAsDataURL(event.target.files[0])
if (imagePreviewUrl) {
// can't break into multiline, sytax error 
setImagePreview('(<div><br/> <img src={imagePreviewUrl} alt="icon" width="200" /> </div>)');

}

return (
 <div>
 <label>Profile picture: </label>
  <input className="Column" type="file" accept="image/*" 
       onChange={(e) => {fileChangedHandler(e)}} />
  // I want to show the selected image here as preview before upload 
  { imagePreview }
  </div>
 )

Hopefully, I will give a clear understanding. What is my intention?

masiboo
  • 4,537
  • 9
  • 75
  • 136
  • Is the `if (imagePreviewUrl)` check evaluating to `true`? And what does the "change event" look like exactly, you're only showing a part of it. How does it get triggered? – goto Feb 29 '20 at 11:41
  • Pls, check now. I added more code. Hopefully, it shows what is expected here? – masiboo Feb 29 '20 at 12:28
  • I see what the issue is. – goto Feb 29 '20 at 12:36

2 Answers2

2

The reason you're having issues is because you're trying to access the state of imagePreviewUrl right after you update it with setImagePreviewUrl - this will not work.

const fileChangeHandler = (event) => {
  // ...
  reader.onloadend = () => {
    // this is asynchronous
    // so this definitely wouldn't get
    // executed before you get to the `if` statement below
    // also, `setImagePreviewUrl` is asynchronous as well
    setImagePreviewUrl(reader.result)
  }
  // ...
  if (imagePreviewUrl) {
    // the old value of imagePreviewUrl is being used
    // here, and since the initial value evaluates to 
    // `false`, the `setImagePreview` call below
    // never gets executed
  }
}

Your new value for imagePreviewUrl won't be available immediately because it's asynchronous, so you should probably create a separate hook that listens for changes to imagePreviewUrl and updates imagePreview.

function App() {
  const [imagePreviewUrl, setImagePreviewUrl] = React.useState("");
  const [imagePreview, setImagePreview] = React.useState(null);

  const fileChangedHandler = () => {
    setImagePreviewUrl(
      "https://interactive-examples.mdn.mozilla.net/media/examples/grapefruit-slice-332-332.jpg"
    );
  };

  React.useEffect(() => {
    if (imagePreviewUrl !== "") {
      const image = (
        <div>
          <img alt="fruit" src={imagePreviewUrl} />
        </div>
      );
      setImagePreview(image);
    }
  }, [imagePreviewUrl]);

  return (
    <div className="App">
      <button type="button" onClick={fileChangedHandler}>
        Trigger file change handler.
      </button>
      {imagePreview}
    </div>
  );
}

Here's a working example:

goto
  • 4,336
  • 15
  • 20
  • May I know why "Your new value for imagePreviewUrl won't be available immediately"? I was guessing this is a synchronous call and it should set immediately. Never mind, I am learning reactjs. – masiboo Mar 01 '20 at 20:47
  • @masiboo It's not an asynchronous call. As per docs, "The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component." Similar to calling `this.setState` in `class-based components`, you will have to "wait" until next render to access the latest value of your state variable. See here for more if you'd like - https://stackoverflow.com/a/54069332/5862900 – goto Mar 01 '20 at 20:55
0

This is not a recommended approach, what you want is a conditional rendering:

const [imagePreview, setImagePreview] = useState(false);

if (imagePreviewUrl) {
  setImagePreview(true);
}

return (
  imagePreview && (
    <div>
      <br /> <img src={imagePreviewUrl} alt="icon" width="200" />
    </div>
  )
);
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • I didn't test it. Definitely it will work. I am trying to achieve it a bit cleaner way by the hooks. – masiboo Feb 29 '20 at 12:36