2

I have been advised to shallow copy an object, since copying the state object using only the = sign would "point" to the old state.

Now, I've got this: there's a list on my screen, and each item on it is an element from a state array. Each item has its own "change" button, and when that button is pressed, a react-modal is opened with all the item information into text inputs, so I'd be able to change it and press the "Update" button in the modal. After pressing it, the modal should close and the list should be refreshed, but this last thing doesn't happen.

This is the entire component code:

import React, { useContext, useEffect, useState } from 'react';
import { ThemeContext } from '../../providers/ThemeContext';
import axios from '../../services/axios';
import './styles.css';
import Modal from 'react-modal';
import { Divider } from '@material-ui/core';

Modal.setAppElement('#root')
function AllInOneProjectPage() {
    const [projects, setProjects] = useState<any[]>([]);
    const { theme } = useContext(ThemeContext);
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [id, setId] = useState('');
    const [title, setTitle] = useState('');
    const [url, setUrl] = useState('');
    const [techs, setTechs] = useState<any[]>([]);
    
    const openModal = (idModal: string, title: string, url: string, techs: any[]) => {
        setId(idModal);
        setTitle(title);
        setUrl(url);
        setTechs(techs);
        setIsModalOpen(true);
    }

    const closeModal = () => {
        setIsModalOpen(false);
    }

    const updateProjects = async () => {
        const res = await axios.get('list');
        res.data.status === 1 && setProjects(res.data.projects);
    };
    
    useEffect(() => {
        updateProjects();
    }, []);

    const onUpdateProject = async () => {
        const newTitle = (document.getElementById(`modal_title_${id}`) as HTMLInputElement)?.value;
        const newUrl = (document.getElementById(`modal_url_${id}`) as HTMLInputElement)?.value;
        const techsArray = (document.getElementById(`modal_techs_${id}`) as HTMLInputElement)?.value;
        const filteredTechs = techsArray.split(',').filter(tech => {
            if(tech !== '')
                if(tech !== ' ')
                    return tech;
        });
        
        await axios.patch(`update/${id}`, { title: newTitle, url: newUrl, techs: filteredTechs });
        setProjects(projects.map(p => id === p.id ? { ...p, title: newTitle, url: newUrl, techs: filteredTechs } : p));
        setIsModalOpen(false);
        console.log(projects.map(p => id === p.id ? { ...p, title: newTitle, url: newUrl, techs: filteredTechs } : p));
    };
    
    return (
        <div style={{
            width: '100vw',
            minHeight: 'calc(100vh - 64px)',
            backgroundColor: theme.bg,
            margin: '0',
            display: 'flex',
            justifyContent: 'space-around',
            flexWrap: 'wrap'
        }}>
            { projects && projects.map((p, i) => <div key={p.id} style={{
                display: 'flex',
                flexDirection: 'column',
                width: '550px',
                height: '300px',
                border: '1px solid blue',
                margin: '50px'
            }}>
                <div style={{ backgroundColor: 'green', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-around' }}>
                    <div style={{display: 'flex', flexDirection: 'column', width: '80%'}}>
                        <h4>Title:</h4>
                        <input type="text" name="title" disabled defaultValue={ p.title }/>
                    </div>
                    <div style={{display: 'flex', flexDirection: 'column', width: '80%'}}>
                        <h4>URL:</h4>
                        <input type="text" name="url" disabled defaultValue={ p.url }/>
                    </div>
                    <div style={{display: 'flex', flexDirection: 'column', width: '80%'}}>
                        <h4>Techs:</h4>
                        <input type="text" name="techs" disabled defaultValue={ p.techs.map((t: any) => t) }/>
                    </div>
                    <a style={{textDecoration: 'underline', color: 'darkblue', cursor: 'pointer' }} onClick={() => openModal(p.id, p.title, p.url, p.techs)}>Alterar</a>
                </div>
            </div>) }

            <Modal isOpen={isModalOpen} style={{
                content: {
                    display: 'flex',
                    flexDirection: 'column',
                    maxWidth: '450px',
                    height: '280px',
                    margin: 'auto',
                    backgroundColor: 'lightgrey'
                },
                overlay: {
                    backgroundColor: 'rgba(0, 0, 0, 0.85)'
                }
            }}>
                <div className="modal-header" style={{ display: 'flex', justifyContent: 'space-between' }}>
                    <h1>Edit { title }</h1>
                    <button onClick={closeModal} style={{ width: '90px', height: '20px', margin: 'auto 0' }}>Close</button>
                </div>
                <Divider />
                <div style={{ width: '80%', margin: '10px auto 0 auto'}}>
                    <h2>Title: </h2>
                    <input type="text" name="title" id={`modal_title_${id}`} defaultValue={ title } style={{ width: '90%' }}/>
                </div>
                <div style={{ width: '80%', margin: '10px auto 0 auto'}}>
                    <h2>URL: </h2>
                    <input type="text" name="title" id={`modal_url_${id}`} defaultValue={ url } style={{ width: '90%' }}/>
                </div>
                <div style={{ width: '80%', margin: '10px auto 0 auto'}}>
                    <h2>Techs: </h2>
                    <input type="text" name="title" id={`modal_techs_${id}`} defaultValue={ techs } style={{ width: '90%' }}/>
                </div>
                <button onClick={ () => onUpdateProject() } style={{ width: '90px', margin: '10px auto 0 auto' }}>Update</button>
            </Modal>
        </div>
    );
}

export default AllInOneProjectPage;
Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
João Casarin
  • 674
  • 2
  • 9
  • 27
  • Thanks for the clarification @AjeetShah, i'll do so! But I thought he would be tagged, I followed a "guide" on stachexchange that said that... Anyways, I'm doing it right now, and thanks again :D – João Casarin Apr 08 '21 at 23:01
  • Is your `projects` state *really* not updating, or are you just seeing the current not-yet-updated state in the console log right after the state update? I.E. `setProjects(projects.map( .... ));` then `console.log(projects.map( .... ));` the log is going to be the current state. – Drew Reese Apr 08 '21 at 23:18
  • The console log does show the already updated array, but the main array on screen should be updated right after the modal-close. The log works because I'm using the same logic of map on it – João Casarin Apr 09 '21 at 05:12
  • Outside of trying a functional state update (`setProjects(projects => projects.map( .... )`) I'm a bit stumped. Think you could create a *running* codesandbox that reproduces this update issue that we could live debug in? – Drew Reese Apr 09 '21 at 05:50
  • @DrewReese are you able to access this? I tried to create, but I haven't used that platform yet... https://codesandbox.io/s/youthful-shaw-3tx3q – João Casarin Apr 09 '21 at 23:19
  • Yes, I can and it looks like it loaded a page with a header, hamburger menu, and particle.js background. I'll take a deeper look when I've a moment. Can you remind me again what the reproduction steps for your issue are? Navigate to "/aioProject" and try to update a project entry? – Drew Reese Apr 09 '21 at 23:20
  • Oh sorry, forgot to mention that. Yes, you're absolutely correct, just go to AIO page using the hamburguer button, you'll see a list of projects, just try to update one of them pressing the "Alterar" button, so you will see a modal opens up, then just update any field you want. The modal will close, but the list won't "auto refresh", but if you update the page, the list is updated, what means that the update function works. – João Casarin Apr 09 '21 at 23:34
  • hey, wassup man, got a moment for this? I've changed a little the AIO.tsx file to be more readable as some were having problems with that... But no one could help on this at the end... – João Casarin Apr 13 '21 at 02:07
  • 1
    Oh, sorry, I've been a bit busy. I believe I was able to reproduce your issue in your sandbox but hadn't a chance to investigate just yet, though I will here in a bit. Ajeet, if you're already looking at this feel free to continue. – Drew Reese Apr 13 '21 at 02:51
  • I appreciate both AjeetShah and DrewReese efforts on trying to help my problems, you are such amazing developers, but mainly people :D thanks guys! – João Casarin Apr 13 '21 at 03:40

1 Answers1

2

Issue

I think the issue is a simple one; you've used defaultValue for the disabled inputs on the AllInOneProjectPage component. When an input uses the defaultValue prop it makes them uncontrolled inputs, meaning they don't respond to any external changes nor are they controlled by any state.

Solution

Convert these inputs over to controlled inputs by using the value prop instead.

{projects &&
  projects.map((p, i) => (
    <div key={p.id} style={outerDivProject}>
      <div style={projectFields}>
        <h4>Title:</h4>
        <input type="text" name="title" disabled value={p.title} /> // <-- now controlled
      </div>

      <div style={projectFields}>
        <h4>URL:</h4>
        <input type="text" name="url" disabled value={p.url} /> // <-- now controlled
      </div>

      <div style={projectFields}>
        <h4>Techs:</h4>
        <input
          type="text"
          name="techs"
          disabled
          value={p.techs.map((t: any) => t)} // <-- now controlled
        />
      </div>

      <p
        style={changeButtonStyles}
        onClick={() => openModal(p.id, p.title, p.url, p.techs)}
      >
        Alterar
      </p>
    </div>
  ))}

Forked Demo

Edit react-modal-updater-doesnt-refresh-page-after-setstate

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Dude, I really want to know why some stuffs simply doesn't work at first hahahaha.... I remember once I tried to use the prop `value`, and react just threw me an error demanding to use `defaultValue` instead, so somehow it stick in my mind, and I just followed that 'order'... Rep++++ thanks man :D – João Casarin Apr 13 '21 at 03:38