Flaskista Flask+Reactiin

Liittynyt
07.01.2021
Viestejä
685
Joku taisi jo aiemmin ehdottaa, että tekisin frontin sivustooni (www.sf-bibliografia.fi) Reactilla. Silloin tyrmäsin ajatuksen, kun oli ihan tarpeeksi opettelemista ja ihmettelemistä muutenkin. Tässä viestissä/ketjussa avaan vähän ajatuksia, kun aika niukasti tällaisesta siirrosta tuntuu löytyvän tietoa. Kommentteja, ehdotuksia jne saa esittää.

Nyt olen alkanut lämmetä ajatukselle. Siihen on pari syytä: ensinnäkin uskoisin, että voin tehdä homman sivu kerralla, kaikkea ei tarvitse tehdä ensin valmiiksi. Toiseksi sivuston käytettävyyden parantaminen nykyisessä ympäristössä on turhan työlästä. Kolmanneksi jinja on kohtuullisen karsea templaattikieli ja templaattini ovat jo melko hankalia ylläpidettäviä. Neljänneksi voisin siinä samalla tehdä vihdoin testit kuntoon.

Ajattelin aloittaa kirjoittamalla jonkun sivun vaatiman API-kutsun. Teen API:t saman tien blueprintiksi. Voisin ensin tehdä ihan ilman sen kummempia apupaketteja, mutta ehkä joku Flask-RESTful voisi olla ihan hyvä apu jossain vaiheessa. Mutta mitenkäs tuohon kannattaisi rakentaa testaus? Vai pitäisikö koko homma tehdä Postmanilla saman tien?

Sen jälkeen ajattelin polkaista pystyyn Typescript-pohjaisen React-projektin ihan omaan repoonsa. En siis sotke Flaskia ja Reactia samaan, kuten joissakin ohjeissa neuvotaan. Pysyy paremmin hallittavissa näin. Aloitan sellaisesta sivusta, joka ei vaadi autentikaatiota tai muuta monimutkaista. Koska Flask on portissa 5000 ja React portissa 3000, niin näiden pitäisi pystyä elämään rinnakkain. Jos ei muuten, niin käyttämällä juurikin noita porttinumeroita osoitteissa. Mutta tätä pitää vielä kokeilla. Olisi kiinnostavaa kuulla, jos jollain on tähän jotain ideaa. Sen verran kokeilin, että nuo palvelut todellakin pyörivät sulassa sovussa ainakin kehityskoneessa ja pääsin molempiin sisään. Mutta parempi olisi, jos joku http://www.sf-bibliografia.fi:3000/work/1000 -tyyppinen osoite ohjautuisi Flaskille. Tällä tavalla olisi helppo joskus tulevaisuudessa, kun konversio on tehty, vaihtaa palvelimelta oletusportti 5000 -> 3000.

Frameworkiksi otan React-Bootstrapin, näin uusien sivujen ulkoasua ei tarvitse sen kummemmin sovitella vanhojen kanssa (jotka on tehty juurikin Bootstrapilla). Mutta ajattelin poimia tarvitsemiani komponentteja komponenttikirjastoista. Tein pientä vertailevaa tutkimusta ja PrimeReactissa näyttäisi olevan jokseenkin kaikki tarvitsemani jutut. Varmaan kymmenisen kirjastoa tsekkasin, Mantine oli seuraavaksi paras.

Frontin testaamiseen Jest + React Testing Library vaikuttaa hyvältä kombolta.

Se on mainittava, että tavalliset käyttäjät näkevät vain pienen osan sivustoon käytetystä työstä. Ylläpitopuoli on vienyt suurimman osan ajasta. Siellä on mutkikkain UI, ja myös suurin osa kehitystarpeista tällä hetkellä. Normikäyttäjien kannalta parannusideoita on mm. erilaisten listojen lajittelun mahdollistaminen. Kaiken kaikkiaan kirjasin seuraavanlaisia tarpeita komponenteilta:
  • Tag/autocomplete. Eli kontrolli, jossa voi hakea kannasta arvoja kirjoittamalla ja ylläpitäjä voi mahdollisesti lisätä tästä suoraan uusia arvoja (nimensä mukaisia tageja esimerkiksi).
  • Search box - joku fiksu hakukenttä, joka näyttäisi koko ajan tuloksia ja näyttäisi ne muotoiltuna. Ehkä onnistuu edellisellä komponentilla, en ole varma. Poikkeaa vähän kuitenkin ajatukseltaan, kun tulokset sisältää monenlaisia eri asioita, kuten henkilöitä, teoksia ja artikkeleita.
  • In-place editing. Yksi tavoite alunperin minulla oli ylläpitonäkymissä, että ylläpitäjä voisi muokata kenttiä suoraan, samaan tapaan kuin esimerkiksi Jirassa. Tämä ei ole toteutunut, vaan näyttö pitää vaihtaa muokkaustilaan. Ajattelin tutkia, onnistuisiko nyt. Aika moni komponentti on mahdollista disabloida ei-ylläpitäjiltä. Nykyinen ratkaisu tuottaa turhan monimutkaisia templaatteja ja vaikeuttaa ylläpitoa rutkasti.
  • Rich-text-editori.
  • Modaalit.
  • Vuosiluvun valinta (siis pelkän vuosiluvun). Ei kovin tärkeä, onnistuuhan tuo ihan numerokentälläkin, mutta olisihan tuo hienompi.
  • Navbar. Löytyy Bootstrapista, joten käytän sitä.
  • File upload. Ellei löydy jotain olennaisia parannuksia, niin käytän Bootstrapin versiota.
  • Tri-state checkbox. Minulla on muutamia kenttiä, joilla on kolme arvoa: "Ei tietoa", "Kyllä" ja "Ei". Nyt nämä valitaan radiobuttoneilla. Tri-state checkbox olisi parempi.
  • Picklist/orderlist. Joitakin asioita, kuten novelleita kirjassa, pitäisi voida järjestellä helposti.
  • DataView. Olisi tarvetta esittää painoksia tiivimmässä muodossa, tällainen komponentti olisi siihen täydellinen.
  • Details List / Data table. Kontrolli, jossa käyttäjä voi valita haluamansa rivit. Microsoftin Fluid UI:ssa olisi tarvitsemaani käyttöön aika lailla täydellinen (Details list). PrimeReactissa Data table on sinne päin, mutta taitaa pientä tunkkaamista vaatia.
PrimeReactista löytyy muut paitsi pelkän vuosiluvun valinta ja search boxia ei löytynyt suoraan tuolla nimellä. Mutta varmaankin tehtävissä tarjolla olevilla komponenteilla.

Näistä lähtökohdista ajattelin lähteä liikkeelle. Riippuen, miten homma lähtee liikkeelle, konversio tapahtuu joko pitkän ajan kuluessa tai sitten annan periksi. Mutta tärkeintä siis on se, että en tee koko hommaa kerralla valmiiksi, koska se olisi helposti liikkuvaan maalitauluun tähtäämistä kun nykyinenkin toteutus vaatii huomiota ja tässä menee aikaa, kun kuitenkin omalla ajalla teen ja muutakin tekemistä on elämässä.
 
Viimeksi muokattu:
Liittynyt
31.01.2018
Viestejä
11
Koska Flask on portissa 5000 ja React portissa 3000, niin näiden pitäisi pystyä elämään rinnakkain. Jos ei muuten, niin käyttämällä juurikin noita porttinumeroita osoitteissa. Mutta tätä pitää vielä kokeilla. Olisi kiinnostavaa kuulla, jos jollain on tähän jotain ideaa. Sen verran kokeilin, että nuo palvelut todellakin pyörivät sulassa sovussa ainakin kehityskoneessa ja pääsin molempiin sisään. Mutta parempi olisi, jos joku http://www.sf-bibliografia.fi:3000/work/1000 -tyyppinen osoite ohjautuisi Flaskille. Tällä tavalla olisi helppo joskus tulevaisuudessa, kun konversio on tehty, vaihtaa palvelimelta oletusportti 5000 -> 3000.
Pyöritän omaa React & Flask projektiani dockerilla, Nginx kontti tarjoilee käyttäjälle React frontin ja ohjaa /api osoitteeseen tulevat kutsut Flaskia pyörittävälle Python kontille.
Kutakuinkin näin: How to Dockerize a React + Flask Project
 
Liittynyt
07.01.2021
Viestejä
685
Toistaiseksi siis pitäisi saada ohjattua kaikki sellaiset routet, joita ei löydy Reactista tuonne Flaskiin.

Onko joku käyttänyt Postmania? Koitan ekaa pathia sinne määritellä. Se olisi kaikessa yksinkertaisuudessaan /magazines, eikä vaadi parametrejä. Mutta tuo code generator ei suostu toimimaan, jos en määrittele tuolle pakollista parametriä. Siis tällaista:
Koodi:
                    {
                        "name": "id",
                        "in": "query",
                        "description": "ID of the user",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "format": "int32"
                        }
                    }
Vaikka vaihdan tuosta vain requiredin falseksi, niin generaattori lakkaa toimimasta. En löydä kyllä äkkiseltään OpenAPI:n speksistä tästä mitään.

Deploymentissa minulle taitaa riittää tuo chapter 2:n nginx-ratkaisu, koska se minulla jo onkin. Pitää vaan saada React mukaan sitten kun on jotain valmista.

Ääh, nyt tuo generaattori lakkasi toimimasta kokonaan. Onkohan tuo ihan susi? "Unable to generate server boilerplate. Try again later. ". Näyttää toimivan välillä, ja sitten taas ei.
 
Liittynyt
07.01.2021
Viestejä
685
Aloin funtsimaan tuota API:a ja mietin että olisiko tämä hyvä rakenne:

Ensinnäkin kaikki kutsut alkavat /api. Versionumeroa en käytä tässä vaiheessa. Jos tulee joskus hamassa tulevaisuudessa joku v2, niin sitten se tulee tuohon perään. Nyt versionumero on turha.

URL:t ovat sitten mallia:
/api/magazines
/api/magazines/{magazineId}
/api/magazines/{magazineId}/issues
/api/magazines/{magazineId}/publisher
/api/magazines/{magazineId/tags

Mutta jossain ehdotettiin, että noita kolmea viimeistä kannattaa vielä laajentaa:
/api/magazines/{magazinedId}/issues/{issueId}

Jos jo issues palauttaa issue-tyyppisen arrayn, niin en hoksaa mihin tätä tarvitaan.

Noihin sitten metodit tarpeen mukaan. GET on tietysti kaikille, PATCH, POST ja DELETE sen mukaan mikä on tarve. Kolmea viimeistä ei tietysti ole noille aliosoitteille, eli esimerkiksi kustantajan muutokset menee osoitteen
/api/publishers/{publisherId}
kautta.

Lisäksi funtsin, että noille objekteille voisi olla hyvä laittaa yhdeksi kentäksi URI, jolloin clientissä ei tarvitse tietää miten linkata resurssiin. Kun homman hoitaa vielä bäkkärissä url_for()-funkkarilla, niin sielläkään ei tarvitse tehdä mitään jos vaihtaa funkkarien nimiä.

Esimerkiksi yksi API-kutsu:
Koodi:
  /api/magazines/{magazineId}:
    parameters:
      - name: magazineId
        required: true
        in: path
        description: ID of the magazine
        schema:
          type: integer
          format: int32
    get:
      summary: 'Return info about a magazine'
      operationId: getMagazine
      tags:
        - Magazines
      responses:
        '200':
          description: 'Info about a magazine'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Magazine'
        default:
          description: Unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      summary: 'Update magazine info'
      operationId: updateMagazine
      tags:
          - Magazines
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Magazine'
      responses:
        '200':
          description: 'Success - magazine updated'
        default:
          description: Unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
Virhekoodeja pitää laajentaa, mutta kunhan nyt ensin jotain... bäkkäri saa hoitaa oikeat parametrit. Error-objektilla on vain koodi ja kuvaus, mutta virhekoodihan palautetaan Responsessa.
 
Liittynyt
17.10.2016
Viestejä
14 650
Aloin funtsimaan tuota API:a ja mietin että olisiko tämä hyvä rakenne:
Jos teet sitä APIa vain omaa tarvettasi varten, niin aloita minimaalisesti. Ehkä sulle riittää että magazines/ID antaa tagit ja publisherin. Ehkä ei ole mitään todellista tarvetta erillisille endpointeille. Laajentaa sitten jos on muulle tarvetta. GraphQL on yksi mahis, jossa kutsussa määritellään data, jota halutaan saada. Jos haluat tagit, pyydät kutsussa tagejä.
 
Liittynyt
07.01.2021
Viestejä
685
Siis tarkoitus on tehdä rajapinta Reactia varten, eikös kaikki data pidä saada silloin tuota kautta ulos ja muutokset sisään?

Postmanilla siis tätä väsään.
 
Liittynyt
17.10.2016
Viestejä
14 650
Siis tarkoitus on tehdä rajapinta Reactia varten, eikös kaikki data pidä saada silloin tuota kautta ulos ja muutokset sisään?

Postmanilla siis tätä väsään.
Joo pitää. Mutta tosiaan käyttötarpeesta riippuu, mitä kaikkea pitää muokata ja mille kaikelle tarvitaan oma endpoint. Esim. GET:iä et välttämättä tarvitse noille kaikelle, vaikka jonkun muun metodin tarvitsisitkin. Pitääkö esim. erikseen voida hakea jonkun tietyn lehden julkaisija omasta endpointista? Tai pitääkö sitä voida muokata oman endpointin kautta? Tai ehkä julkaisijan voisi muuttaa PUT:lla tai PATCH:llä käyttäen sitä magazines/ID-endpointtia. Tuon voi tehdä monella tavalla.
 
Liittynyt
07.01.2021
Viestejä
685
Päädyin tuohon ratkaisuun, koska halusin pitää käsittelyn simppelinä. Jos kaikki menisi magazines-endpointin kautta, niin eikös siinä tule aika monimutkainen datamalli? Jos endpoint on
/issues/{issueId}
niin PATCH:lle tarvitsee antaa parametrinä vain Issue-skema. Sama juttu /issues-POST:lle, yksi objekti riittää, ja se voi palauttaa vaikka samantyyppisen objektin.

GET:iä pitää miettiä, mutta ajattelisin niin, että frontin puolella jos vaikka on sivu, joka näyttää lehden ja siinä on listattu kaikki numerot, niin datan haku on helpointa endpointeista
/magazines/{magazineId} ja /magazines/{magazineId}/issues

Noilla issueilla, mitkä jälkimmäisestä tulee on sitten URI, eli tuon pidemmälle ei tarvitse mennä. Tuon kautta ei myöskään pitäisi tarvita muokata mitään, pelkkä GET riittää. Muokkaaminen menee /issues/{issueId}-endpointin kautta.
 
Liittynyt
17.10.2016
Viestejä
14 650
Äh, my bad. Mulla meni päässä magazinet ja issuet iloisesti sekaisin :facepalm: Joo, just näin. Tarkoitin siis yksitäistä issueta. Eli jos avaa yhden issuen, voi jossain tilanteessa olla perusteltua, että sieltä tulee tagit ja publisher samassa vastauksessa. Ettei tarvitse ampua 3 requestia tiedolle, jota aina tarvitaan kun näytetään issue.
 
Liittynyt
07.01.2021
Viestejä
685
Tuota just itsekin vähän pohdin, koska kyllähän nuo tiedot issuen sivua näytettäessä tarvitaan. Toisaalta sitten kun listataan yhden lehden numerot, niin tarvitaan vain id, numero(t) ja mahdollinen otsikko (numerotietoa on juokseva numero, vuosi ja kuluvan vuoden numero). Tuo on vielä suht pieni tietue, mutta joissakin tapauksissa kenttiä on paljon enemmän, osa text-tyyppisiä (eli voivat olla hyvinkin pitkiä) ja niistäkään ei välttämättä tarvita kuin muutama kenttä kun listataan jonkun alle kuuluvia tietoja.
 
Liittynyt
07.01.2021
Viestejä
685
Mietin eri puolia, ja koska tätä voi tarvittaessa myöhemmin muuttaa, niin aloitan tuolla alkuperäisellä suunnitelmalla ihan sen yksinkertaisuuden vuoksi. Varsinkin backend on helppo tehdä kun endpointin paluuarvo ei sisällä aliobjekteja. Saatan joutua pari poikkeusta matkan varrella tekemään. Noita requesteja ammutaan jo nyt kuitenkin useita, kun lataillaan resursseja.

Korjailen sitten jos tein väärän ratkaisun, mutta tällä pääsee ainakin alkuun.
 
Liittynyt
07.01.2021
Viestejä
685
Ja siis se ajatus on se, että nuo bäkkärin funktiot olisivat jokseenkin tämän näköisiä:
Koodi:
session = new_session()
magazines  = session.query(Magazine).all()
return Response(jsonify(magazines))
Yksittäistä magazinea varten lisätään vain filtteri. Issuet haetaan käyttäen Magazine.id:tä filtterinä. Jne. Jos vaikka Magazine-objektiin haluaa lisätä issuet, niin tuossa kai pitää kasata se rakenne käsin hakemalla Magazine ja Issuet. Ainakaan suoraan queryllä sitä tulosta ei oikein voi saada aikaan.

Tietääkö muuten joku jotain paikkaa, mistä saisi infoa, miten käyttää Flaskin loginmanageria ja Reactia yhdessä? Olen toteuttanut käyttäjien hallinnan tuolla palikalla ja pitäisi saada se toimimaan myös Reactin kanssa.
 
Liittynyt
20.03.2017
Viestejä
184
Ei nyt kannata liikaa miettiä sitä Reactia, se on kuitenkin vain käyttöliittymäkirjasto pohjimmiltaan. REST-apin ei pitäisi välittää millä tavalla tehty (tai edes mikä) client sitä käyttää. Kirjautumiset, api-kutsut yms. on kuitenkin ihan samaa tavaraa teitpä niitä selaimella tai vaikka Postmanilla.
 
Liittynyt
07.01.2021
Viestejä
685
Siis nimenomaan en mieti Reactia vaan Flaskia, eli backendia. Mutta jotenkin tuo autentikaatio pitää saada hoidettua, koska tuon kautta tullaan ylläpitämään tietokanta. Jos teen tuon API:n niin melkeinpä toinen sivu jonka teen käyttämään sitä vaatii jo autentikoinnin.
 
Liittynyt
20.03.2017
Viestejä
184
/login endpoint ja kirjaa käyttäjän sisään, ei kai siinä sen kummempaa ole? Ei-julkisiin endpointeihin sitten tarkistukset ja ohjaus kirjautumaan tarvittaessa.

En tiedä miten flaskissa tarkalleen mutta yleensä ihan perus middleware ketjuillahan noita tehdään.

Tyyliin GET /profile : requireAuth(), getProfile()
 
Liittynyt
07.01.2021
Viestejä
685
Tuota API:a vielä pohdittuani tajusin, että pitäähän ne käyttäjän syöttämät tiedotkin saada takaisin, joten tuo rakenne ei riitä kuitenkaan.

Flask-marshmallow vaikuttaisi hyvältä komponentilta tuottamaan SQLAlchemyn datasta JSON:ia. Toki tuossa pitää kerran tehdä nuo modelit (autoschema ei näyttäisi toimivan sisäkkäisten rakenteiden kanssa), mutta vaikuttaa suoraviivaiselta hommalta.
 
Liittynyt
07.01.2021
Viestejä
685
Marshmallowin kanssa piti tapella, koska eipä ne latest-tason ohjeetkaan pitäneet paikkaansa.

Ongelmallisempaa näytti olevan Postmanin tuottama API-koodi, joka oli suoraan sanottuna päin helvettiä. Siellä oli mennyt komponentit jotenkin sekaisin ja joku GetIssue palauttaa Person-komponentin sisältöä. Sen lisäksi koodi ei ollut lähellekään ajokelpoista, esimerkiksi importit oli väärin tehty. Eli taas kerran koodigeneraattori bugaa.
 
Liittynyt
07.01.2021
Viestejä
685
Python, tai sen forward declarationin puute, muuten haittaa API:n määrittelyssä Marshmallowin kanssa melkoisesti. Jos esimerkiksi haluan määritellä MagazineScheman niin, että se palauttaa myös listan Issueita, joilla sillä on, teen näin:
Koodi:
class MagazineSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Magazine
        include_fk = True
    issues = ma.List(fields.Nested(IssueSchema))
Issuesta Magazineen linkkaaminen tehdään muuten samoin, mutta ma.List:iä ei tarvita koska kyse ei ole listasta. Tässä se ongelma kuitenkin tulee, kun molemmissa ei voi viitata toiseen.

Mutta toimii muuten kyllä ihan ok, tässä esimerkki JSON:sta mitä yllä oleva model tuottaa:
Koodi:
{
  "description": null,
  "id": 5,
  "issn": null,
  "issues": [
    {
      "count": 1,
      "cover_number": "1 / 1958",
      "id": 51,
      "image_attr": null,
      "image_src": null,
      "link": "",
      "magazine": 5,
      "magazine_id": 5,
      "notes": "Galaxy Science Fiction Magazinen mukaan",
      "number": 1,
      "number_extra": "",
      "pages": 132,
      "size_id": null,
      "title": "",
      "year": 1958
    },
    {
      "count": 2,
      "cover_number": "2 / 1958",
      "id": 52,
      "image_attr": null,
      "image_src": null,
      "link": "",
      "magazine": 5,
      "magazine_id": 5,
      "notes": "Galaxy Science Fiction Magazinen mukaan",
      "number": 2,
      "number_extra": "",
      "pages": 132,
      "size_id": null,
      "title": "",
      "year": 1958
    },
    {
      "count": 3,
      "cover_number": "3 / 1958",
      "id": 53,
      "image_attr": null,
      "image_src": null,
      "link": "",
      "magazine": 5,
      "magazine_id": 5,
      "notes": "Galaxy Science Fiction Magazinen mukaan",
      "number": 3,
      "number_extra": "",
      "pages": 132,
      "size_id": null,
      "title": "",
      "year": 1958
    },
    {
      "count": 4,
      "cover_number": "4 / 1958",
      "id": 54,
      "image_attr": null,
      "image_src": null,
      "link": "",
      "magazine": 5,
      "magazine_id": 5,
      "notes": "Galaxy Science Fiction Magazinen mukaan",
      "number": 4,
      "number_extra": "",
      "pages": 132,
      "size_id": null,
      "title": "",
      "year": 1958
    },
    {
      "count": 5,
      "cover_number": "5 / 1958",
      "id": 55,
      "image_attr": null,
      "image_src": null,
      "link": "",
      "magazine": 5,
      "magazine_id": 5,
      "notes": "Galaxy Science Fiction Magazinen mukaan",
      "number": 5,
      "number_extra": "",
      "pages": 132,
      "size_id": null,
      "title": "",
      "year": 1958
    }
  ],
  "link": null,
  "name": "Aikamme tieteislukemisto",
  "publisher_id": 380,
  "type": 0
}
Implementaatio näyttää näinkin simppeliltä:
Koodi:
    session = new_session()
    magazine = session.query(Magazine)\
                      .filter(Magazine.id == options['magazineId'])\
                      .first()
    schema = MagazineSchema()
    return schema.dump(magazine), 200
 
Liittynyt
07.01.2021
Viestejä
685
Huomannut taas tänään miten rasittavaa fronttikoodaus on. Komponenttien tekijöiden nyt vaan pitää hajottaa taaksepäinyhteensopivuus about vähintään kerran vuodessa koska reasons. Olen tuota React Routeria nyt yrittänyt saada toimimaan (ja kyllä se pikku hiljaa alkaa toimia), mutta asiaa ei kyllä auttanut se, että ovat laittaneet kutosversiossa koko systeemin. Joten joka ikinen ohjesivu on samalla rikki.
 
Liittynyt
07.01.2021
Viestejä
685
Saa kyllä aika näppärää koodia aikaiseksi, se on sanottava:
Koodi:
const client = axios.create({
    baseURL: "http://localhost:5000/api/magazines"
});

function Magazines() {
    const defaultMagazines: IMagazine[] = [];
    const [magazines, setMagazines]: [IMagazine[], (magazines: IMagazine[]) => void] = React.useState(defaultMagazines);
    const [loading, setLoading]: [boolean, (loading: boolean) => void] = React.useState<boolean>(true);
    const [error, setError]: [string, (error: string) => void] = React.useState("");
    React.useEffect(() => {
        async function getMagazines() {
            try {
                const response = await client.get("");
                setMagazines(response.data);
                setLoading(false);
            }
            catch (e) {
                console.error(e);
                //let ex: string = e;
                //setError(ex);
            }
        }
        getMagazines();
    }, []);
    if (!magazines) return null;
    return (
        <main>
            <h1 className="title">Lehdet</h1>
            {magazines.length > 0 ? (
                <div>
                    {
                        magazines.map((magazine) => (
                            <div key={magazine.id}>
                                <Link to={`/api/magazines/${magazine.id}`}>{magazine.name}</Link></div>
                        ))
                    }
                </div>
            ) : (
                <p>Haetaan tietoja...</p>
            )
            }
        </main >
    );
}
Siinä on koko koodi, mikä tarvitaan hakemaan ja näyttämään tietystä API:sta haettu tieto. Virheenkäsittelyä en ole vielä hoksannut, kun TypeScriptille ei tunnu löytyvän järkevää ratkaisua. Merkkijono tuo virhe siis ei ole. Mutta siis yhdistelmä React hookkeja, Axiosta ja async/await:ia saa aikaan melkoisen mukavan rakenteen. Tuohon olisi vielä mahdollista lisätä mahdollisuus käyttäjälle keskeyttääkin tietojen haku. Itse tosin en sitä taida tarvita, kun tietomäärät on sen verran pieniä.
 

kaarlos

Virallinen JimmZ-boikotoija
Premium-jäsen
Liittynyt
13.11.2016
Viestejä
1 533
Melkein suosittelisin käyttämään vaikka react-querya tai swr:aa tuohon datan hakuun ja client-side cachettamiseen. Jossain vaiheessa sulla tulee eteen keissi jossa tarvitset samaa dataa useammassa paikassa ja haluavat välttää sen hakemista uudestaan joka komponentissa erikseen. Tai haluat, että tiettyjen kyselyiden jälkeen päivitetään jotain muuta dataa UI:ssa. Sitten huomaat että jos teet käsin hookin joka hakee datan, tarvitset mekanismin jolla estät queryjen turha haun useampaan otteeseen jos samaa hookkia käytetään monessa paikkaa.

Helppona ratkaisuna on joku tuon kaltainen kirjasto joka ottaa vastuun näiden asioiden hanskaamisesta. Ei tarvitse olla aina jotain komponenteissa itsessään hallittuja tilamuuttujia errorille, latausindikaattoreille tai muulle.

 
  • Tykkää
Reactions: hmb
Liittynyt
07.01.2021
Viestejä
685
Eikös ainakin tuossa muun datan päivityksen tapauksessa ongelma ratkea antamalla hookille se sen "oma" data parametrinä? Silloinhan sitä useEffectsiä ei ajeta jos se ei ole muuttunut.

Täytyy katsoa noita jossain vaiheessa, nyt on vaan niin pirusti noita kirjastoja katsottavana. Pitäisi Formikia tutkia, JWT-asiat pitää saada kuntoon jne. API:a pitää vielä miettiä, varsinkin kirjojen osalta. Niitä kun pitää voida hakea aika monella eri tavalla:
- Kaikki kirjat niin, että haetaan kaikki samalla kirjaimella alkavat kirjoittajien teokset ryhmiteltynä kirjoittajan mukaan, samalla myös painokset: Suomenkieliset SF-, Fantasia- ja Kauhukirjat
- Normihaku: Suomenkieliset SF-, Fantasia- ja Kauhukirjat
- Yhden teoksen tiedot: SuomiSF - King, Stephen: Musta torni
- Kirjoittajan alle lista hänen kirjoistaan: Suomenkieliset SF-, Fantasia- ja Kauhukirjat

Ja mitähän vielä...
 
Liittynyt
07.01.2021
Viestejä
685
Uuh... ehkä se pitää kuitenkin tuo Queries opiskella, koska tällä hetkellä isoin ratkaistava juttu on miten hanskata näyttäminen vs. ylläpito. Varmaankin erilliset komponentit pitää noille tehdä ja valita komponentti sen mukaan ollaanko ylläpitotilassa (johon taas ei voi vaihtaa ellei ole ylläpitäjä).
 
Liittynyt
03.03.2018
Viestejä
1 205
Melkein suosittelisin käyttämään vaikka react-querya tai swr:aa tuohon datan hakuun ja client-side cachettamiseen. Jossain vaiheessa sulla tulee eteen keissi jossa tarvitset samaa dataa useammassa paikassa ja haluavat välttää sen hakemista uudestaan joka komponentissa erikseen. Tai haluat, että tiettyjen kyselyiden jälkeen päivitetään jotain muuta dataa UI:ssa. Sitten huomaat että jos teet käsin hookin joka hakee datan, tarvitset mekanismin jolla estät queryjen turha haun useampaan otteeseen jos samaa hookkia käytetään monessa paikkaa.

Helppona ratkaisuna on joku tuon kaltainen kirjasto joka ottaa vastuun näiden asioiden hanskaamisesta. Ei tarvitse olla aina jotain komponenteissa itsessään hallittuja tilamuuttujia errorille, latausindikaattoreille tai muulle.

Tätä varten reactissa on context, johon voi eri arvoja ja jopa react funktioita laittaa talteen käytettäväksi missä tahansa päin sovellusta.

 

kaarlos

Virallinen JimmZ-boikotoija
Premium-jäsen
Liittynyt
13.11.2016
Viestejä
1 533
Tätä varten reactissa on context, johon voi eri arvoja ja jopa react funktioita laittaa talteen käytettäväksi missä tahansa päin sovellusta.

Tiedän, mutta ei tuo ratkaise maagisesti kaikkia noita ongelmia. Dependent queryt, cachetus hakuparametrien perusteella.. list goes on.

Tietty jos haluaa keskittyä noiden ongelmien ratkomiseen sen sijaan että kirjoittaa toiminnallisuuksia, niin ei siinä mitään. Client-side cachetus on ongelma joka pitää joka projektissa ratkaista tavalla tai toisella, eikä sen keksiminen joka kerta uudestaan ole tehokasta ajankäyttöä. Tämä tosin tulee sellaisesta työelämän näkövinkkeliatä, tokihan omissa projekteissaan voi haluta opetella tuollaisen tekemistä käsin mutta ei se tarkoita että siitä tulisi parempi tai edes yhtä hyvä toteutus automaattisesti.

Hynänä bonuksena tuon kaltaisen kirjaston käytössä tulee kaupanpäällisiksi mahdollisuus siirtää projekti helposti vaikka Next.js päälle jolloin server renderingiä (perus SSR tai ISG) pääsee hyödyntämään ilman että tarvitsee keksiä mitään taikuutta server rendered propsien siirtelyyn.
 
Viimeksi muokattu:
Liittynyt
07.01.2021
Viestejä
685
Hyviä esittely/demoprojekteja vaikka GitHubista saa muuten eri näihin juttuihin vinkata.
 
Liittynyt
03.03.2018
Viestejä
1 205
Tiedän, mutta ei tuo ratkaise maagisesti kaikkia noita ongelmia. Dependent queryt, cachetus hakuparametrien perusteella.. list goes on.

Tietty jos haluaa keskittyä noiden ongelmien ratkomiseen sen sijaan että kirjoittaa toiminnallisuuksia, niin ei siinä mitään. Client-side cachetus on ongelma joka pitää joka projektissa ratkaista tavalla tai toisella, eikä sen keksiminen joka kerta uudestaan ole tehokasta ajankäyttöä. Tämä tosin tulee sellaisesta työelämän näkövinkkeliatä, tokihan omissa projekteissaan voi haluta opetella tuollaisen tekemistä käsin mutta ei se tarkoita että siitä tulisi parempi tai edes yhtä hyvä toteutus automaattisesti.

Hynänä bonuksena tuon kaltaisen kirjaston käytössä tulee kaupanpäällisiksi mahdollisuus siirtää projekti helposti vaikka Next.js päälle jolloin server renderingiä (perus SSR tai ISG) pääsee hyödyntämään ilman että tarvitsee keksiä mitään taikuutta server rendered propsien siirtelyyn.
Kyllä. Halusin vain tuoda tuon esille, että omastakin takaa tuon kaltaisia mekanismeja sieltä löytyy joita voi helposti hyödyntää joidenkin arvojen tallentamiseen (kun mainitsit, että tarvii samaa dataa useammassakin paikassa jne.).
 
Liittynyt
07.01.2021
Viestejä
685
Mihin Reduxia sitten oikein tarvitaan? Eikös senkin pointti ole säilyttää tila?
 
Liittynyt
17.10.2016
Viestejä
14 650
Contextista taitaa vielä puuttua kunnon useSelector, jolla saa vain ne komponentit uudelleenrendattua, joiden tarvitsema datamuuttuu päästoressa. Nyt rendataan kaikki. Redux hanskaa tuon. Eli perffin takia Redux ja vastaavat voivat tulla tarpeen. Ja tähän on tulossa muistaakseni parannus.
 

kaarlos

Virallinen JimmZ-boikotoija
Premium-jäsen
Liittynyt
13.11.2016
Viestejä
1 533
Mihin Reduxia sitten oikein tarvitaan? Eikös senkin pointti ole säilyttää tila?
Tuohonhan Reduxia usein käytetään. Itse en kyllä lähtisi Reduxia ottamaan käyttöön ihan vaan sen takia että tarvitaan jotain tilaa - kuten @bonekuukkeli tuossa ylempänä nostikin, voidaan normaalia tilankäyttöä laajentaa konteksteilla haluttaessa.

Oma näkemykseni aiheesta on se, että näissä single-page applikaatioissa client-side tilan hallintaa kannattaa miettiä kahtena kokonaisuutena: (1) API response store ja (2) puhdas client-side state.

Puhuttaessa (1):sta, tarkoitan tällä tuota kaikissa applikaatioissa tarvittavaa statea joka liittyy siihen kun haet dataa jostain ulkoa ja tallennat sen johonkin jotta voit näyttää sen perusteella dataa näkymissä. Tämän kaltainen data noudattaa aika tarkasti tiettyjä pelisääntöjä; tarvitaan loading booleanit, errorit ja mahdollisuus kytkeä noihin hakuihin erinäisiä sivuvaikutuksia. Tämä on asia johon henkilökohtaisesti käyttäisin jotain näitä moderneja react-queryn kaltaisia kirjastoja (oma suosikki ehkä apollo), koska tämän datan luonteen takia tuohon on voitu kehittää selkeitä patterneja noudattavat kirjastot. Jos lähdet itse Reduxilla rakentamaan vastaavan, niin tullaan ongelmaan jossa et pysy siinä ydintekemisessä eli näkymien rakentamisessa, vaan löydät itsesi koodaamasta ja miettimästä sitä sun client-side cachen toteutusta Reduxin päälle.

Sitten on tämä (2) eli puhdas client-side cache. Tässä mennään sitten alueelle jossa clientti itsessään generoi dataa joko haetun datan tai ihan puhtaan client-side interaktion kautta, ilman että tätä dataa välttämättä lähetetään mihinkään. Tällaisen ongelman ratkaisuun joku Reduxin kaltainen tilanhallinta alkaa olla järkevä, koska se on työkalu jolla mallinnetaan tapahtumien tuottamia muutoksia sovelluksen tilaan.

Oma kokemus reduxin käytöstä rajoittuu redux + redux-saga yhdistelmään jolla saa kyllä ihan elegantteja asioita tehtyä, mutta vuosia tuota tehneenä ja nyt viime aikoina enemmän Apollon kanssa työskennelleenä en näe mitään paluuta aiempaan. En löydä enää itseäni kirjoittamasta jotain reducer boilerplateja, vaan riittää kun kirjoitan GraphQL queryn ja graphql-codegen generoi suoraan komponenteille kelpaavat hookit datan hakuun ja muokkaukseen typescript rajapintoineen. 5/5
 
  • Tykkää
Reactions: hmb
Liittynyt
07.01.2021
Viestejä
685
Perhana kun löysin hyvän esimerkin jossa oli tehty login-himmeli hyvin lähelle niin kuin minulle toimisi mutta vissiin vahingossa lyönyt välilehden kiinni, enkä enää löydä ko. sivua millään.

Perffi tuskin on minun sovelluksessa ongelmana. Mutta tässä on nyt ehdotettu ainakin neljää täysin toisistaan poikkeavaa tapaa tehdä sama juttu. Minä kaipaisin tässä vaiheessa jotain suhteellisen simppeliä systeemiä, tämä sovellus ei ikinä tule palvelemaan miljoonia asiakkaita tai tuuttamaan gigatavuja tavaraa sekunneissa. Eli ellei nyt joku ole halukas kirjoittamaan vaikka login-logiikkaa tuohon sovellukseen valmiiksi asti, niin tarvitsisin sellaista ratkaisua, jonka tajuaminen on tehtävissä kohtuullisella työmäärällä. Tuo useEffects() tuntui siltä, mutta ei sitten vissiin kuitenkaan.

Eli käytännössä tarvitsen seuraavat toiminnallisuudet:
  1. Kirjautuminen. (Ja tietysti myös uuden tunnuksen luominen.)
  2. Datan näyttämisen, joka on sama kirjautumattomalle, kirjautuneelle ja ylläpitäjälle.
  3. Uuden datan luomisen ja olemassaolevan muokkaamisen lomakkeella (deleten pitäisi olla aika triviaali).
Ja no, siinäpä ne. Kuten sanottua, sivustosta ei varamasti ikinä tule mitään niin suosittua, että joku perffi on ongelma. Datan määräkin on maltillinen, pahimmillaan puhutaan jostain megatavuista kuvia (jotka selain joka tapauksessa osaa itse cachettaa), kun ladataan jonkun suositun kirjailijan sivu, jolloin myös kaikki kannet näytetään (yksi kansi on noin 100kt).
 
Liittynyt
07.01.2021
Viestejä
685
Muistutetaan nyt siis vielä, että lukuunottamatta jonkunlaista kokemusta Flaskista, minulla ei ole fronttihommista mitään kokemusta. Olen kirjoittanut ehkä 100 riviä JavaScriptiä yhteensä. Joten minulla ei ole kerrassaan mitään konstia arvioida, mikä ehdotuksista olisi minulle sopiva tai kuinka työläitä ne ovat ilman hirmuista tutustumista jokaiseen vaihtoehtoon. Mihin taas vapaa-ajalla tehtynä menisi varmaan useampi kuukausi. En tarvitse maailman sofistikoituneinta ratkaisua, vaan sellaista, joka on kohtuullisen selkeä & nopeasti tehtävissä, sama malli kun pitää toistaa kaikille järjestelmän tietotyypeille. Jos ihan oikeasti ko malli osoittautuu vääräksi, niin voin vaikka tehdä viisaampana homman myöhemmin uusiksi. Mutta koko sovelluksen kirjoittaminen uusiksi Reactilla on ihan tarpeeksi iso homma ilman tarpeetonta monimutkaisuuttakin.

Herokussa voi kokeilla, miten monimutkainen tuo ylläpitopuoli on: Suomenkieliset SF-, Fantasia- ja Kauhukirjat (admin/admin). Menee vaikka kirjakantaan (Suomenkieliset SF-, Fantasia- ja Kauhukirjat), valitsee jonkun kirjan ja ylläpitovalikosta "Muokkaa". Käyttöliittymän parannus on siis yksi React-harjoituksen tärkeimmistä pointeista (joten siihen ei tarvitse ottaa kantaa :), mutta käyttölogiikaksi tuo käy ihan hyvin. Inline editingiä haaveilin alunperin, mutta on liian työläs hyötyyn nähden. Ja siis noita tietoja saa vapaasti muokata, kanta resetoituu joka yö.

Ylläpidettäviä tietoja on kuitenkin aika monta:
  • Teos,
  • Painos,
  • Henkilö,
  • Novelli,
  • Artikkeli,
  • Lehti,
  • Irtonumero,
  • Kirjasarja,
  • Julkaisijan sarja,
  • Kustantaja,
  • Käyttäjä.
Kaikille ei ole vielä koko CRUD-palettia, enkä tiedä teenkö edes käyttöliittymään deleteä kaikille ikinä.
 
Liittynyt
07.01.2021
Viestejä
685
Koitan saada näiden ohjeiden mukaan autentikointia toimimaan: React Typescript Authentication example with Hooks - BezKoder

Tuossakin ihan tarpeeksi haasteita, varsinkin kun tuossakin näyttäisi olevan virhe (vaikka sivua on päivitetty viimeksi muutama päivä sitten). React Routessa on vissiin muuttunut jokin, kun RouteComponentPropsia ei enää löydy. En vielä keksinyt, että mikä tuota vastaisi.

E. näyttää siis siltä, että tuo sivu käyttää vanhempaa versiota, kun nykyinen on v6 ja v6 rikkoo melkein kaiken. On kyllä vaikea tajuta, miksi taaksepäin yhteensopivuus pitää tarkoituksella hajottaa näin perusteellisesti. Aivan kuin olisi tarkoituksella lähdetty tekemään mahdollisimman epäyhteensopiva versio, jotta varmasti kaikki maailman ohjeet vanhenee kerralla. Tuohon ongelmaan ei edes löydy selitystä helpolla. Ei ole hajuakaan miten seuraava koodinpätkä pitäisi tehdä:

Koodi:
import { RouteComponentProps } from "react-router-dom";

interface RouterProps {
  history: string;
}

type Props = RouteComponentProps<RouterProps>;

const Login: React.FC<Props> = ({ history }) => {
Kun tuota RouteComponentPropsia siis ei ole, enkä löydä mitään muutakaan sopivaa tyyppiä Routerista.
 
Viimeksi muokattu:
Liittynyt
07.01.2021
Viestejä
685
En osaa näköjään googlettaa oikeilla termeillä, mutta jos on sivu, joka listaa linkkejä toisiin sivuihin, esimerkiksi lehtilistaus ja siinä on linkkejä sivuston lehtiin, niin miten kerron lehti-komponentille id:n? Propseilla, vai onko tuohon joku muu menetelmä?

Siis kun listauksessa on tällaista koodia:
Koodi:
                        magazines
                            .sort((a, b) => a.name > b.name ? 1 : -1)
                            .map((magazine) => (
                                <React.Fragment key={magazine.id}>
                                    <Link to={`/magazines/${magazine.id}`}>{magazine.name}</Link><br></br>
                                </React.Fragment>
                            ))
Niin miten se Magazine-objekti saa tuon id:n jotta osaa hakea oikean lehden tiedot? Itse komponenttihan aukeaa Routella:
Koodi:
ReactDOM.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route path="magazines" element={<Magazines />} />
        <Route path="magazines/:magazineId" element={<Magazine />} />
        <Route path="/login" element={<Login />} />
      </Route>
    </Routes>
  </BrowserRouter>,
  rootElement);
Ei nyt millään löydy tosiaan sopivaa sivua, jossa tämä käsiteltäisiin (tai sitten en ymmärrä vastauksia). Propseilla varmaan jotenkin näin:
Koodi:
const Magazine: React.FC<Props> = ({ props }) {
    const id = props.id;
Mutta missä tuo props oikein asetetaan?
 

kaarlos

Virallinen JimmZ-boikotoija
Premium-jäsen
Liittynyt
13.11.2016
Viestejä
1 533
En ole react-routeria hetkeen käyttänyt, mutta Googlauksen perusteella tuohon on oma hookkinsa jolla luet komponentin sisällä URLista nuo parametrit

Koodi:
import {
  useParams
} from "react-router-dom";

// ...


function Child() {
  // We can use the `useParams` hook here to access
  // the dynamic pieces of the URL.
  let { id } = useParams();

  return (
    <div>
      <h3>ID: {id}</h3>
    </div>
  );
}
 
Liittynyt
07.01.2021
Viestejä
685
Aa.. en tajunnut että tuo onnistuu nimenomaan Routerilla, etsiskelin jotain Reactin itsensä ominaisuutta.
 
Liittynyt
07.01.2021
Viestejä
685
Okei, lisää tymiä kysymyksiä: entäs kun haluan upottaa komponentin samalle sivulle? Olen siis määritellyt nyt reitin tällä tavalla:
Koodi:
        <Route path="magazines/:magazineId" element={<Magazine />} >
          <Route path="issues/:issueId" element={<Issue />} />
        </Route>
Ja magazine-sivulla on
Koodi:
                                magazine.issues
                                    .sort((a, b) => a.year > b.year ? 1 : -1)
                                    .map((issue) => (
                                        <React.Fragment key={issue.id}>
                                            <Issue />
                                        </React.Fragment>
                                    ))
Tuossa ei nyt parametrit välity vaan tuloksena on undefined.

Joo, en tiedä onko tämä järkevin tapa hoitaa asia vai pitäisi tuossa välittää issuen tiedot tuolle komponentille. Nyt siis ajattelin tehdä niin, että Issue-komponentti saa id:n ja se hakee tiedot itse.

Idea siis kuitenkin on, että ylläpitäjä voisi suoraan tuosta myös muokata tietoja. Issuella voisi olla oma nappi, josta näkymä vaihtuu ylläpitonäkymäksi (tai jos saisi inline editingin toimimaan siististi, niin ei tarvita edes tuota).
 
Viimeksi muokattu:
Liittynyt
06.11.2016
Viestejä
1 783
Okei, lisää tymiä kysymyksiä: entäs kun haluan upottaa komponentin samalle sivulle? Olen siis määritellyt nyt reitin tällä tavalla:
Koodi:
        <Route path="magazines/:magazineId" element={<Magazine />} >
          <Route path="issues/:issueId" element={<Issue />} />
        </Route>
Ja magazine-sivulla on
Koodi:
                                magazine.issues
                                    .sort((a, b) => a.year > b.year ? 1 : -1)
                                    .map((issue) => (
                                        <React.Fragment key={issue.id}>
                                            <Issue />
                                        </React.Fragment>
                                    ))
Tuossa ei nyt parametrit välity vaan tuloksena on undefined.

Joo, en tiedä onko tämä järkevin tapa hoitaa asia vai pitäisi tuossa välittää issuen tiedot tuolle komponentille. Nyt siis ajattelin tehdä niin, että Issue-komponentti saa id:n ja se hakee tiedot itse.

Idea siis kuitenkin on, että ylläpitäjä voisi suoraan tuosta myös muokata tietoja. Issuella voisi olla oma nappi, josta näkymä vaihtuu ylläpitonäkymäksi (tai jos saisi inline editingin toimimaan siististi, niin ei tarvita edes tuota).
Nythän tuo issue komponentti ei saa tuota id:tä kun lykkäät sen tuolle fragmentille, joka on itseasiassa täysin turha tuossa.
Välitä tieto suoraan sitä käsittelevälle komponentille.

Fragment dokumentaatio
 
Liittynyt
07.01.2021
Viestejä
685
Miten tuo tapahtuu, minä kuvittelin, että nyt sen nimenomaan pitäisi mennä komponentille suoraan?

Poistin tuon fragmentin, mutta eipä se mitään vaikuttanut. Fragmentti oli jäänyt tuohon kun aiemmin printtasin pari tietoa suoraan tuossa. Sitten totesin, että pidemmän päälle ei ole kovin järkevää...

E. Fragmentin poisto saa aikaan sen, että konsolissa valitetaan, että komponentilla ei ole uniikkia key:tä.

Koodi:
Warning: Each child in a list should have a unique "key" prop. 
Check the render method of `Magazine`. 
See https://reactjs.org/link/warning-keys for more information.
 
Viimeksi muokattu:
Liittynyt
06.11.2016
Viestejä
1 783
Miten tuo tapahtuu, minä kuvittelin, että nyt sen nimenomaan pitäisi mennä komponentille suoraan?

Poistin tuon fragmentin, mutta eipä se mitään vaikuttanut. Fragmentti oli jäänyt tuohon kun aiemmin printtasin pari tietoa suoraan tuossa. Sitten totesin, että pidemmän päälle ei ole kovin järkevää...

E. Fragmentin poisto saa aikaan sen, että konsolissa valitetaan, että komponentilla ei ole uniikkia key:tä.

Koodi:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of `Magazine`.
See https://reactjs.org/link/warning-keys for more information.
Syötät tuolle Issue-komponentille uniikin avaimen samaan tyyliin, kuin syötit tuolle fragmentille.
JavaScript:
<Issue data={issue} key={issued.id} />

Issue-komponentissa voit ottaa tuon datan vastaan (joka siis sisältää yksittäisen issuen) seuraavanlaisesti:
JavaScript:
const Issue = ({ data }) => {
    console.log(data)
    ...
};
 
Liittynyt
07.01.2021
Viestejä
685
Jostain syystä tuo ei nyt oikein kelpaa. Minulla on siis
Koodi:
const Issue: React.FC<IIssue> = (data) => {
Mutta virhettä pukkaa:
Koodi:
/home/mep/src/suomisf-ui/src/index.tsx
TypeScript error in /home/mep/src/suomisf-ui/src/index.tsx(23,51):
Type '{}' is missing the following properties from type 'IIssue': id, type, number, number_extra, and 9 more.  TS2740

    21 |         <Route path="magazines" element={<Magazines />} />
    22 |         <Route path="magazines/:magazineId" element={<Magazine />} >
  > 23 |           <Route path="issues/:issueId" element={<Issue />} />
       |                                                   ^
    24 |         </Route>
    25 |         <Route path="/login" element={<Login />} />
    26 |         <Route path="*"
Miksi type on {}?

magazine.issues[]:n pitäisi olla tyyppiä IIssue.
 
Liittynyt
06.11.2016
Viestejä
1 783
Jostain syystä tuo ei nyt oikein kelpaa. Minulla on siis
Koodi:
const Issue: React.FC<IIssue> = (data) => {
Mutta virhettä pukkaa:
Koodi:
/home/mep/src/suomisf-ui/src/index.tsx
TypeScript error in /home/mep/src/suomisf-ui/src/index.tsx(23,51):
Type '{}' is missing the following properties from type 'IIssue': id, type, number, number_extra, and 9 more.  TS2740

    21 |         <Route path="magazines" element={<Magazines />} />
    22 |         <Route path="magazines/:magazineId" element={<Magazine />} >
  > 23 |           <Route path="issues/:issueId" element={<Issue />} />
       |                                                   ^
    24 |         </Route>
    25 |         <Route path="/login" element={<Login />} />
    26 |         <Route path="*"
Miksi type on {}?

magazine.issues[]:n pitäisi olla tyyppiä IIssue.
Johtuukohan tuo siitä, että tuo komponentti ei saa koko Issueta, koska et syötä sitä sille?
En tiedä mitä versiota käytät React routerista, mutta täältä löytyy juttua tuosta: How to Pass Props to a Component Rendered by React Router

edit: pitäs nähdä vähän enemmän koodia, että pystyis sen tarkemmin sanomaan.
 
Viimeksi muokattu:
Liittynyt
07.01.2021
Viestejä
685
Hm, mitähän koodia vielä laittaisin. No, tässä Magazine:
Koodi:
export interface IMagazine {
    id: number,
    name: string,
    //publisher_id: number,
    description: string,
    link: string,
    issn: string,
    type: number,
    uri: string,
    issues: IIssue[]
}
const baseURL = "magazines/";

const Magazine = () => {
    let params = useParams();
    const user = getCurrenUser();
    const [magazine, setMagazine]: [IMagazine | null, (magazine: IMagazine) => void] = React.useState<IMagazine | null>(null);
    const [loading, setLoading]: [boolean, (loading: boolean) => void] = React.useState<boolean>(true);
    const [error, setError]: [string, (error: string) => void] = React.useState("");
    React.useEffect(() => {
        async function getMagazine() {
            let url = baseURL + params.magazineId?.toString();
            console.log("magazine url:" + url);
            try {
                const response = await getApiContent(url, user);
                setMagazine(response.data);
                setLoading(false);
            }
            catch (e) {
                console.error(e);
            }
        }
        getMagazine();
    }, [])
    if (!magazine) return null;
    return (
        <main>
            <h1 className="title">{magazine.name}</h1>
            {magazine !== undefined ? (
                <div>
                    {magazine.issues.length > 0 ? (
                        <div>
                            {
                                magazine.issues
                                    .sort((a, b) => a.year > b.year ? 1 : -1)
                                    .map((issue) => (
                                        <React.Fragment key={issue.id}>
                                            <Issue data={issue} />
                                        </React.Fragment>
                                    ))
                            }
                        </div>
                    ) : (
                        <p></p>
                    )}
                </div>
            ) : (
                <p>Haetaan tietoja...</p>
            )
            }
        </main >
    )
}
Ja tässä Issue:
Koodi:
const baseURL = 'issues/';
export interface IIssue {
    id: number,
    type: number,
    number: number,
    number_extra: string,
    count: number,
    year: number,
    cover_number: string,
    publisher_id: number,
    image_src: string,
    pages: number,
    link: string,
    notes: string,
    title: string
}
const Issue: React.FC<IIssue> = (data) => {
    let params = useParams();
    const user = getCurrenUser();
    const [issue, setIssue]: [IIssue | null, (issue: IIssue) => void] = React.useState<IIssue | null>(null);
    const [loading, setLoading]: [boolean, (loading: boolean) => void] = React.useState<boolean>(true);
    const [error, setError]: [string, (error: string) => void] = React.useState("");
    React.useEffect(() => {
        async function getIssue() {
            let url = baseURL + data.id.toString();
            console.log("issue url: " + url);
            try {
                const response = await getApiContent(url, user);
                setIssue(response.data);
                setLoading(false);
            } catch (e) {
                console.error(e);
            }
        }
        getIssue();
    }, [])
    return (
        <div>
            {issue != null ? (
                <Link to={`/issues/${issue.id}`}
                    key={issue.id}
                >{issue.cover_number}<br></br></Link>
            ) : (
                <p>Haetaan tietoja...</p>
            )}
        </div>
    )
}
 
Liittynyt
06.11.2016
Viestejä
1 783
const Issue: React.FC<IIssue> = (data) => {
...

Väittäisin, että tuo kuuluu olla muodossa:
JavaScript:
const Issue: React.FC<IIssue> = ({data}) => {
    
// tai
    
const Issue: React.FC<IIssue> = (props.data) => {
 
Liittynyt
17.10.2016
Viestejä
14 650
Okei, lisää tymiä kysymyksiä: entäs kun haluan upottaa komponentin samalle sivulle? Olen siis määritellyt nyt reitin tällä tavalla:

Koodi:
<Route path="magazines/:magazineId" element={<Magazine />} >
  <Route path="issues/:issueId" element={<Issue />} />
</Route>
Luitko jo ohjeet täältä:


<Outlet /> -komponentti kertoo, minne se sisempi komponentti rendataan. Ja sitten sun pitää pitää huoli useParams:lla että Issue-komponentti saa sen issueId:n käyttöönsä.

Ja tyypeistä. Et tarvitse React.FC<>:tä lainkaan. Vaan tämä riittää:

Koodi:
type Props = { foo: number };

const Foo = ({ foo }: Props) => <div>{foo}</div>;

export default Foo;
Muutoinkin kannattaa ottaa destructuring käyttöön. Eli otat se haluamasi propsin (foo) heti kättelyssä.
 
Liittynyt
07.01.2021
Viestejä
685
Perskatti, onnistuihan se, mutta piti värkätä huolella. Eli:

Koodi:
type Props = { id: number | null };
export const Issue: React.FC<Props> = ({ id }: Props) => {
...
Ja route piti tehdä näin:
Koodi:
<Route path="issues/:issueId" element={<Issue id={null} />} />
React.FC:lle on tuo <Props> laitettava tai tulee:
Koodi:
Type '({ id }: Props) => JSX.Element' is not assignable to type 'FC<{}>'.
  Types of parameters '__0' and 'props' are incompatible.
    Property 'id' is missing in type '{ children?: ReactNode; }' but required in type 'Props'.ts(2322)
 
Liittynyt
07.01.2021
Viestejä
685
Tarvitsen nullin, koska muuten tuo ei suostu toimimaan. Tulee virhe siinä routessa, koska se odottaa jotain arvoa tuolle parametrille jos sen määrittelee noin.

Koodi:
/home/mep/src/suomisf-ui/src/index.tsx
TypeScript error in /home/mep/src/suomisf-ui/src/index.tsx(23,51):
Property 'id' is missing in type '{}' but required in type 'Props'.  TS2741

    21 |         <Route path="magazines" element={<Magazines />} />
    22 |         <Route path="magazines/:magazineId" element={<Magazine />} >
  > 23 |           <Route path="issues/:issueId" element={<Issue />} />
       |                                                   ^
    24 |         </Route>
    25 |         <Route path="/login" element={<Login />} />
    26 |         <Route path="*"
Just testasin, eli otin tuon FC:n pois ja nullin tuolta Propsilta + siitä Routesta ja tuo on lopputulos.
 
Toggle Sidebar

Statistiikka

Viestiketjut
239 301
Viestejä
4 191 340
Jäsenet
70 722
Uusin jäsen
StormyTheWulf

Hinta.fi

Ylös Bottom