A React Design rendszer csatlakoztatása a Firebase és a Redux rendszerhez

Miután majdnem két évig dolgoztam a ReactJS-szel a Creative-Timnél, évek óta, amíg egyszerű front-end ReactJS-projekteket, front-end sablonokat készítettem, elkezdtem többet megismerni a React-ról és néhány oktatóanyagot létrehozni.

Hosszú órákig tartó firebase oktatóanyagok, firebase és reagálási oktatóanyagok megtekintése és olvasása, valamint a firebase hivatalos dokumentumainak elolvasása után készen állok arra, hogy írjak magamnak egy oktatóanyagot.

Mit fogok használni ebben a kis oktató cikkben:

A Reduxot és a Firebase-t fogjuk használni a bejelentkezéshez, regisztrációhoz és néhány dinamikus statisztikai kártya létrehozásához.

Figyelmemet a Firebase-re fogom fordítani, és magyarázatokat adok erről. Ha nem ismeri a Redux-ot, a legjobb, ha megnézi a másik oktatóanyagomat arról, hogy mi is a Redux, és mit csinál. Ezt követően könnyedén visszatérhet ide.

A React Design System használatának megkezdése

Mivel nincs időnk átgondolni saját tervezési rendszerünk létrehozását - ehhez napokra, hetekre vagy akár hónapokra lenne szükség -, vállalunk egyet, amin már dolgoztam.

A projekt megszerzéséhez a következők egyikét teheti meg (az első lehetőséget fogom használni):

  • Klón a Githubból:
git clone //github.com/creativetimofficial/argon-dashboard-react.git
  • Letöltés a Github-ból (a link megnyomásával automatikusan elindul a letöltés)
  • Töltse le a Creative-Tim oldalról (ott fiókkal kell rendelkeznie)

Miután megszerezte a projektet, cd-t bele (az én esetemben az lesz):

cd argon-dashboard-react

Indítsuk el a terméket, és nézzük meg, hogyan néz ki:

npm run install:clean

Redux hozzáadása ehhez az indító sablonhoz

Műveletek, reduktorok és tárolás

Menjünk vissza a terminálba, és fussunk:

npm i -E redux redux-thunk react-redux

Amikor futtattam ezt a parancsot, a gépemen a következők voltak telepítve:

A bemutató elején azt a célt tűztük ki, hogy két dolog történjen: bejelentkezés és regisztráció (auth), valamint hogy hozzá tudjunk adni néhány dinamikus kártyát az adatbázisunkból (egyszerű hozzáadás). Ez azt jelenti, hogy két szűkítőnk lesz, az egyik a hitelesítéshez, a másik pedig a dinamikus kártyákhoz (és szükségünk lesz egy gyökérszűkítőre, amely ezt a kettőt egyesíti). Négy műveletünk is lesz: egy a bejelentkezéshez, egy a regisztrációhoz, egy a kártyák hozzáadásához az adatbázisunkba (ezekre gondolhatsz néhány Todo-ra vonatkozóan), és egy az összes ilyen kártya megszerzéséhez az adatbázisból renderelés az alkalmazásunkban). És egy bolt is.

Tehát futtassuk a következő parancsokat:

1 - Linux / Mac parancsok

mkdir src/actionsmkdir src/reducerstouch src/actions/addStatCardAction.jstouch src/actions/getAllStatCardsAction.jstouch src/actions/loginAction.jstouch src/actions/registerAction.jstouch src/reducers/statCardReducer.jstouch src/reducers/authReducer.jstouch src/reducers/rootReducer.jstouch src/store.js

2 - Windows parancsok

mkdir src\actionsmkdir src\reducersecho "" > src\actions\addStatCardAction.jsecho "" > src\actions\getAllStatCardsAction.jsecho "" > src\actions\loginAction.jsecho "" > src\actions\registerAction.jsecho "" > src\reducers\statCardReducer.jsecho "" > src\reducers\authReducer.jsecho "" > src\reducers\rootReducer.jsecho "" > src\store.js

Műveletek

src / actions / addStatCardAction.js

A statisztikai kártya, amelyet dinamikusan szeretnénk létrehozni, a következők egyike:

Mint láthatjuk, van nevük, statjuk, ikonjuk (változó színű), láblécikonjuk és százalékuk (ez még egyszer változó színű) és láblécszövegük.

Tehát létre kell hoznunk azt a műveletet, amely elfogadja a fentieket, így:

const addStatCardAction = ( statName, statDescription, statIcon, statIconColor, statFooterIcon, statFooterIconState, statFooterPercentage, statFooterText) => async dispatch => { // here we'll make a call to our database (firebase) // to add our new stat card with the above details
 dispatch({ type: "addStatCard", payload: { statName: statName, statDescription: statDescription, statIcon: statIcon, statIconColor: statIconColor, statFooterIcon: statFooterIcon, statFooterIconState: statFooterIconState, statFooterPercentage: statFooterPercentage, statFooterText: statFooterText } });};
export default addStatCardAction;

Mint láthatjuk, együtt fogunk működni aszinkron akciókészítőkkel, mivel hívásokat indítunk egy adatbázisba. A hívás befejezése után el kell küldenünk az üzletünkbe azokat az adatokat, amelyeket az imént adtunk hozzá az adatbázisunkhoz a firebase-ben.

src / actions / getAllStatCardsAction.js

Ehhez nem lesz szükség paraméterekre, mivel csak lekér valamit az adatbázisból. Tehát a kód így fog kinézni:

const getAllStatCardsAction = () => async dispatch => { // here we'll make a call to our database (firebase) // that will retrieve all of our stat cards
 dispatch({ type: "getAllStatCards" , payload: {}});};
export default getAllStatCardsAction;

src / actions / loginAction.js

A bejelentkezéshez lesz e-mailünk és jelszavunk, tehát ez a művelet kódja (a bejelentkezési űrlapunkon is van e-mail és jelszó):

const loginAction = (email, password) => async dispatch => { // at the moment, since we haven't yet connected to the database // we are going to say that each time we try to login // we should not be able to log in (that is why we send false)
 dispatch({ type: "login", payload: false });};
export default loginAction;

src / actions / registerAction.js

const registerAction = (name, email, password) => async dispatch => { // at the moment, since we haven't yet connected to the database // we are going to say that each time we try to register // we should not be able to register (that is why we send false)
 dispatch({ type: "register", payload: false });};
export default registerAction;

Reduktorok

src / reducers / statCardReducer.js

Mivel két műveletünk van a statisztikai kártyával kapcsolatban, két esetünk lesz ebben a szűkítőben:

export default (state = {}, action) => { switch (action.type) { case "addStatCard": console.log("adding ", action.payload); // since we will always fetch our stat cards // from firebase, each time we add one new // we will just return the state return state; case "getAllStatCards": console.log("getting ", action.payload); console.log(action.payload); return { // keep the old state ...state, // add all the cards from the database // they will come in a json format, // so we need to convert them to array statCardState: Object.values(action.payload) }; default: return state; }};

Naplózza azt is, hogy mit adunk hozzá, és mit próbálunk megszerezni a tűzbázisunkból.

src / reduktorok / authReducer.js

export default (state = {}, action) => { switch (action.type) { // in both cases, we want to tell our app, // if the user is logged in or not // if the user registers, he will automatically be logged in
 case "register": console.log("register is ",action.payload); return { // keep old state ...state, // add true/false if the user is or not logged in loggedIn: action.payload }; case "login": console.log("login is ",action.payload); return { // keep old state ...state, // add true/false if the user is or not logged in loggedIn: action.payload }; default: return state; }};

Amikor új felhasználót regisztrálunk, automatikusan bejelentkezünk. Néhány naplót is felvettünk, hogy lássuk, sikeres-e a regisztráció vagy a bejelentkezés.

src / reducers / rootReducer.js

Ez a fenti reduktorok kombinálására szolgál:

import { combineReducers } from "redux";
import authReducer from "reducers/authReducer";import statCardReducer from "reducers/statCardReducer";
export default combineReducers({ // the authReducer will work only with authState authState: authReducer, // the statCardReducer will work only with statCardState statCardState: statCardReducer});

Bolt

src / store.js

Mivel aszinkron cselekvési alkotóink vannak, szükségünk lesz egy köztes szoftverre, amely lehetővé teszi számunkra, hogy ezeket az akciók alkotóit használjuk, ezért a redux-thunk használata:

import { createStore, applyMiddleware } from "redux";import reduxThunk from "redux-thunk";
import rootReducer from "reducers/rootReducer";
function configureStore( state = { authState: {}, statCardState: {} }) { return createStore(rootReducer, state, applyMiddleware(reduxThunk));}
export default configureStore;

Csatlakoztatjuk alkalmazásunkat az üzletünkhöz

At the moment, if we were to start our app, nothing would happen, since all the actions and our store are not being rendered in our app. So this is what we are going to do now.

First, let’s add our store, for this, we need to fo inside src/index.js.

Before the ReactDOM.render() function we need to add the following imports:

import { Provider } from "react-redux";import configureStore from "store";

And after that, we’ll wrap the BrowserRouter from the ReactDOM.render() function inside the Provider tag as follows:

     } />   } />   ,

Our next concern is to make our users to be redirected to the login page if not authenticated and if they are authenticated to be redirected to the user page. Basically, if they are logged in, they will not be able to access the Auth layout (src/layouts/Auth.jsx), and if they are not, they won’t be able to access the Admin layout (src/layouts/Admin.jsx).

Let’s go inside src/layouts/Auth.jsx and after the React import, make the following imports:

import { connect } from "react-redux";import { Redirect } from "react-router-dom";

After that let’s change the export of this component as follows:

const mapStateToProps = state => ({ ...state});
export default connect( mapStateToProps, {})(Auth);

After this, we go inside the render function of this component, and before the return, add the following code:

if (this.props.authState.loggedIn) { return ;}

So, if the user is authenticated, they will be redirected to their profile page.

Next, we go inside src/layouts/Admin.jsx and make the same changes as with the Auth layout. So add the following imports:

import { connect } from "react-redux";import { Redirect } from "react-router-dom";

Change it’s export to:

const mapStateToProps = state => ({ ...state});
export default connect( mapStateToProps, {})(Admin);

Once again, in the render function, before the return we add:

if (!this.props.authState.loggedIn) { return ;}

This time, we say !this.props.authState.loggedIn, since we want the user to be redirected to the login page if they are not authenticated.

Let us start again our project and see how, each time if we try to navigate to the Dashboard or Profile, we are not allowed since we are not logged in.

Now, we need to go inside the Login and Register view-pages and add Redux to them as well.

Connecting our Login page to redux using loginAction

First, let's go inside src/views/examples/Login.jsx and after the React import, add these imports:

import { connect } from "react-redux";
import loginAction from "actions/loginAction";

Then, change the export at the end of the file with this:

const mapStateToProps = state => ({ ...state});
const mapDispatchToProps = dispatch => ({ loginAction: (email, password) => dispatch(loginAction(email, password))});
export default connect( mapStateToProps, mapDispatchToProps)(Login);

Now, before the render function we write:

state = { email: "", password: ""};onChange = (stateName, value) => { this.setState({ [stateName]: value });};

We’ll need to keep a local state for the email and password and send these two to our firebase.

Then, we need to change line 85 from:

To:

 this.onChange("email", e.target.value)}/>

We’ll also change line 99 from:

To:

 this.onChange("password", e.target.value)}/>

We’re almost set for the login. Next we need to change the Sign in button so that, when we press it, it will call the loginAction. So change it from:

 Sign in

To:

 this.props.loginAction( this.state.email, this.state.password ) }> Sign in

Now go back in your browser, and on the Login page, open your console, and try to log in. You should get an output of login is false. So we know that our action and our reducer work.

Connecting our Register page to redux using registerAction

Go inside src/views/examples/Register.jsx and do the same as the above. So first add the imports (this time with the registerAction):

import { connect } from "react-redux";
import registerAction from "actions/registerAction";

Then, the export to:

const mapStateToProps = state => ({ ...state});
const mapDispatchToProps = dispatch => ({ registerAction: (name, email, password) => dispatch(registerAction(name, email, password))});
export default connect( mapStateToProps, mapDispatchToProps)(Register);

Add the following before the render function:

state = { name: "", email: "", password: ""};onChange = (stateName, value) => { this.setState({ [stateName]: value });};

Change:

To:

 this.onChange("name", e.target.value)}/>

Then:

To:

 this.onChange("email", e.target.value)}/>

And lastly, the password as well:

To:

 this.onChange("password", e.target.value)}/>

One more thing — the button, we need to change it from:

 Create account

To:

 this.props.registerAction( this.state.name, this.state.email, this.state.password )}> Create account

So, we are all set with Redux. Again, go to the Register page, type something inside the form, and then press the Create account button with the console opened. You should get a register is false.

Connecting our Header component to redux using addStatCardAction and getAllStatCardsAction actions

Now we need to make our Stat Cards from the Header component (this component can be seen for example inside the Dashboard page) to be rendered from our store/firebase, and also, make them create dynamically — for example on a button click.

Go inside src/components/Headers/Header.jsx and add the following imports (after the React import):

import {connect} from "react-redux";
import addStatCardAction from "actions/addStatCardAction";import getAllStatCardsAction from "actions/getAllStatCardsAction";
import { Button } from "reactstrap";

Change the default export to:

const mapStateToProps = state => ({ ...state});const mapDispatchToProps = dispatch => ({ getAllStatCardsAction: () => dispatch(getAllStatCardsAction()), addStatCardAction: ( statName, statDescription, statIcon, statIconColor, statFooterIcon, statFooterIconState, statFooterPercentage, statFooterText ) => dispatch( addStatCardAction( statName, statDescription, statIcon, statIconColor, statFooterIcon, statFooterIconState, statFooterPercentage, statFooterText ) )});
export default connect( mapStateToProps, mapDispatchToProps)(Header);

Then, let’s add a componentDidMount function right before the render one as follows:

componentDidMount(){ this.props.getAllStatCardsAction();}

And now, after the first div inside the return statement of the render function, we’ll add a Button that will add our stat cards inside our firebase:

    this.props.addStatCardAction( "Performance", "49,65%", "fas fa-percent", "bg-info text-white rounded-circle shadow", "fas fa-arrow-up", "text-success", " 12%", "Since last month" ) } > Add stat card

And, we now need to delete the whole contents of the Row tag (~lines 48–165 — from ow&g t; to ), and replace it with the following:

{// we first verify if the statCardState is undefined this.props.statCardState && // then verify if the statCardState.statCardState is // populated with cards from our firebase this.props.statCardState.statCardState && // and lastly, we render them using the map function this.props.statCardState.statCardState.map((prop, key) => { return ( {prop.statName} {prop.statDescription} 

{" "} {prop.statFooterPercentage} {" "} {prop.statFooterText}

); })}

Adding Firebase

Setting Firebase Account

For this, you need to have a Google Account. If you do not have one, Google offers you a fast (1 minute) Guide.

After you’ve made your account, sign into it, or if you have one, sign into that one.

After that, navigate to this page (this is the homepage of firebase) and press the GO TO CONSOLE button, or just navigate directly to this link.

After that press on the Add project button. You will be prompted with a modal, with an input for a name (you can type whatever name you would like). For me, it will be react-redux-firebase-tutorial. You can leave everything else as is. Accept the terms and then press the Create Project button. You’ll have to wait a bit until it creates the project (around 30 seconds).

After that press the Continue button. That will automatically redirect you to the new project page. In the left menu press the Authentication link. On that press the Set up sign-in method. You will have a table with Provider and Status. Press on the line Email/Password. And check the first Switch and then press the Save button.

Now, go to Database link, scroll down the page and press Create database button, under the Realtime Database. After this, on the modal prompt that opens, choose Start in test mode radio and then press Enable and wait a few seconds.

Next, you’ll need to get your config file (config file that we will add it to our project in the next section). For this press on Project Overview link in the left menu, and after that press on the <;/> (Web) button. Copy the config variable and the firebase initialization. We’ll paste this in a new file, in the next section.

We are done!

We won’t need to create any tables for our users, our users’ details, or our dynamic cards, since firebase will automatically create them — we’ll talk about this in the next section.

Here are the above steps, as images:

Adding Firebase to our project

Let’s install firebase in our app:

npm i -E firebase

After this, we need to create a file for configuring our firebase in our app, so:

1 — Linux/Mac commands

touch src/firebaseConfig.js

2 — Windows commands

echo "" > src\firebaseConfig.js

And let’s import firebase in this file, and then export firebase with the initialization (you need the code from the previous section — see the last image):

import * as firebase from "firebase";
// replace this variable, with your own config variable// from your firebase projectvar config = { apiKey: "YOUR_KEY_HERE", authDomain: "YOUR_DOMAIN_HERE", databaseURL: "YOUR_URL_HERE", projectId: "YOUR_ID_HERE", storageBucket: "YOUR_BUCKET_HERE", messagingSenderId: "YOUR_ID_HERE"};
let firebaseConfig = firebase.initializeApp(config);
export default firebaseConfig;

Now, we can import our firebaseConfig everywhere we need it.

Register

Let us first make our registerAction functional. So, we go inside src/actions/registerAction.js and at the beginning of the file we import our firebase config:

import firebase from "firebaseConfig";

After this, we may need for our users to keep stuff, like their name, their photos etc. so we are going to create a new table called user-details. If it doesn’t exist, add in it the name of our user.

Our form only has email, password, and name — firebase will automatically create a database table in which it will only put the credentials (email and password) of the account. So if we want to keep more details about our users, we’ll need to create a new table — my table will have the ID of the user, from the table with the credentials, and the user’s name.

So after the above import, we say:

// get me the firebase database
const databaseRef = firebase.database().ref();
// get me the table named user-details// if it does not exist, firebase will// automatically create it
const userDetailsRef = databaseRef.child("user-details");

After that, we’ll change our dispatch code from:

dispatch({ type: "register", payload: false });

To:

// firebase offers us this function createUserWithEmailAndPassword// which will automatically create the user for us// it only has two arguments, the email and the password
firebase.auth().createUserWithEmailAndPassword(email, password)
// then() function is used to know when the async call has ended// that way, we can notify our reducers that register was succesful
.then(function(user) {
 // we take the user id and it's name and we add it in our // user-details table
 userDetailsRef.push().set({userId: user.user.uid, userName: name});
 // after that we dispatch to our reducers the fact that // register was succesful by sending true
 dispatch({type:"register", payload: true});
// if the register was not succesful we can catch the erros here
}).catch(function(error) {
 // if we have any erros, we'll throw an allert with that error
 alert(error);
});

So in the end, our registerAction will look like this:

import firebase from "firebaseConfig";
const databaseRef = firebase.database().ref();const userDetailsRef = databaseRef.child("user-details");
const registerAction = (name, email, password) => async dispatch => { firebase .auth() .createUserWithEmailAndPassword(email, password) .then(function(user) { userDetailsRef.push().set( { userId: user.user.uid, userName: name } ); dispatch({ type: "register", payload: true }); }) .catch(function(error) { alert(error); });};
export default registerAction;

Open the app again, and go to the register page. Type a name, a valid email and a password (something simple to remember — something like qwerty). After you press the Create account button you should be redirected to the user-profile page — this means that our registration was successful. We can now go back to our firebase project (//console.firebase.google.com/u/0/ — press on your project), click the Authentication link, and we’ll see that email that we’ve just written. Also, if we go to the Database link, we’ll see our user-details table.

Login

we go inside src/actions/loginAction.js and at the beginning of the file we import our firebase config:

import firebase from "firebaseConfig";

For this action, we won’t need anything else, so the next thing is to change our dispatch code from:

dispatch({ type: "login", payload: false });

To:

// firebase offers us this function signInWithEmailAndPassword// which will automatically create the user for us// it only has two arguments, the email and the password
firebase .auth() .signInWithEmailAndPassword(email, password) // then() function is used to know when the async call has ended // that way, we can notify our reducers that login was succesful .then(function(user) { // if the login was succesful, then // we dispatch to our reducers the fact that // login was succesful by sending true dispatch({type:"login", payload: "true"}); })
// if the login was not succesful we can catch the erros here .catch(function(error) {
// if we have any erros, we'll throw an allert with that error alert(error); });

So in the end, our loginAction should look like this:

import firebase from "firebaseConfig";
const loginAction = (email, password) => async dispatch => { firebase .auth() .signInWithEmailAndPassword(email, password) .then(function(user) { dispatch({ type: "login", payload: "true" }); }) .catch(function(error) { alert(error); });};
export default loginAction;

If we open again our app (we should be redirected by default to Login page), and if we enter our email and password, we will be able to login to our new account.

Add stat cards and render them

Now, we need to make some changes to our actions regarding the stat cards.

Inside src/actions/getAllStatCardsAction.js we need to add the following imports:

import firebase from "firebaseConfig";
const databaseRef = firebase.database().ref();// this is to get the stat-cards table from firebaseconst statCardsRef = databaseRef.child("stat-cards");

Then we need to change the dispatch from:

dispatch({ type: "getAllStatCards", payload: {} });

To:

// this function will get all the entires of the// stat-cards table, in a json formatstatCardsRef.on("value", snapshot => { dispatch({ type: "getAllStatCards", // if the json returns null, i.e. the // stat-cards table is blank - empty // then we'll return an empty object payload: snapshot.val() || {} });});

This is how the action should now look:

import firebase from "firebaseConfig";
const databaseRef = firebase.database().ref();const statCardsRef = databaseRef.child("stat-cards");
const getAllStatCardsAction = () => async dispatch => { statCardsRef.on("value", snapshot => { dispatch({ type: "getAllStatCards", payload: snapshot.val() || {} }); });};
export default getAllStatCardsAction;

Next, is the src/actions/addStatCardAction.js. Like the previous one, we need some imports:

import firebase from "firebaseConfig";
const databaseRef = firebase.database().ref();const statCardsRef = databaseRef.child("stat-cards");

Now, instead of the simple dispatch, we’ll overwrite it from:

dispatch({ type: "addStatCard", payload: { statName: statName, statDescription: statDescription, statIcon: statIcon, statIconColor: statIconColor, statFooterIcon: statFooterIcon, statFooterIconState: statFooterIconState, statFooterPercentage: statFooterPercentage, statFooterText: statFooterText }});

To:

statCardsRef // the push function will send to our firebase the new object .push() // and will set in a new row of the table stat-cards // with the bellow object .set({ statName: statName, statDescription: statDescription, statIcon: statIcon, statIconColor: statIconColor, statFooterIcon: statFooterIcon, statFooterIconState: statFooterIconState, statFooterPercentage: statFooterPercentage, statFooterText: statFooterText }) // when the push has terminated, we will dispatch to our // reducer that we have successfully added a new row .then(() => { dispatch({ type: "addStatCard" }); });

So, it now should look like:

import firebase from "firebaseConfig";
const databaseRef = firebase.database().ref();const statCardsRef = databaseRef.child("stat-cards");
const addStatCardAction = ( statName, statDescription, statIcon, statIconColor, statFooterIcon, statFooterIconState, statFooterPercentage, statFooterText) => async dispatch => { statCardsRef .push() .set({ statName: statName, statDescription: statDescription, statIcon: statIcon, statIconColor: statIconColor, statFooterIcon: statFooterIcon, statFooterIconState: statFooterIconState, statFooterPercentage: statFooterPercentage, statFooterText: statFooterText }) .then(() => { dispatch({ type: "addStatCard" }); });};
export default addStatCardAction;

And we are all set. Run again the app, login into your account, navigate on the Dashboard page, and then press the Add stat card button. Stats should now start adding to your Header.

Thanks for reading!

If you’ve enjoyed reading this tutorial give it a clap. I am very keen on hearing your thoughts about it. Just give this thread a comment and I’ll be more than happy to reply.

Useful links:

  • Get the code for this tutorial from Github
  • Read more about ReactJS on their official website
  • Read more about Redux here
  • Read more about React-Redux
  • Read more about Firebase
  • Check out our platform to see what we are doing and who we are
  • Read more about Reactstrap, the core of Argon Dashboard React
  • Read my Webpack tutorial and/or my Redux tutorial

Find me on:

  • Facebook: //www.facebook.com/NazareEmanuel
  • Instagram: //www.instagram.com/manu.nazare/
  • Linkedin: //www.linkedin.com/in/nazare-emanuel-ioan-4298b5149/
  • Email: [email protected]