React-Bootstrap: modal się nie chowa

react
programowanie

#1

Cześć!

Poszukuję wymiatacza React’owego, który pomoże mi z moim problemem :wink: Sprawa wygląda następująco: w moim (i nie tylko) dość dużym (nawet bardzo) projekcie tworzę formę, która ma się wyświetlać wewnątrz modalu. Modal jest w osobnym komponencie, akcja wywołania modala znajduje się w osobnym komponencie, a pomiędzy nimi jest jeszcze jeden komponent, który mając na uwadze kliknięty przez nas element wyświetla odpowiednią formę (i to już np. jest ten modal). Tak w skrócie wygląda flow, a teraz przejdę do większych szczegółów i samego kodu:

Rodzic (w nim następuje kliknięcie i w nim inicjuję prop, który będzie odpowiedzialny za wyświetlenie modala):

/* ... */
showModal: true // tylko tyle dotyczy modala w onClick
/* ... */
{this.state.addingState && (
    <MyForm
        {...this.state.item}
        show={this.state.showModal}
        /* ... */
    />
)}

Teraz komponent MyForm, który zajmuje się przekazywaniem prop do odpowiednich komponentów tworzących odpowiednie formy:

createModalComponent {
    /* ... */
    let modalComponentProps= {
        /* ... */
        show: this.props.show
        /* ... */
     }
}

Na koniec sam komponent z modalem:

/* ... */
constructor(props) {
    super(props);

    this.state = {
        show: this.props.show
    };

    this.handleHide = this.handleHide.bind(this);
}
/* ... */
handleHide() { 
   this.setState({ show: !this.state.show });
}
/* ... */
     <React.Fragment>
            <Modal
                {...this.props}
                bsSize="large"
                aria-labelledby="contained-modal-title-lg"
                show={this.state.show}
                onHide={this.handleHide}
             >
            <Modal.Body>
               ...
            </Modal.Body>
            <Modal.Footer>
               <Button id = "closeModal" variant="danger" onClick= 
               {this.handleHide}>
                            Save and close
               </Button>
            </Modal.Footer>
            </Modal>
     </React.Fragment>

Próbowałem sam się z tym uporać, ale nic niestety nie udało mi się zmienić. Interesujące jest to, że modal po kliknięciu się wyświetla, ponadto this.state.show zmienia się tak, jak można by tego oczekiwać. To coś z metodą handleHide() jest nie tak (przynajmniej tak myślę), ale naprawdę nie mam pojęcia co… Za jakąkolwiek pomoc serdecznie dziękuję! :slight_smile:


#2

Zanim przejdziemy do naprawiania modala, warto zadać sobie pytanie - czy modal na pewno jest tutaj konieczny?

Inne rozwiązania UI sprawiają mniej bólu głowy zarówno programistom jak i użytkownikom… :wink:


#3

hmm, to prawda… ale nie wiem co innego wybrać… modal ma być dla całej formy i po to aby w czasie jej edycji pozostała część strony była niedostępna do momentu jej zapisania


#4

A co pojawia się w tym modalu? Opisz trochę kontekst


#5

w rodziciu możesz wybrać z dropdown czy chcesz ankietę, sondę czy komentarz. Rodzic też jest formą. Czyli w jednej formie wybierasz kolejną formę do wyrenderowania. I ta kolejna forma miałaby się pojawiać w modalu (albo w czymś co będzie rozsądniejsze :wink: )


#6

(nie mogę nie zwrócić uwagi - dotychczas spotkałem się tylko z odmianą "ten form / w tym formie / z tym formem" :wink: )

A czy ten nowy form nie może po prostu pojawiać się pod spodem tego dropdowna?


#7

No wiem, też właśnie pisałem z taką konsternacją coś mi nie pasowało, za rzadko takie rzeczy się po polsku pisze :smiley: Do tej pory było właśnie tak jak napisałeś, ale generowało to szereg różnych błędów i ciężko to było ogarnąć, łatwiejsze będzie zrobienie modala/czegoś podobnego. No i również zależy mi na tym, żeby pozostała część formy (mam nadzieję, że dobrze :smiley: ) była wtedy zablokowana. Modal to pierwsze co mi przyszło do głowy przy takich “wytycznych”.


#8

Tylko dlaczego zrobienie modala ma być łatwiejsze?

Jak nie chcesz aby formularz był edytowalny to ustawiasz w każdym inpucie disabled=true i tyle, możesz go jeszcze wizualnie wyszarzyć


#9

tak tak, ale pozostaje jeszcze kwestia wizualna. Osoby odpowiedzialne za projekt nalegają, żeby to był właśnie modal również z uwagi na kwestie wizualne. No ale może istnieje coś w podobnym guście co nie jest modalem :confused:


#10

Nie ma to, jak gdy klienta obchodzi bardziej “”""“estetyka”"""" niż funkcjonalność :stuck_out_tongue:

Możesz przygotować mini-repo, które pomoże odtworzyć ten błąd? Sklonuję sobie i postaram się coś zdiagnozować.

(W międzyczasie możesz sprobować dalej tłumaczyć zleceniodawcom, że modale są szkodliwe :wink: )


#11

Bardzo Ci dziękuję! Jutro pogadam z osobami decyzyjnymi i spróbuję ich przekonać do zmiany koncepcji :wink: Jeżeli w swej mądrości i doskonałości się nie ugną to wrzucę Ci kod, bo będę potrzebować pomocy :smiley:


#12

no to tego… potrzebuję pomocy przy modalu :smiley: całości projektu nie mogę Ci udostępnić, bo jest bardzo duży + niestety względy prywatności i w ogóle :confused: ale wrzuciłem samą konstrukcję modala na codepen.io. Jeśli będzie tego za mało żeby cokolwiek stwierdzić to daj znać i postaram się odpowiednio uzupełnić.


#13

teraz zauważyłem, że jak kliknę w button, który ma chować modal, a następnie na sam modal, to wtedy się chowa. samo kliknięcie w button jednak tego nie sprawia. i po tym już nie można wywołać modala po raz kolejny
[EDIT] Uwielbiam po prostu takie sytuacje… Grzebałem grzebałem i teraz chowa się poprawnie po kliknięciu… Tyle, że nie mam pojęcia jak to się stało. No ale wciąż występuje problem, że nie można modala ponownie wywołać :confused:
[EDIT 2] Wiem co sprawiło, że modal zaczął się chować - zakomentowałem tę część:

shouldComponentUpdate(nextProps) {
    return !isEqual(this.props, nextProps);
}

#14

Czyli problem rozwiązany? :smiley:

Jeżeli nie, to przygotuj proszę małe repo, które będę mógł sklonować, zbudować wszystko i odpalić i zobaczyć ten problem na własne oczy. Wiem, że proszę o dużo, ale pomoże to wykluczyć różne inne czynniki i ułatwi mi diagnozę.

Jak chcesz, możesz stworzyć repo na Sealhubie:

https://hub.sealcode.org/diffusion/edit/form/default/


#15

Wtedy jeszcze nie był rozwiązany, ale teraz już tak :sleepy: Tylko teraz stoję przed kolejnym problemem, tym razem koncepcyjnym - jak najlepiej rozwiązać sprawę formularza wewnątrz modala wewnątrz formularza? W sensie jak najlepiej zająć się danymi? Myślałem o czymś takim, że po zakończeniu edycji i kliknięciu w zapisz modal się zamyka, a pojawia się formularz z modala z “disabled” polami, które wypełnione są danymi podanymi w modalu. Ale też myślę jak ugryźć taki problem - przy pomocy state? Co do tego? Masz może jakieś pomysły? :slight_smile:


#16

Najlepiej oczywiście trzymać dane w jednym miejscu, aby uniknąć problemów z synchronizwaniem zmian w tych danych.

Jeżeli wszystkie komponenty należą do jednego, łatwego do wydzielenia drzewa, to możesz po prostu wepchnąć wszystkie wartości pól do state komponentu, który jest korzeniem tego drzewa. Następnie do podkomponentów wystarczy podawać te wartości (i ich settery) jako propsy.

Jeżeli komponenty są bardziej “rozproszone”, to możesz użyć jakiegoś globalnie dostępnego obiektu, w którym będziesz trzymał wszystkie wartości - tzw. “Store”. Na pewno jest dużo gotowych store’ów do Reacta. Ja korzystam z reguły z prostego, “domowej roboty” store’a, aby zmniejszyć ilość zależności.

const EventEmitter = require("event-emitter");

class KeyValueStore {
	constructor(initial_values) {
		this.ee = new EventEmitter();
		this.values = initial_values || {};
		this.on = (event, callback) => this.ee.on(event, callback);
		this.off = (event_name, listener) => this.ee.off(event_name, listener);
	}

	set(key, value) {
		const key_elements = key.split(".").reverse();
		let current_pointer = this.values;
		while (key_elements.length > 1) {
			let current_key = key_elements.pop();
			if (current_pointer[current_key] === undefined) {
				current_pointer[current_key] = {};
			}
			current_pointer = current_pointer[current_key];
		}
		current_pointer[key_elements.pop()] = value;
		this.ee.emit("change", this.values);
	}

	get(key) {
		return this.values[key];
	}

	replaceStore(values) {
		this.values = values;
		this.ee.emit("change", this.values);
	}

	getStore() {
		return this.values;
	}
}

module.exports = KeyValueStore;

Wystarczy stworzyć instancję tego store’a:

const FormStore = new KeyValueStore({field1: "value", field2: "value"});

I za pomocą takiego komponentu:

const React = require("react");
const merge = require("merge");

const ConnectWithKeyValueStore = function(store, store_prop_name, component) {
	return class extends React.Component {
		constructor(props) {
			super(props);
			this.state = { [store_prop_name]: {} };
		}
		componentDidMount() {
			const self = this;
			const listener = function(value) {
				self.setState({ [store_prop_name]: value });
			};
			store.on("change", listener);
			self.setState({
				listener: listener,
				[store_prop_name]: store.getStore(),
			});
		}
		componentWillUnmount() {
			const self = this;
			(store.off || store.removeListener).bind(store)(
				"change",
				self.state.listener
			);
		}
		render() {
			return React.createElement(
				component,
				Object.assign({}, this.props, {
					[store_prop_name]: this.state[store_prop_name],
				})
			);
		}
	};
};

można “przypinać” dowolny komponent do tego store’a:

const ModalView = ({ store_values }) => (
  <form>
    <input
      type="text"
      name="field1"
      value={store_values.field1}
      onChange={e => FormStore.set("field1", e.target.value)}
    />
  </form>
);
const ConnectedModal = ConnectWithKeyValueStore(
  FormStore,
  "store_values",
  ModalView
);

Chyba, że już w projekcie korzystacie z Reduxa albo innej biblioteki do zarządania stanem apki, to w tedy nie ma potrzeby wymyślać koła od nowa :wink: