e

Tyylien lisääminen React-sovellukseen

Sovelluksemme ulkoasu on tällä hetkellä hyvin vaatimaton. Osaan 0 liittyvässä tehtävässä 0.2 oli tarkoitus tutustua Mozillan CSS-tutoriaaliin.

Katsotaan vielä tämän osan lopussa nopeasti kahta tapaa liittää tyylejä React-sovellukseen. Tapoja on useita ja tulemme tarkastelemaan muita myöhemmin. Liitämme ensin CSS:n sovellukseemme vanhan kansan tapaan yksittäisenä, käsin eli ilman esiprosessorien apua kirjoitettuna tiedostona (tämä ei itseasiassa ole täysin totta, kuten myöhemmin tulemme huomaamaan).

Tehdään sovelluksen hakemistoon src tiedosto index.css ja liitetään se sovellukseen lisäämällä tiedostoon index.js seuraava import:

import './index.css'

Lisätään seuraava sääntö tiedostoon index.css:

h1 {
  color: green;
}

CSS-säännöt koostuvat valitsimesta, eli selektorista ja määrittelystä eli deklaraatiosta. Valitsin määrittelee, mihin elementteihin sääntö kohdistuu. Valitsimena on nyt h1, eli kaikki sovelluksessa käytetyt h1-otsikkotägit.

Määrittelyosa asettaa ominaisuuden color, eli fontin värin arvoksi vihreän, eli green.

Sääntö voi sisältää mielivaltaisen määrän määrittelyjä. Muutetaan edellistä siten, että tekstistä tulee kursivoitua, eli fontin tyyliksi asetetaan italics:

h1 {
  color: green;
  font-style: italic;}

Erilaisia selektoreja eli tapoja valita tyylien kohde on lukuisia.

Jos haluamme kohdistaa tyylejä esim. jokaiseen muistiinpanoon, voisimme nyt käyttää selektoria li, sillä muistiinpanot ovat li-tagien sisällä:

const Note = ({ note, toggleImportance }) => {
  const label = note.important 
    ? 'make not important' 
    : 'make important';

  return (
    <li>
      {note.content} 
      <button onClick={toggleImportance}>{label}</button>
    </li>
  )
}

lisätään tyylitiedostoon seuraava (koska osaamiseni tyylikkäiden web-sivujen tekemiseen on lähellä nollaa, nyt käytettävissä tyyleissä ei ole sinänsä mitään järkeä):

li {
  color: grey;
  padding-top: 3px;
  font-size: 15px;
}

Tyylien kohdistaminen elementtityypin sijaan on kuitenkin hieman ongelmallista, jos sovelluksessa olisi myös muita li-tageja, kaikki saisivat samat tyylit.

Jos haluamme kohdistaa tyylit nimenomaan muistiinpanoihin, on parempi käyttää class selectoreja.

Normaalissa HTML:ssä luokat määritellään elementtien attribuutin class arvona:

<li class="note">tekstiä</li>

Reactissa tulee kuitenkin classin sijaan käyttää attribuuttia className, eli muutetaan komponenttia Note seuraavasti:

const Note = ({ note, toggleImportance }) => {
  const label = note.important 
    ? 'make not important' 
    : 'make important';

  return (
    <li className='note'>      {note.content} 
      <button onClick={toggleImportance}>{label}</button>
    </li>
  )
}

Luokkaselektori määritellään syntaksilla .classname, eli:

.note {
  color: grey;
  padding-top: 5px;
  font-size: 15px;
}

Jos nyt lisäät sovellukseen muita li-elementtejä, ne eivät saa muistiinpanoille määriteltyjä tyylejä.

Parempi virheilmoitus

Toteutimme äsken olemassaolemattoman muistiinpanon tärkeyden muutokseen liittyvän virheilmoituksen alert-metodilla. Toteutetaan se nyt Reactilla omana komponenttinaan.

Komponentti on yksinkertainen:

const Notification = ({ message }) => {
  if (message === null) {
    return null
  }

  return (
    <div className="error">
      {message}
    </div>
  )
}

Jos propsin message arvo on null ei renderöidä mitään, muussa tapauksessa renderöidään viesti div-elementtiin. Elementille on liitetty tyylien lisäämistä varten luokka error.

Lisätään komponentin App tilaan kenttä error virheviestiä varten, laitetaan kentälle heti jotain sisältöä, jotta pääsemme heti testaamaan komponenttia:

const App = () => {
  const [notes, setNotes] = useState([]) 
  const [newNote, setNewNote] = useState('')
  const [showAll, setShowAll] = useState(true)
  const [errorMessage, setErrorMessage] = useState('some error happened...')
  // ...

  return (
    <div>
      <h1>Notes</h1>
      <Notification message={errorMessage} />      <div>
        <button onClick={() => setShowAll(!showAll)}>
          show {showAll ? 'important' : 'all' }
        </button>
      </div>      
      // ...
    </div>
  )
}

Lisätään sitten virheviestille sopiva tyyli:

.error {
  color: red;
  background: lightgrey;
  font-size: 20px;
  border-style: solid;
  border-radius: 5px;
  padding: 10px;
  margin-bottom: 10px;
}

Nyt olemme valmiina lisäämään virheviestin logiikan. Muutetaan metodia toggleImportanceOf seuraavasti:

  const toggleImportanceOf = id => {
    const note = notes.find(n => n.id === id)
    const changedNote = { ...note, important: !note.important }

    noteService
      .update(changedNote).then(returnedNote => {
        setNotes(notes.map(note => note.id !== id ? note : returnedNote))
      })
      .catch(error => {
        setErrorMessage(          `Note '${note.content}' was already removed from server`        )        setTimeout(() => {          setErrorMessage(null)        }, 5000)        setNotes(notes.filter(n => n.id !== id))
      })
  }

Eli virheen yhteydessä asetetaan tilaan errorMessage sopiva virheviesti. Samalla käynnistetään ajastin, joka asettaa 5 sekunnin kuluttua tilan errorMessage-kentän arvoksi null.

Lopputulos näyttää seuraavalta

26e

Sovelluksen tämänhetkinen koodi on kokonaisuudessaan githubissa, branchissa part2-7.

Inline-tyylit

React mahdollistaa myös tyylien kirjoittamisen suoraan komponenttien koodin joukkoon niin sanoittuina inline-tyyleinä.

Periaate inline-tyylien määrittelyssä on erittäin yksinkertainen. Mihin tahansa React-komponenttiin tai elementtiin voi liittää attribuutin style, jolle annetaan arvoksi Javascript-oliona määritelty joukko CSS-sääntöjä.

CSS-säännöt määritellään Javascriptin avulla hieman eri tavalla kuin normaaleissa CSS-tiedostoissa. Jos haluamme esimerkiksi asettaa jollekin elementille vihreän, kursivoidun ja 16 pikselin korkuisen fontin, eli CSS-syntaksilla ilmaistuna

{
  color: green;
  font-style: italic;
  font-size: 16px;
}

tulee tämä muotoilla Reactin inline-tyylin määrittelevänä oliona seuraavasti

 {
  color: 'green',
  fontStyle: 'italic',
  fontSize: 16
}

Jokainen CSS-sääntö on olion kenttä, joten ne erotetaan Javascript-syntaksin mukaan pilkuilla. Pikseleinä ilmaistut numeroarvot voidaan määritellä kokonaislukuina. Merkittävin ero normaaliin CSS:ään on väliviivan sisältämien CSS-ominaisuuksien kirjoittaminen camelCase-muodossa.

Voisimme nyt lisätä sovelluksemme "alapalkin", muodostavan komponentin Footer, ja määritellä sille inline-tyylit seuraavasti:

const Footer = () => {
  const footerStyle = {
    color: 'green',
    fontStyle: 'italic',
    fontSize: 16
  }

  return (
    <div style={footerStyle}>
      <br />
      <em>Note app, Department of Computer Science, University of Helsinki 2020</em>
    </div>
  )
}

const App = () => {
  // ...

  return (
    <div>
      <h1>Notes</h1>

      <Notification message={errorMessage} />

      // ...  

      <Footer />    </div>
  )
}

Inline-tyyleillä on tiettyjä rajoituksia, esim. ns. pseudo-selektoreja ei ole mahdollisuutta käyttää (ainakaan helposti).

Inline-tyylit ja muutamat myöhemmin kurssilla katsomamme tavat lisätä tyylejä Reactiin ovat periaatteessa täysin vastoin vanhoja hyviä periaatteita, joiden mukaan Web-sovellusten ulkoasujen määrittely eli CSS tulee erottaa sisällön (HTML) ja toiminnallisuuden (Javascript) määrittelystä. Vanha koulukunta pyrkiikin siihen että sovelluksen CSS, HTML ja Javascript on kaikki kirjoitettu omiin tiedostoihinsa.

Itseasiassa Reactin filosofia on täysin päinvastainen. Koska CSS:n, HTML:n ja Javascriptin erottelu eri tiedostoihin ei ole kuitenkaan osoittautunut erityisen skaalautuvaksi ratkaisuksi suurissa ohjelmistoissa, on Reactissa periaatteena tehdä erottelu (eli jakaa sovelluksen koodi eri tiedostoihin) noudattaen sovelluksen loogisia toiminnallisia kokonaisuuksia.

Toiminnallisen kokonaisuuden strukturointiyksikkö on React-komponentti, joka määrittelee niin sisällön rakenteen kuvaavan HTML:n, toiminnan määrittelevät Javascript-funktiot kuin komponentin tyylinkin yhdessä paikassa, siten että komponenteista tulee mahdollisimman riippumattomia ja yleiskäyttöisiä.

Sovelluksen lopullinen koodi on kokonaisuudessaan githubissa, branchissa part2-8.