Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useState hook not updating a value

I'm new to react and I don't understand why the title inside the h1 tag gets updated, but the url inside the Image Component doesn't ?

Listing Component

import React, { useState, useEffect, useContext } from 'react';
import Image from './Image';
import Size from '../index';

export default function Listing(props) {
  const [title, setTitle] = useState(props.title);
  const [url, setUrl] = useState(props.url);

  const value = useContext(Size);

  return (
    <div>
      <Image url={url} size={value.size} />

      <h1>{title}</h1>
      <form>
        <input id='URL' type='text' />
        <button
          onClick={e => {
            e.preventDefault();
            setUrl(document.getElementById('URL').value);
            setTitle(document.getElementById('URL').value);
          }}
        >
          Submit
        </button>
      </form>
    </div>
  );
}

My guess so far is that React wont update a child component if a prop changes, but how can I update that manually then ?

Image Component

import React from 'react';

export default class Image extends React.Component {
  //console.log(props.size);
  constructor(props) {
    super(props);
    this.url = props.url;
    this.size = props.size;
  }
  render() {
    return <img src={this.url} style={{ height: this.size + 'px' }} alt='' />;
  }
}```
like image 215
Twiggeh Avatar asked Dec 10 '19 15:12

Twiggeh


People also ask

Why setState hook is not updating immediately?

setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.

How do I set value in useState?

If you want to set an initial value for the variable, pass the initial value as an argument to the useState function. When React first runs your component, useState will return the two-element array as usual but will assign the initial value to the first element of the array, as shown in figure 5.

How do you update state useState hooks?

To update the state, call the state updater function with the new state setState(newState) . Alternatively, if you need to update the state based on the previous state, supply a callback function setState(prevState => newState) .

Why is useState not synchronous?

TL;DR: useState is an asynchronous hook and it doesn't change the state immediately, it has to wait for the component to re-render. useRef is a synchronous hook that updates the state immediately and persists its value through the component's lifecycle, but it doesn't trigger a re-render.


1 Answers

There are many "smells" with your code. Here is the short, quick-fix answer:

Change this: src={this.url} to this: src={this.props.url}.

The reason the image never updated is because of this:

constructor(props) {
  super(props);
  this.url = props.url;
  this.size = props.size;
}

You are setting local variables to be the initial prop values. Since you are setting these in your constructor, these lines will ever only be executed when your component is created, but never when new props are sent in. React is still triggering a re-render, since you are sending in new props, but the new value is never used, so the old result stays.


Slightly longer answer:

It's rarely a good idea to mix values read from the DOM directly like you do here:

setUrl(document.getElementById('URL').value);
setTitle(document.getElementById('URL').value);

Instead, have 2 states. One that holds the current value of your input which updates with every keystroke, and another that holds the value that you send down to your Image component.

Maybe something like:

export default function Listing(props) {
  const [title, setTitle] = useState(props.title);
  const [inputUrl, setInputUrl] = useState(props.url);
  const [url, setUrl] = useState(props.url);

  const value = useContext(Size);

  return (
    <div>
      <Image url={url} size={value.size} />

      <h1>{title}</h1>
      <form>
        <input
          value={inputUrl}
          onChange={e => setInputUrl(e.target.value)}
        />
        <button
          type="button"
          onClick={() => {
            setUrl(inputUrl);
            setTitle(inputUrl);
          }}
        >
          Submit
        </button>
      </form>
    </div>
  );
}

Notice also that I removed e.preventDefault() and added type="button" to your button, since the default type is submit, which would probably refresh your page.

like image 121
Chris Avatar answered Nov 15 '22 00:11

Chris