0

I have an object, which has a number of keys, and at each key, there is an array of objects. Each object contains an image file that I need to show to preview the image that the user has just uploaded. But it's showing the image that was uploaded the previous time (not the one that's been just uploaded)

Apparently useState doesn't immediately cause a rerender unless it sees a change in the array or object How do I update states onchange in an array of object in React Hooks

I followed the above (and a number of similar suggestions from StackOverflow) and made a copy of the object and a copy of the array at the key but it still doesn't work

Any idea why?

const Form = props => {
let [images, setImages] = useState({
    bannerPicture: [],
    projectPictures: [],
    projectsPictures: [],
    thumbnailPictures: []
})
const showTempImage = (file, tempFileObj, key) => {
    const imagesCopy = {...images}
    // add this image to the end of the array at the given key
    imagesCopy[key] = [...imagesCopy[key], tempFileObj]
    setImages({...imagesCopy})
....

}

I just want the image to show immediately after the user uploads it

hillybob991
  • 97
  • 11
  • Did you check, if the key is correct? – FireFighter Apr 21 '21 at 16:36
  • @FireFighter key is definitely correct, yes – hillybob991 Apr 21 '21 at 16:46
  • Am I right that the only thing which stopping your progress is that's not rerendering UI even if your state updated? Or your state is not updating after showTempImage? – Sabit Rakhim Apr 21 '21 at 16:51
  • @SabitRakhim good question. So I console.log(images) after setImages, and it doesn't show a change in images nor does the UI rerender. But if I upload another image, it shows the previous image that was uploaded both in the UI and the images state – hillybob991 Apr 21 '21 at 16:55
  • Can you try to add console.log() before setImages and assign something like `const newImages = {...imagesCopy}`, then show it. The difference is that state is async and I think it's not work as single source of truth – Sabit Rakhim Apr 21 '21 at 16:58
  • 1
    You need to use `useEffect` hook with `images` as a dependency, and perform the actions after uploading the image inside the `useEffect` body. – BlackMath Apr 21 '21 at 16:59
  • @SabitRakhim it console.logs() nothing until I upload another image – hillybob991 Apr 21 '21 at 17:03
  • @BlackMath I tried adding images as a dependency to useEffect before but it didn't work. How would I call useEffect with images as a dependency from within the same component? I know how to do it if it were coming from props but this is within the same component – hillybob991 Apr 21 '21 at 17:06
  • @hillybob991 `useEffect(() => {//somelogic}, [images])` – BlackMath Apr 22 '21 at 05:28

1 Answers1

0

Do it from your async function.

I'm assuming you'll only show the image after the upload is complete.

const mockUploadApi = () => {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve("uploadedImage");
    },1000);
  });
};


const App = () => {

  const [state,setState] = React.useState({
    status: "IDLE",
    image: ""
  });
  
  const uploadImage = () => {
    setState((prevState) => ({
      ...prevState,
      status: "UPLOADING"
    }));

    // THIS SHOULD USE await BUT THE SNIPPET DOES NOT ALLOW IT
    // SO I'M USING .then()
    
    mockUploadApi().then((uploadedImage) => {
      setState({
        status: "IDLE",
        image: uploadedImage
      });
    });
  };

  return(
    <div>
      <div>
        status: {state.status}
      </div>
      <div>
        image: {state.image || "no image"}
      </div>
      <div>
        <button onClick={uploadImage}>
          Upload
        </button>
      </div>
    </div>
  );
};

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336