0

I have seen other questions with the exact same title, but none solved my problem: This is my code:

import React, { useState } from 'react';
import Validation from './Validation/Validation';
import Char from './Char/Char';

function App() {
  const [textState, textSetState] = useState('');
  const [charsState, charsSetState] = useState([]);

  let inputChange = (event) => {
    textSetState(event.target.value);
    charsSetState(event.target.value.split('').map((char, index) => {
      return <Char char={char} key={index} click={() => { deleteCharHandler(index) }} />
    }));
  }

  const deleteCharHandler = (index) => {
    alert(textState);
  }
  return (
    <div className="App">
      <input type="text" onChange={(event) => inputChange(event)} />
      <Validation text={textState} />
      {charsState}
    </div>
  );
}

export default App;

This is the result:

enter image description here

When I click a character, it displays the value from 1 step behind, like the example above.

rafael.js
  • 550
  • 6
  • 16
  • 2
    Does this answer your question? [React setState not updating state](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state) – Brian Ogden Dec 17 '19 at 22:49
  • React state is asynchronous the value of textState has not yet updated to the new value when you alert – Brian Ogden Dec 17 '19 at 22:50
  • Why are you storing components in state? – Asaf Aviv Dec 17 '19 at 22:55
  • 1
    @BrianOgden Hello. No, because I need to delete a character when I click it, so adding a callback for when I change the state will not do the job. – rafael.js Dec 17 '19 at 22:56
  • is your goal to render text from input as sequence of `` and later delete some char after being clicked? – skyboyer Dec 17 '19 at 23:01
  • @skyboyer yes it is – rafael.js Dec 17 '19 at 23:05
  • @AsafAviv to re-render the components everytime I change the text – rafael.js Dec 17 '19 at 23:06
  • 1
    When you update the text the component is going to re-render, then just map over the text inside the JSX and render the `Char` components. when you want to remove a char just update the text state – Asaf Aviv Dec 17 '19 at 23:07
  • One more thing: When I changed it to a class component, it worked as intended. – rafael.js Dec 17 '19 at 23:09
  • 1
    sure, because for class-based component you are accessing most recent `state` by reference `this.state`. While in functional components you are typically facing closure(and stale data in closure is reason you face this issue above) – skyboyer Dec 17 '19 at 23:11
  • @AsafAviv coreyward's answer + your comment solved it. – rafael.js Dec 17 '19 at 23:13
  • This seems like a perfectly acceptable question, not sure why thumbs down? – Chris Oct 25 '21 at 21:08

4 Answers4

3

You're putting an array of rendered Char components in your state, then rendering it. There are a number of issues with this approach.

  1. The local copy of the state (charsState) will not be updated immediately; instead React will re-render the component with the new value for state.
  2. Since you are defining the onClick callback within the function, deleteCharHandler will always be referencing an outdated copy of the state.
  3. Multiple state hooks being updated in lockstep in this way will cause additional re-renders to happen.

Since the naming in your example is a bit confusing it's hard to tell what the desired behavior is to make good recommendations for how to resolve or refactor.

coreyward
  • 77,547
  • 20
  • 137
  • 166
2

So there are a few things which may or may not be causing an issue so lets just clear a few things up:

  1. You don't need to pass the anonymous function on the input:

    <input type="text" onChange={inputChange} /> should suffice

  2. As with OG state, it's never a good idea to call two setStates simultaneously, so lets combine the two:

    const [state, setState] = useState({text: '', char: []});

Once you've updated everything you should be setting one state object onClick.

  1. Your Char object is using click instead of onClick? unless you are using that as a callback method i'd switch to:

    return <Char char={char} key={index} OnClick={() => deleteCharHandler(index)} />

If that doesn't fix your solution at the end, you can simply pass the deleteCharHandler the updated text value instead of re-grabbing the state value

1

I think you need useCallback or to pass textState in parameter. the deleteCharHandler method doesn't change in time with the textState value.

try :

return <Char char={char} key={index} click={() => { deleteCharHandler(index, textState) }} />
...

const deleteCharHandler = (index, textState) => {
    alert(textState);
  }

or :

import React, { useState, useCallback } from 'react';
...
const deleteCharHandler = useCallback(
  (index) => {
    alert(textState);
  }, [textState]);
1

Try this

function App() {
    const [textState, textSetState] = useState('');

    const inputChange = useCallback((event) => {
        textSetState(event.target.value);
    },[])

    function renderChars(){
        return textState.split('').map((char, index) => {
            return <Char char={char} key={index} click={() => { deleteCharHandler(index) }} />
        }));
    }

    const deleteCharHandler = useCallback( (index) => {
         alert(textState);
    }, [textState])

    return (
        <div className="App">
            <input type="text" onChange={inputChange} />
            <Validation text={textState} />
           {renderChars()}
       </div>
   );
}