In this series, I want to go over the who, what, and why of Redux as a tool, separate from React, and then go over the popular combination of React and Redux.
Woo!
I'm happy to share two bits of good news with you: my appointment for the vaccine is later this week and we're going to go over making asynchronous API calls.
We're going to continue where we left off last week with our React app.
Since we don't have an actual backend for this app, we're going to use JSONPlaceholder and list 10 users who will act as a list of bakers on our app. The goal of this blog post is to simply use the GET method to populate a list of 10 users.
In case you're not familiar with JSONPlaceholder, it's a great way to test and prototype an API and get your hands on some fake data.
Today, we're going to be using Thunk middleware for Redux with our application and you can find documentation here.
redux-thunk
npm install redux-thunk yarn add redux-thunk
redux-thunk
?With a normal Redux store, we can only make synchronous calls. Therefore, we use redux-thunk
so that we can extend the abilities of our store and perform asynchronous actions like API calls and router transitions.
redux-thunk
also allows us to perform logic within our actions instead of returning only an object and still interact with the state.
After installing redux-thunk
, let's navigate to index.js
and import thunk
.
(Don't forget to import applyMiddleware
too)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import thunk from "redux-thunk"; // <== Importing thunk import rootReducer from './reducers' import { Provider } from "react-redux"; import { createStore, applyMiddleware } from 'redux' //<== we'll need to add applyMiddleware too const store = createStore( rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
Next we're going to need to add this to our store.
You make think we should do something like this in our createStore
function:
const store = createStore( rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(thunk))
I'm sorry to say that would be wrong because you'll receive this error:
Error: It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function.
Recall, our createStore
function can only task two arguments. However, we can use the built-in compose
function that Redux provides because it allows use apply several store enhances. (Read more about it here.)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import thunk from "redux-thunk"; import rootReducer from './reducers' import { Provider } from "react-redux"; import { createStore, applyMiddleware, compose } from 'redux' const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() && compose const store = createStore( rootReducer, composeEnhancer(applyMiddleware(thunk))) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
With this implementation, you should notice that the error isn't there anymore and we can interact with our app again.
As mentioned above, we're going to use JSONPlaceholder and import a list of 10 users (our bakers) via a GET request.
There's a few changes that I'm adding:
baker-actions.js
file in our actions
folderbaker-reducer.js
file in our reducers
folderBakerComponent.js
file in our Components
folderHere's what I have so far:
baker-actions.js
export const GET_BAKERS = 'GET_BAKERS' export const getBakers = () => { return (dispatch) => { fetch("https://jsonplaceholder.typicode.com/users") .then(response => response.json()) .then(data => { console.log(data) }) } }
baker-reducer.js
import { GET_BAKERS } from '../actions/baker-actions' const bakers = { bakers: [] } const bakerReducer = (state = bakers, action) => { switch (action.type) { case GET_BAKERS: return { ...state, bakers: action.payload } default: return state } } export default bakerReducer
BakerComponent.js
import React from 'react'; const BakerComponent = (props) => { return ( <React.Fragment> <ul> <li>props</li> </ul> </React.Fragment> ) } export default BakerComponent;
app.js
import React from 'react' // import logo from './logo.svg'; import './App.css'; import BakerComponent from './Components/BakerComponent' import CakeComponent from './Components/CakeComponent' function App() { return ( <React.Fragment> <div style={{ "margin": "50px", "textAlign": "center"}}> <CakeComponent /> </div> <BakerComponent /> </React.Fragment> ); } export default App;
Now, I've added a few changes to our app.js
file, take a look:
import { React } from "react"; // import logo from './logo.svg'; import "./App.css"; import BakerComponent from "./Compoonents/BakerComponent"; import CakeComponent from "./Compoonents/CakeComponent"; import { connect } from "react-redux"; import { getBakers } from "./actions/baker-actions"; const renderBakers = (props) => { props.getBakers() } function App(props) { renderBakers(props) return ( <> <div style={{ margin: "50px", textAlign: "center" }}> <CakeComponent /> </div> <BakerComponent /> </> ); } const mapStateToProps = (state) => { return state.baker } const mapDispatchToProps = { getBakers, } export default connect(mapStateToProps, mapDispatchToProps)(App, renderBakers);
I added props
as an argument in our functional component along with the mapStateToProps
and mapDispatchToProps
functions.
I created a renderBakers
function that when invoked it will hit our async action getBakers
. Let's take a look at our BakerComponent
file:
import React from 'react'; import { connect } from "react-redux" import { getBakers } from '../actions/baker-actions' const BakerComponent = (props) => { //console.log(props) return ( <React.Fragment> {props.bakers.map(baker => <ul><li>{baker.name}</li></ul>)} </React.Fragment> ) } const mapStateToProps = (state) => { return state.baker } const mapDispatchToProps = { getBakers } export default connect(mapStateToProps, mapDispatchToProps)(BakerComponent);
The line {props.bakers.map(baker => <ul><li>{baker.name}</li></ul>)}
maps through our array of bakers and returns an unordered list of baker names.
I hope you found this blog post series helpful and engaging. For my next subject, I'm torn between a few choices. However, I think a series or one-off post about marketing in web development could be important.