javascript-wtf
javascript-wtf

Reputation: 61

React state doesn't update immediately with useState

Apologies if you feel like this is a repeated question, but I couldn't find the solution for my problem in any of similar questions and couldn't understand why this problem was occurring.

I have a component called Selector.tsx which basically has an input field and a bunch of buttons that when clicked are supposed to create a card with the data from that input field.

selector.tsx:


const profileList: Profile[] = [
  {
    name: 'Website',
    value: 'website',
  },
  {
    name: 'Documentation',
    value: 'documentation',
  },
  {
    name: 'Telegram',
    value: 'telegram',
  },
  {
    name: 'Twitter',
    value: 'twitter',
  },
  {
    name: 'Github',
    value: 'github',
  },
  {
    name: 'Facebook',
    value: 'facebook',
  },
  {
    name: 'Blog',
    value: 'blog',
  },
  {
    name: 'Discord',
    value: 'discord',
  },
  {
    name: 'LinkedIn',
    value: 'linkedin',
  },
  {
    name: 'Slack',
    value: 'slack',
  },
];

export const Selector: FC<SelectorProps> = ({ wrapperClass }) => {
  const [inputVal, setInputVal] = useState('https://google.com');
  const [saved, setSaved] = useState<Saved[]>([]);

  const handleAdd = (val: string) => {
    if (!inputVal) return;
    const _saved = saved;
    _saved.push({ type: val, text: inputVal });
    setSaved(_saved);
  };

  const handleClearSaved = () => {
    setSaved([]);
  };

  const DisplayCard: FC<DisplayCardProps> = ({ text, type }) => (
    <div className="w-full text-left text-text-gray bg-red-100 px-4 py-1 border rounded-lg">
      <span className="text-gray-600 text-xs">{type}:</span>
      <span className="text-base text-black text-sm ml-4">{text}</span>
    </div>
  );

  return (
    <div className="mt-4 py-4 flex flex-col lg:flex-row-reverse gap-4 items-center">
      <div className="mt-8 space-y-2 overflow-y-auto border border-blue-500 p-4 rounded">
        {saved.length ? (
          saved.map(({ type, text }, i) => (
            <DisplayCard key={i} text={text} type={type} />
          ))
        ) : (
          <i>Nothing to show</i>
        )}
      </div>
      <div className="border rounded p-4">
        <div className="flex items-center gap-2">
          <input
            className="flex-grow pl-2 text-sm leading-6 border border-transparent bg-blue-100 rounded outline-none focus:border-prim-border"
            value={inputVal}
            onChange={(e) => setInputVal(e.target.value)}
          />
          <div className="mt-2 flex flex-col space-y-2">
            {profileList.map(({ name, value }, i) => (
              <button
                className="border border-red-400 text-sm bg-black bg-opacity-0 hover:bg-opacity-10 transform scale-100 active:scale-90 disabled:cursor-not-allowed"
                onClick={() => handleAdd(value)}
                disabled={!inputVal}
              >
                {name}
              </button>
            ))}
          </div>
        </div>
        <button
          className="mt-2 border border-red-500 transform scale-100 active:scale-90 px-2"
          onClick={handleClearSaved}
        >
          Clear Fields
        </button>
      </div>
    </div>
  );
};

Whenever we click any of the profile buttons (and the input field is not empty), the handleSave button is called & saved array state is supposed to get updated(object is pushed into the array) & based on that, DisplayCards are rendered.

The problem: The saved state doesn't get updated after any of the profile buttons are clicked. When we make changes in the input field(which updates the inputVal state), only then the saved state gets updated

Also weirdly enough, the handleClearSaved function works properly when the clear button is clicked. It is able to empty the saved array. But for some reason, pushing into the saved array doesn't seem to update the state.

Problem repoduced in Stackblitz: https://stackblitz.com/edit/well-well-well?file=components/Selector.tsx

I can't wrap my head around, why this behavior is occurring in react. I've never encountered such issues before. If any one can help or provide any hints on this, it would be really helpful. 🙏

Thanks.

Upvotes: 2

Views: 321

Answers (2)

ATTILA
ATTILA

Reputation: 102

the reason is you are doing variable(object) copy with reference and when reference is not changing your state is not updating

insted of

 const handleAdd = (val: string) => {
    if (!inputVal) return;
    const _saved = saved;
    _saved.push({ type: val, text: inputVal });
    setSaved(_saved);
  };

try this code

 const handleAdd = (val: string) => {
    if (!inputVal) return;
    setSaved((pre) => ([...pre, { type: val, text: inputVal });
  };

objects and arrays work with by reference which mean you can not copy them like this

const a = [1,2,3]
const b = a

for copying an array you should use ES6 Spread syntax to create new instance like this

const a = [1,2,3]
cosnt b = [...a]

react state check previous value and next value then do an update when reference is not changing, no render happens. for updating object state or array state you should create new instance, and you can use ES6 Spread syntax

Upvotes: 1

Helena S&#225;nchez
Helena S&#225;nchez

Reputation: 220

Use this to change the state directly:

const handleAdd = (val: string) => {
  if (!inputVal) return;
  setSaved(prevState => [...prevState, {type: val, text: inputVal}]);
};

Besides, you should add a key to the buttons map, it's giving you a console error.

Upvotes: 1

Related Questions