MAK
MAK

Reputation: 2283

Input text - 'defaultValue' is not loading the state value and 'value' is not updated on change

I have an input text as follows (MovieInput.tsx):

<input 
    id="inputMovieTitle" 
    type="text"
    onChange={ e => titleHandleChange(e) }
    value={ getTitle() }>
</input>

titleHandleChange:

const [title, setTitle] = useState('');

const titleHandleChange = (e: ChangeEvent<HTMLInputElement>) => {        
    setTitle(e.target.value);
}

getTitle:

const moviesInState = useSelector((state: any) => state.movies);

const getTitle = () => {      
    console.log(moviesInState.item.title); // It displays correct movie title from the store corresponding to the selected row
    return moviesInState.item.title;
}

I have a table (Grid.tsx) which lists all the movies from the state (state.movies.items[]). When I click any movie row in that table, that selected movie will be updated in the store i.e., state.movies.item.

Grid.tsx:

const dispatch = useDispatch();

const selectMovie = (movie: Movie) => {
    const movieSelected: Movie = {
        _id: movie._id,
        __v: movie.__v,
        title: movie.title,
        rating: movie.rating,
        year: movie.year
    }
    dispatch(getMovie(movieSelected));
}
...
<tbody>
    {
        props.allMovies.map((movie: Movie) => 
            <tr onClick={() => selectMovie(movie)}>
                <td>{movie.title}</td>
                <td>{(+movie.rating).toFixed(1)}</td>
                <td>{movie.year}</td>
            </tr>
        )
    }
</tbody>

Now, when I click any row in the table (Grid.tsx), it updates the store.movies.item in store and the text input (in MovieInput.tsx) is showing the title of the movie selected. When I change the row selected, the text input's value is also updated.

Why do I load the selected movie's title text in the text input?

Because, I should be able to edit the title value and update the record in DB. I select any row, change its title and then click 'Update' button and save. However, though the text is successfully populated in the text input and changing correctly while we change the row selected, the text input remain 'uneditable'. This is due to the following code:

value={ getTitle() }>

When I change 'value' to 'defaultValue' as follows, I am able to edit the text input, but this time, the text value is not changing when I select another row.

defaultValue={ getTitle() }>

It just works fine and show the updated values (when I change selected rows) until I edit the input. Once it is edited for the first time, it is not changing its value on another row click. Only the edited text remain but I expect the value to be changing when I select another row. However, the console log inside the getTitle() function displays the correct title from the store (store.movies.item) corresponding to the row selected.

Could you let me know how to do the following please?

Upvotes: 0

Views: 83

Answers (1)

wentjun
wentjun

Reputation: 42576

The reason why input does not display the updated value after change is because it is binded to the value from your redux store, through moviesInState.

From what I can see, you wanted the initial value of the input to come from getTitle(), whereas the updated values of the input should come from whatever the user has typed in.

Therefore, the first thing you should change is to set initial value of the title state with the value from the store.

const moviesInState = useSelector((state: any) => state.movies);
const getTitle = () => {      
    console.log(moviesInState.item.title); // It displays correct movie title from the store corresponding to the selected row
    return moviesInState.item.title;
}
const [title, setTitle] = useState(getTitle());

And given that the input has to be updated as the store is updated, the useEffect hook is needed to propagate the update in state.

useEffect(() => {
  if (moviesInState.item.title !== title) {
    setTitle(moviesInState.item.title);
  }
}, [moviesInState]);

Next, bind the title state to the value props of input. There are no issues with the titleHandleChange method, thus we can leave it the way it is. In addition, there is no need to rely on defaultValue, thus we can remove the usage of it.

<input 
  id="inputMovieTitle" 
  type="text"
  onChange={titleHandleChange}
  value={title}>
</input>

On another note, you should not use any, as this will remove the benefit of TypeScript's typechecking capabilities. Instead, you should define the state parameter as the root state type that you have defined for your redux store. For instance,

const moviesInState = useSelector((state: RootState) => state.movies);

Upvotes: 2

Related Questions