Sådan bruges Redux i ReactJS med virkelige eksempler

Siden jeg begyndte at arbejde med ReactJS på Creative-Tim, har jeg kun brugt det til at oprette enkle reageringsapps eller skabeloner, hvis du vil. Jeg har kun brugt ReactJS med create-react-app og har aldrig forsøgt at integrere det med noget mere.

Mange af vores brugere har spurgt mig eller mit team, om de skabeloner, jeg oprettede, havde Redux på dem. Eller hvis de blev skabt på en sådan måde, at de kunne bruges sammen med Redux. Og mit svar var altid noget i retning af: “Jeg har ikke arbejdet med Redux endnu, og jeg ved ikke, hvilket svar jeg skal give dig”.

Så her er jeg nu og skriver en artikel om Redux, og hvordan den skal bruges i React. Senere, i denne artikel, vil jeg tilføje Redux oven på et af de projekter, som jeg har arbejdet i løbet af det sidste og nogle år.

Godt at vide, inden vi kæmper med disse to biblioteker:

  • Jeg skal bruge [email protected] (installeret globalt)
  • Jeg bruger [email protected]
  • Min Node.js-version på tidspunktet for skrivning af dette indlæg var 10.13.0 (LTS)
  • Hvis du vil bruge Webpack i stedet, kan du læse min Webpack-artikel og kombinere det, jeg viser dig der, med det, jeg vil vise dig her.

Oprettelse af et nyt ReactJS-baseret projekt og tilføjelse af Redux til det

Første ting først lad os oprette en ny reageringsapp, cd ind i den og starte den.

create-react-app react-redux-tutorial cd react-redux-tutorial npm start

Som vi kan se giver create-react-app os en meget grundlæggende skabelon med et afsnit, et anker til React-webstedet og det officielle ReactJS-ikon, der roterer.

Jeg har ikke fortalt jer, hvad vi skal bruge Redux til, eller hvad laver vi her. Og det er fordi jeg havde brug for ovenstående gif-billede.

For at gøre denne tutorial-artikel let og let at forstå, vil vi ikke bygge noget meget komplekst. Vi skal bruge Redux til at få ovenstående React-billede til at stoppe eller begynde at rotere.

Så når det er sagt, lad os gå videre og tilføje følgende Redux- pakker:

npm install --save redux react-redux

redux v4.0.1

  • Hvad Redux gør i en meget generel forstand, er at det skaber en global tilstand for hele applikationen, der kan tilgås af enhver af dine komponenter
  • Det er et statsforvaltningsbibliotek
  • Du har kun en tilstand for hele din app og ikke stater for hver af dine komponenter

react-redux v5.1.1

  • Dette bruges, så vi kan få adgang til Reduxs data og ændre dem ved at sende handlinger til Redux - faktisk ikke Redux, men vi kommer derhen
  • Den officielle dokumenttilstand: Den lader dine React-komponenter læse data fra en Redux-butik og sende handlinger til butikken for at opdatere data

BEMÆRK : Hvis du har problemer med ovenstående kommando, kan du prøve at installere pakkerne separat

Når du arbejder med Redux, har du brug for tre hoved ting:

  • handlinger: disse er objekter, der skal have to ejendomme, hvoraf den ene beskriver typen af handling, og én der beskriver, hvad der skal ændres i app tilstand.
  • reduceringsanordninger: disse er funktioner, der implementerer handlingernes opførsel. De ændrer appens tilstand baseret på handlingsbeskrivelsen og beskrivelsen af ​​statusændringen.
  • butik: det samler handlingerne og reduceringerne, holder og ændrer tilstanden for hele appen - der er kun en butik.

Som jeg har sagt ovenfor, skal vi stoppe og starte React-logoet. Dette betyder, at vi har brug for to handlinger som følger:

1 - Linux / Mac-kommandoer

mkdir src/actions touch src/actions/startAction.js touch src/actions/stopAction.js

2 - Windows-kommandoer

mkdir src\actions echo "" > src\actions\startAction.js echo "" > src\actions\stopAction.js

Lad os nu redigere src / actions / startAction.js som følger:

export const startAction = { type: "rotate", payload: true };

Så vi vil sige til vores reducer, at typen af ​​handling handler om rotation ( rotering ) af React-logoet. Og tilstanden for drejning af React-logoet skal ændres til sand - vi ønsker, at logoet skal begynde at rotere.

Lad os nu redigere src / actions / stopAction.js som følger:

export const stopAction = { type: "rotate", payload: false };

Så vi vil sige til vores reducer, at typen af ​​handling handler om rotation ( rotering ) af React-logoet. Og tilstanden for drejning af React-logoet skal ændres til falsk - vi ønsker, at logoet skal stoppe med at rotere.

Lad os også oprette reduktionsgearet til vores app:

1 - Linux / Mac-kommandoer

mkdir src/reducers touch src/reducers/rotateReducer.js

2 - Windows-kommandoer

mkdir src\reducers echo "" > src\reducers\rotateReducer.js

Og tilføj følgende kode inde i den:

export default (state, action) => { switch (action.type) { case "rotate": return { rotating: action.payload }; default: return state; } };

Så reduceringen modtager begge vores handlinger, som begge er af typen roterer, og de ændrer begge den samme tilstand i appen - som er tilstand . Roterende . Baseret på nyttelasten for disse handlinger vil state.rotating ændre sig til true eller false .

Jeg har tilføjet en standardsag , som holder staten uændret, hvis handlingstypen ikke roteres . Standardværdien er der, hvis vi opretter en handling, og vi glemmer at tilføje en sag til den handling. På denne måde sletter vi ikke hele apptilstanden - vi gør simpelthen intet og holder det, vi havde.

Den sidste ting, vi skal gøre, er at oprette vores butik til hele appen. Da der kun er en butik / en tilstand for hele appen, opretter vi ikke en ny mappe til butikken. Hvis du vil, kan du oprette en ny mappe til butikken og tilføje den der, men det er ikke som med handlingerne, for eksempel hvor du kan have flere handlinger, og det ser bedre ud at holde dem inde i en mappe.

Så når det er sagt, vil vi køre denne kommando:

1 - Linux / Mac-kommando

touch src/store.js

2 - Windows-kommando

echo "" > src\store.js

And also add the following code inside it:

import { createStore } from "redux"; import rotateReducer from "reducers/rotateReducer"; function configureStore(state = { rotating: true }) { return createStore(rotateReducer,state); } export default configureStore;

So, we create a function named configureStore in which we send a default state, and we create our store using the created reducer and the default state.

I’m not sure if you’ve seen my imports, they use absolute paths, so you might have some errors due to this. The fix for this is one of the two:

Either

1 — Add a .env file into your app like so:

echo "NODE_PATH=./src" > .env

Or

2 — Install cross-env globally and change the start script from the package.json file like so:

npm install -g cross-env

And inside package.json

"start": "NODE_PATH=./src react-scripts start",

Now that we have set up our store, our actions and our reducer we need to add a new class inside the src/App.css file. This class will pause the rotating animation of the logo.

So we are going to write the following inside src/App.css:

.App-logo-paused { animation-play-state: paused; }

So your App.css file should look something like this:

.App { text-align: center; } .App-logo { animation: App-logo-spin infinite 20s linear; height: 40vmin; } /* new class here */ .App-logo-paused { animation-play-state: paused; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

Now, we only need to modify our src/App.js file so that it listens to our store state. And when clicking on the logo, it calls one of the start or stop actions.

First things first, we need to connect our component to our redux store so we import connect from react-redux.

import { connect } from "react-redux";

After this, we’ll export our App component through the connect method like this:

export default connect()(App);

To change the redux store state we’ll need the actions that we’ve done earlier so let’s import them as well:

import { startAction } from "actions/startAction"; import { stopAction } from "actions/stopAction";

Now we need to retrieve the state from our store and to say that we want the start and stop actions to be used for changing the state.

This will be done using the connect function, which accepts two parameters:

  • mapStateToProps: this is used to retrieve the store state
  • mapDispatchToProps: this is used to retrieve the actions and dispatch them to the store

You can read more about them here: react-redux connect function arguments.

So let’s write inside our App.js (at the end of the file if you may):

const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ startAction: () => dispatch(startAction), stopAction: () => dispatch(stopAction) });

After this, let’s add them inside our connect function like so:

export default connect(mapStateToProps, mapDispatchToProps)(App);

And right now, inside our App component, we can access the store state, the startAction and stopAction through props.

Let’s change the img tag to:

So, what we are saying here is, if the store state of rotating (this.props.rotating) is true, then we want just the App-logoclassName to be set to our img. If that is false, then we also want the App-logo-paused class to be set in the className. This way we pause the animation.

Also, if this.props.rotating is true, then we want to send to our store for the onClick function and change it back to false, and vice-versa.

We are almost done, but we’ve forgot something.

We haven’t yet told our react app that we have a global state, or if you will, that we use redux state management.

For this, we go inside src/index.js, we import a Provider from react-redux, and the newly created store like so:

import { Provider } from "react-redux"; import configureStore from "store";
  • Provider: makes the Redux store available to any nested components that have been wrapped in the connect function

After this, instead of rendering our App component directly, we render it through our Provider using the store that we’ve created like so:

ReactDOM.render(   , document.getElementById('root') );

Here we could have used the configureStore function with some other state, for example configureStore({ rotating: false }).

So, your index.js should look like this:

import React from 'react'; import ReactDOM from 'react-dom'; // new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; // changed the render ReactDOM.render(   , document.getElementById('root') ); // changed the render serviceWorker.unregister();

Let’s go ahead and see if our redux app works:

Using action creators

Optionally, instead of actions, we can use action creators, which are functions that create actions.

This way, we can combine our two actions in just one function and reduce a bit our code.

So, let’s go ahead and create a new file:

1 — Linux / Mac command

touch src/actions/rotateAction.js

2 — Windows command

echo "" > src\actions\rotateAction.js

And add this code:

const rotateAction = (payload) => { return { type: "rotate", payload } } export default rotateAction;

We are going to send an action of type rotate, with a payload that we are going to get in the App component.

Inside the src/App.js component, we need to import our new action creator:

import rotateAction from "actions/rotateAction";

Add the new function to the mapDispatchToProps like so:

rotateAction: will receive a (payload) and will dispatch the rotateAction with the payload

Change the onClick function to:

onClick={() => this.props.rotateAction(!this.props.rotating)}

And finally, add our new action creator to the mapDispatchToProps like this:

rotateAction: (payload) => dispatch(rotateAction(payload))

We can also delete the old imports for the old actions, and delete them from the mapDispatchToProps as well.

This is how you new src/App.js should look like:

import React, { Component } from 'react'; // new lines from here import { connect } from "react-redux"; import rotateAction from "actions/rotateAction"; //// new lines to here import logo from './logo.svg'; import './App.css'; class App extends Component { render() { console.log(this.props); return (  this.props.rotateAction(!this.props.rotating) } />

Edit src/App.js and save to reload.

Learn React ); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ rotateAction: (payload) => dispatch(rotateAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(App);

A real-life example with Paper Dashboard React

As you will see in the above gif image, I am using the right menu to change the colors of the menu on the left. This is achieved by using component states, and by passing that state from a parent component to the two menus and some functions to change that state.

I thought it would be a nice example, to take this product and replace the component states with Redux.

You can get it in these 3 ways:

  1. Download from creative-tim.com
  2. Download from Github
  3. Clone from Github:
git clone //github.com/creativetimofficial/paper-dashboard-react.git

Now that we have this product, let’s cd into it and install again redux and react-redux:

npm install --save redux react-redux

After this, we need to create the actions. Since in the right menu we have 2 colors that set the background of the left menu, and 5 colors that change the color of the links, we need 7 actions, or 2 actions creators — and we are going with this second option since it is a bit less code to write:

1 — Linux / Mac commands

mkdir src/actions touch src/actions/setBgAction.js touch src/actions/setColorAction.js

2 — Windows commands

mkdir src\actions echo "" > src\actions\setBgAction.js echo "" > src\actions\setColorAction.js

After this, let’s create the actions code as follows:

src/actions/setBgAction.js

const setBgAction = (payload) => { return { type: "bgChange", payload } } export default setBgAction;

src/actions/setColorAction.js

const setColorAction = (payload) => { return { type: "colorChange", payload } } export default setColorAction;

Now, as in the first part, we need the reducer:

1 — Linux / Mac commands

mkdir src/reducers touch src/reducers/rootReducer.js

2 — Windows commands

mkdir src\reducers echo "" > src\reducers\rootReducer.js

And the code for the reducer:

export default (state, action) => { switch (action.type) { case "bgChange": return { ...state, bgColor: action.payload }; case "colorChange": return { ...state, activeColor: action.payload }; default: return state; } };

As you can see here, unlike our first example, we want to keep our old state and update its contents.

We also need the store:

1 — Linux / Mac command

touch src/store.js

2 — Windows command

echo "" > src\store.js

The code for it:

import { createStore } from "redux"; import rootReducer from "reducers/rootReducer"; function configureStore(state = { bgColor: "black", activeColor: "info" }) { return createStore(rootReducer,state); } export default configureStore;

Inside the src/index.js we need:

// new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop

And also, change the render function:

ReactDOM.render(    {indexRoutes.map((prop, key) => { return ; })}   , document.getElementById("root") );

So the index.js file should look like this:

import React from "react"; import ReactDOM from "react-dom"; import { createBrowserHistory } from "history"; import { Router, Route, Switch } from "react-router-dom"; // new imports start import { Provider } from "react-redux"; import configureStore from "store"; // new imports stop import "bootstrap/dist/css/bootstrap.css"; import "assets/scss/paper-dashboard.scss"; import "assets/demo/demo.css"; import indexRoutes from "routes/index.jsx"; const hist = createBrowserHistory(); ReactDOM.render(    {indexRoutes.map((prop, key) => { return ; })}   , document.getElementById("root") );

Now we need to make some changes inside src/layouts/Dashboard/Dashboard.jsx. We need to delete the state and the functions that change the state. So go ahead and delete these bits of code:

The constructor (between lines 16 and 22):

constructor(props){ super(props); this.state = { backgroundColor: "black", activeColor: "info", } }

The state functions (between lines 41 and 46):

handleActiveClick = (color) => { this.setState({ activeColor: color }); } handleBgClick = (color) => { this.setState({ backgroundColor: color }); }

The sidebar bgColor and activeColor props (lines 53 and 54):

bgColor={this.state.backgroundColor} activeColor={this.state.activeColor}

All of the FixedPlugin props (between lines 59–62):

bgColor={this.state.backgroundColor} activeColor={this.state.activeColor} handleActiveClick={this.handleActiveClick} handleBgClick={this.handleBgClick}

So, we remain with this code inside the Dashboard layout component:

import React from "react"; // javascript plugin used to create scrollbars on windows import PerfectScrollbar from "perfect-scrollbar"; import { Route, Switch, Redirect } from "react-router-dom"; import Header from "components/Header/Header.jsx"; import Footer from "components/Footer/Footer.jsx"; import Sidebar from "components/Sidebar/Sidebar.jsx"; import FixedPlugin from "components/FixedPlugin/FixedPlugin.jsx"; import dashboardRoutes from "routes/dashboard.jsx"; var ps; class Dashboard extends React.Component { componentDidMount() { if (navigator.platform.indexOf("Win") > -1) { ps = new PerfectScrollbar(this.refs.mainPanel); document.body.classList.toggle("perfect-scrollbar-on"); } } componentWillUnmount() { if (navigator.platform.indexOf("Win") > -1) { ps.destroy(); document.body.classList.toggle("perfect-scrollbar-on"); } } componentDidUpdate(e) { if (e.history.action === "PUSH") { this.refs.mainPanel.scrollTop = 0; document.scrollingElement.scrollTop = 0; } } render() { return ( {dashboardRoutes.map((prop, key) => { if (prop.pro) { return null; } if (prop.redirect) { return ; } return (  ); })} ); } } export default Dashboard;

We need to connect the Sidebar and FixedPlugin components to to the store.

For src/components/Sidebar/Sidebar.jsx:

import { connect } from "react-redux";

And change the export to:

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

For the src/components/FixedPlugin/FixedPlugin.jsx:

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

And the export should now be:

const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

We are going to have these next changes:

  • anywhere you find the word handleBgClick, you’ll need to change it to setBgAction
  • anywhere you find the word handleActiveClick, you’ll need to change it to setColorAction

So, the FixedPlugin component should now look like this:

import React, { Component } from "react"; import { connect } from "react-redux"; import setBgAction from "actions/setBgAction"; import setColorAction from "actions/setColorAction"; import Button from "components/CustomButton/CustomButton.jsx"; class FixedPlugin extends Component { constructor(props) { super(props); this.state = { classes: "dropdown show" }; this.handleClick = this.handleClick.bind(this); } handleClick() { if (this.state.classes === "dropdown") { this.setState({ classes: "dropdown show" }); } else { this.setState({ classes: "dropdown" }); } } render() { return ( 
    
  • SIDEBAR BACKGROUND
  • { this.props.setBgAction("black"); }} /> { this.props.setBgAction("white"); }} />
  • SIDEBAR ACTIVE COLOR
  • { this.props.setColorAction("primary"); }} /> { this.props.setColorAction("info"); }} /> { this.props.setColorAction("success"); }} /> { this.props.setColorAction("warning"); }} /> { this.props.setColorAction("danger"); }} />
  • Download now
  • Documentation
  • Want more components?
  • Get pro version
); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

And we are done, you can start the project and see how everything works fine:

Multiple reducers

As you can have multiple actions, you can have multiple reducers. The only thing is that you need to combine them — we’ll see this a bit further down.

Let’s go ahead and create two new reducers for our app, one for the setBgAction and one for the setColorAction:

1 — Linux / Mac commands

touch src/reducers/bgReducer.js touch src/reducers/colorReducer.js

2 — Windows commands

echo "" > src\reducers\bgReducer.js echo "" > src\reducers\colorReducer.js

After this, let’s create the reducers’ code as follows:

src/reducers/bgReducer.js

export default (state = {}, action) => { switch (action.type) { case "bgChange": return { ...state, bgColor: action.payload }; default: return state; } };

src/reducers/colorReducer.js

export default (state = {} , action) => { switch (action.type) { case "colorChange": return { ...state, activeColor: action.payload }; default: return state; } };

When working with combined reducers, you need to add a default state in each of your reducers that are going to be combined. In my case, I’ve chosen an empty object i.e. state = {};

And now, our rootReducer will combine these two as follows:

src/reducers/rootReducer.js

import { combineReducers } from 'redux'; import bgReducer from 'reducers/bgReducer'; import colorReducer from 'reducers/colorReducer'; export default combineReducers({ activeState: colorReducer, bgState: bgReducer });

So, we say that we want the colorReducer to be referred by the activeState prop of the app state, and the bgReducer to be referred by the bgState prop of the app state.

This means that our state will no longer look like this:

state = { activeColor: "color1", bgColor: "color2" }

It will now look like this:

state = { activeState: { activeColor: "color1" }, bgState: { bgColor: "color2" } }

Since we’ve changed our reducers, now we’ve now combined them together into just one, we need to change our store.js as well:

src/store.js

import { createStore } from "redux"; import rootReducer from "reducers/rootReducer"; // we need to pass the initial state with the new look function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) { return createStore(rootReducer,state); } export default configureStore;

Since we’ve changed the way the state looks, we now need to change the props inside the Sidebar and FixedPlugin components to the new state object:

src/components/Sidebar/Sidebar.jsx:

Change line 36 from

to

src/components/FixedPlugin/FixedPlugin.jsx:

We need to change all the this.props.bgColor to this.props.bgState.bgColor . And all the this.props.activeColor to this.props.activeState.activeColor .

So the new code should look like this:

import React, { Component } from "react"; import Button from "components/CustomButton/CustomButton.jsx"; import { connect } from "react-redux"; import setBgAction from "actions/setBgAction"; import setColorAction from "actions/setColorAction"; class FixedPlugin extends Component { constructor(props) { super(props); this.state = { classes: "dropdown show" }; this.handleClick = this.handleClick.bind(this); } handleClick() { if (this.state.classes === "dropdown") { this.setState({ classes: "dropdown show" }); } else { this.setState({ classes: "dropdown" }); } } render() { return ( 
    
  • SIDEBAR BACKGROUND
  • { this.props.setBgAction("black"); }} /> { this.props.setBgAction("white"); }} />
  • SIDEBAR ACTIVE COLOR
  • { this.props.setColorAction("primary"); }} /> { this.props.setColorAction("info"); }} /> { this.props.setColorAction("success"); }} /> { this.props.setColorAction("warning"); }} /> { this.props.setColorAction("danger"); }} />
  • Download now
  • Documentation
  • Want more components?
  • Get pro version
); } } const mapStateToProps = state => ({ ...state }); const mapDispatchToProps = dispatch => ({ setBgAction: (payload) => dispatch(setBgAction(payload)), setColorAction: (payload) => dispatch(setColorAction(payload)) }); export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

Let’s open the project again with npm start and see how everything works. Ta da!

Thanks for reading!

If you’ve enjoyed reading this tutorial please share it. 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.

Special thanks should also go to Esther Falayi for his tutorial which has given me some much needed understanding on Redux.

Useful links:

  • Get the code for this tutorial from Github
  • Read more about ReactJS on their official website
  • Read more about Redux here
  • Læs mere om React-Redux
  • Tjek vores platform for at se, hvad vi laver, og hvem vi er
  • Få Paper Dashboard React fra www.creative-tim.com eller fra Github
  • Læs mere om Reactstrap, kernen i Paper Dashboard React

Find mig på:

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