What is Redux?
Redux is a state management framework for use in JavaScript-based front-end web apps.
What is Redux used for?
Redux helps to centralize state access and state updating logic, which can help keep the structure of complex applications manageable.
If you would like a quick refresher on Redux, or you’re new to using Redux, be sure to check out the first part of this tutorial: Using the Redux API.
What is React?
React is a JavaScript-based view library. In React, you describe what HTML you want to render, based on the given state. When you update your state, the view automatically changes.
React apps are made up of components, which are self-contained pieces of an app that can be reused multiple times, and can contain other components.
For a more in-depth look at React, check out: How To Use an API with ReactJS.
What is the difference between Redux and React Redux?
Redux by itself is library-agnostic, which means you can use it from any library, including React or Vue.js, or even plain JavaScript.
React Redux is the official API for using Redux from within React apps. It provides convenient functions for working with React.
Should I use Redux with React?
When using React with Redux, it’s generally best to use the official React Redux API, because it does a lot of work to make sure that Redux and React work together correctly.
How do you connect Redux with React?
There are two ways to use Redux API with React:
- The connect function API: This is the older React Redux API, that uses Higher-Order Components pattern. With the advent of React’s Hooks API, there’s less reason to use the
connect()
function API. - The Hooks API: This is the newer and easier API for using React Redux. It uses React Hooks for dispatching new actions to Redux, and for accessing our application state within the Redux store.
We’re going to focus on the React Redux Hooks-based API. For a good overview on React Hooks API, take a look at How to Fetch Data from an API with React Hooks and the Introducing React Hooks tutorial.
How does Redux React work?
Redux React is a very natural blending of the Redux API with the React Hooks API.
You store your state in a Redux store, and you access or update that Redux store from within your React components, very much the same as you would with React useState hooks or React Context.
How to use Redux React
There’s only 4 short steps to using Redux:
- Create a Redux store
- Mount it with a Redux Provider
- Access it within lower React Components
- Dispatch actions from React event handlers
Step 1: Create a Redux store
This step is the same as when using plain Redux:
- Create a Redux store using the official
createStore
function. - Pass a reducer function as the first argument.
- Optionally, pass an initial state object as the second argument.
Step 2: Mount it with a Redux Provider
Near the top of your component tree, use the Provider component.
If this reminds you of React Contexts, it’s not a coincidence: React Redux uses React Context API internally, to integrate seamlessly with React.
If you’re not familiar with React Contexts, make sure to skim through React Context API: Using React Context with APIs effectively.
Step 3: Access it within lower React Components
Inside any component lower in the Component tree than your Provider, use the useSelector Hook to access state from your Redux store. This hook takes a function, which takes your whole Redux store, and returns something relevant from within it.
For example, if your Redux store had the type:
interface Store { name: string; skills: string[]; }
Then you could use the following selectors:
const name = useSelector(state => state.name); const skills = useSelector(state => state.skills); const firstSkill = useSelector(state => state.skills[0]);
Step 4: Dispatch actions from React event handlers
In order to change the Redux store’s contents, React Redux still relies on the simple concept of dispatching actions. All that’s new here is the useDispatch hook, which gives you access to the dispatch
function.
For example, a component that needs to update a value from an <input> field, might define this event handler before rendering:
const dispatch = useDispatch(); const updateText = (e) => dispatch({ type: 'update-text', text: e.target.value, });
Note: Because useDispatch is a hook, typical React Hook rules apply, meaning you can’t call this function conditionally or from within a callback. It must be called at the top-level of a component.
Example: Building a Paragraph Analyzer with APIs in React Redux
In this tutorial, we’re going to build a paragraph analyzer:
When the user types in a paragraph and pressed the “Analyze Text” button, our React Redux app will send the paragraph to a text analyzing API, get the mood of each sentence, and give us a list of the key phrases in the paragraph.
Then we’ll render both of these using two different React components. Neutral sentences will be orange, positive sentences will be green, and negative sentences will be red. This gives us a very visual overview of the paragraph and its overall mood.
Step 1: Testing out Microsoft’s Text Analytics API
RapidAPI.com has many great data analytics APIs, and Microsoft’s Text Analytics API stands out as particularly interesting, because it can analyze the sentiment and key phrases of each sentence in a body of text, without needing any sort of AI training beforehand.
Using the Key Phrases endpoint of this API is very straightforward:
- We give it some text to analyze, along with a language.
- It gives us back an array of key phrases within that text.
This API also gives us the ability to send more than one document, but in this tutorial, we’re not going to use that feature. We’ll just pass “1” for the required “id” value and ignore that field in the response.
Step 2: Using this API from JavaScript
Since our React app uses JavaScript, we’re going to be using the JavaScript version of this API. So select the Code Snippets tab, and select the JavaScript option, and choose fetch from its sub-menu:
You should see something like this:
fetch("https://microsoft-text-analytics1.p.rapidapi.com/keyPhrases", { "method": "POST", "headers": { "x-rapidapi-host": "microsoft-text-analytics1.p.rapidapi.com", "x-rapidapi-key": YOUR_RAPID_API_KEY_GOES_HERE, "content-type": "application/json", "accept": "application/json" }, "body": JSON.stringify({ "documents": [ { "id": "1", "language": "en", "text": "Hello world. This is some input text that I love." }, { "id": "2", "language": "fr", "text": "Bonjour tout le monde" }, { "id": "3", "language": "es", "text": "La carretera estaba atascada. Había mucho tráfico el día de ayer." } ] }) }) .then(response => { console.log(response); }) .catch(err => { console.log(err); });
Feel free to try this out in your browser’s developer tools (press F12 to open them), and experiment with different requests and see what kind of responses you get.
Notice that the Sentiments API Endpoint and Key Phrases API Endpoint are both very similar. The only differences are the final path in the URL. Even the body
parameter has the same format. We can use this to simplify our code later.
Important: Make sure your RapidAPI key is present in the Headers. If it’s not, the API won’t work. You can get a RapidAPI key by signing up for a free account on RapidAPI.com, and clicking Subscribe to Test on this API’s page.
Step 3: Setting up a React Redux app
- If you don’t already have NPM installed, you can get it at npmjs.com/get-npm.
- You can also use an OS-specific installation, e.g. Homebrew for Mac.
- Open up your editor or IDE.
- If you don’t have a favorite, I recommend VS Code. It has great built-in integration with NPM, JavaScript, and libraries like React and Redux.
- Set up a new app using create-react-app:
npm init react-app react-redux-rapidapi --use-npm
- If you prefer Yarn, you can skip the “–use-npm” flag.
cd react-redux-rapidapi
- Add Redux:
npm install redux
- Note: this is a different dependency than React-Redux.
- React-Redux does not install Redux for us, so we install it here.
- Add React Redux:
npm install react-redux
- This provides the actual binding between Redux and React
- Now run the app:
npm start
- The app will automatically open in your browser.
Step 4: Adding our custom React components
For now, we’ll look at the code as a whole. We’ll go through it step by step in just a bit. But it’s nice to have something to get an overview of, and to play around with in the browser, before going into depth.
So let’s overwrite the contents of src/App.js
with the following:
import React from 'react'; import { Provider, useDispatch, useSelector } from "react-redux"; import { createStore } from "redux"; import './App2.css'; const INITIAL_STATE = { text: '', }; const rootReducer = (state, action) => { switch (action.type) { case 'analyzed': return { ...state, phrases: action.phrases, sentiments: action.sentiments, analyzedText: action.analyzedText, }; case 'update-text': return { ...state, text: action.text, }; default: return state; } }; const store = createStore( rootReducer, INITIAL_STATE, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); function App() { return ( <Provider store={store}> <div className='app'> <UserInput /> <div className='results'> <VisualParagraph /> <KeyPhraseList /> </div> </div> </Provider> ); } function UserInput() { const text = useSelector(state => state.text); const dispatch = useDispatch(); const updateText = (e) => dispatch({ type: 'update-text', text: e.target.value, }); const analyzeText = async () => { const phrasesResult = await textAnalyticsRequest('keyPhrases', text); const sentimentsResult = await textAnalyticsRequest('sentiment', text); dispatch({ type: 'analyzed', phrases: phrasesResult.keyPhrases, sentiments: sentimentsResult.sentences, analyzedText: text, }); }; return ( <section> <h2>Paragraph to Analyze</h2> <textarea autoFocus onChange={updateText}></textarea> <div><button onClick={analyzeText}>Analyze Text</button></div> </section> ); } function VisualParagraph() { const sentiments = useSelector(state => state.sentiments); const analyzedText = useSelector(state => state.analyzedText); const COLORS = { positive: 'green', neutral: 'orange', negative: 'red', }; return ( <section> <h2>Textual Mood</h2> <p> {sentiments && sentiments.map(({ sentiment, offset, length }) => { const subtext = analyzedText.substr(offset, length); const color = COLORS[sentiment]; return <span key={offset} style={{ color }}>{subtext} </span>; })} </p> </section> ); } function KeyPhraseList() { const phrases = useSelector(state => state.phrases); return ( <section> <h2>Key Phrases</h2> {phrases && <ul> {phrases.map(phrase => ( <li key={phrase}>{phrase}</li> ))} </ul> } </section> ); } async function textAnalyticsRequest(endpoint, text) { const url = "https://microsoft-text-analytics1.p.rapidapi.com/" + endpoint; const response = await fetch(url, { "method": "POST", "headers": { "x-rapidapi-host": "microsoft-text-analytics1.p.rapidapi.com", "x-rapidapi-key": YOUR_RAPID_API_KEY_GOES_HERE, "content-type": "application/json", "accept": "application/json" }, "body": JSON.stringify({ "documents": [{ "id": "1", "language": "en", "text": text, }], }), }); const body = await response.json(); return body.documents[0]; } export default App;
Check the page in your browser, and you should see this:
Step 5: Styling our React app
Thanks to modern CSS such as CSS Grids, we can make our app look decent very quickly with little effort.
Overwrite the contents of src/App.css
with the following:
body { background-color: #f7f7f7; } .app { display: grid; gap: 1em; width: 40em; margin: 2em; } .app section { display: grid; gap: 1em; background-color: #fff; padding: 1em; box-shadow: 0px 2px 6px 2px #0002; } .app section > * { margin: 0; } .app button { background: #17f; color: #fff; font: inherit; border: none; border-radius: 3px; outline: none; padding: 0.5em 1.5em; } .app button:active { background-color: #05b; } .app textarea { border: 1px solid #999; border-radius: 3px; font: inherit; padding: 0.5em; outline: none; height: 10ex; } .app textarea:focus { outline: auto #17f; } .app .results { display: grid; grid-auto-flow: column; } .app .results > section { align-content: baseline; }
Now you should see this:
Step 6: Creating the Reducer function and Redux Store
Near the top, we create an INITIAL_STATE
, the rootReducer
, and our store
variable. This is exactly the same step we would take if we were using plain Redux.
const INITIAL_STATE = { text: '', }; const rootReducer = (state, action) => { switch (action.type) { case 'analyzed': return { ...state, phrases: action.phrases, sentiments: action.sentiments, analyzedText: action.analyzedText, }; case 'update-text': return { ...state, text: action.text, }; default: return state; } }; const store = createStore( rootReducer, INITIAL_STATE, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
Here our reducer function handles two action types:
- analyzed: We use this to update the state after getting a response back from the Text Analytics API, filling it with the details of the result, as well as the text that was analyzed so we can style it below the user input.
- update-text: When the user updates the <textarea>, we could store that in a simple
React.useState
hook. But to demonstrate that all state can be stored within the Redux store, we have an action to store text.
We also store the initial state as a constant variable. This is because our initial state never changes. Keeping it as a separate variable right above our root reducer helps to make clear the relationship between the two. The only thing we put into this initial state is the initial text of our user input, because the whole app can work well without any results.
Step 7: Using the Provider
Right after this, we use the Provider
component that the react-redux
NPM library gives us, passing it our store
:
function App() { return ( <Provider store={store}> <div className='app'> <UserInput /> <div className='results'> <VisualParagraph /> <KeyPhraseList /> </div> </div> </Provider> ); }
Important: Any component that accesses the Redux store must be sub-components of the Provider. This is why we put our Provider at the top of our component tree.
Step 8: Accessing the Redux Store with useSelector
Let’s skip down to the VisualParagraph
element to see how it uses the useSelector hook to grab data from the Redux store’s current state:
function VisualParagraph() { const sentiments = useSelector(state => state.sentiments); const analyzedText = useSelector(state => state.analyzedText); const COLORS = { positive: 'green', neutral: 'orange', negative: 'red', }; return ( <section> <h2>Textual Mood</h2> <p> {sentiments && sentiments.map(({ sentiment, offset, length }) => { const subtext = analyzedText.substr(offset, length); const color = COLORS[sentiment]; return <span key={offset} style={{ color }}>{subtext} </span>; })} </p> </section> ); }
Notice that we have two selectors, which simply reach right into the Redux store to grab the keys we will be adding after we analyze text. But before that, they return undefined, since they’re not present. The useSelector
hook can handle that.
Note: Be careful not to run any code that might throw exceptions inside the function you give to useSelector
! It might break React Redux until a page reload. This should also be a pure function that has no side-effects.
Once we receive the data from useSelector
, we store them into variables. Now we can use it exactly the same as we would if we got it using the React.useState
hook.
Step 9: Dispatching to our Reducer with useDispatch
Finally, we have to actually update the store at some point. We grab a dispatch
function from Redux using the useDispatch
hook:
function UserInput() { const text = useSelector(state => state.text); const dispatch = useDispatch(); const updateText = (e) => dispatch({ type: 'update-text', text: e.target.value, }); const analyzeText = async () => { const phrasesResult = await textAnalyticsRequest('keyPhrases', text); const sentimentsResult = await textAnalyticsRequest('sentiment', text); dispatch({ type: 'analyzed', phrases: phrasesResult.keyPhrases, sentiments: sentimentsResult.sentences, analyzedText: text, }); }; return ( <section> <h2>Paragraph to Analyze</h2> <textarea autoFocus onChange={updateText}></textarea> <div><button onClick={analyzeText}>Analyze Text</button></div> </section> ); }
This actually creates two functions that internally use our dispatch
function:
- updateText: We use this when the <textarea> changes, to update text. We also have a selector that gives us the current text, which we give to our <textarea> as the “value” prop. This means our textarea is a Controlled Component.
- analyzeText: This async function, which we hook up to our button, is where we actually send data to the Text Analytics API, get our response back, and dispatch the result to our Redux reducer function.
Conclusion
Using React Redux API has a reputation of being complex or difficult, but it’s actually not much different than using the React Hooks API with the React Context API. The advantage of Redux is that it centralizes all our actions and state into one location. For large front-end React apps, this can prove very helpful for reducing and managing code complexity.
FAQ
Why is Redux so popular in React projects?
In the early days of React, there was no easy way to manage state that was shared between different components. Redux emerged as one of the leading ways to handle this without adding too much complexity. Since then, React has added Hooks API and Context API, and Redux has kept up with these changes and incorporated them into its own API. It's still a popular choice for managing complex application state, allowing it to be accessed and updated from many app components at various points in the component tree.
Why do you need Redux middlewhere?
Redux middleware lets you wrap the dispatch function to accomplish other goals that aren't provided by Redux. For example, you can use middleware to compose action creators that work well with async functions, which can be especially useful for interacting with third party APIs.
Should all state be in Redux?
Redux is great for state that needs to be shared and updated by many components in a complex React app's component tree. Many components can manage their own local state. The Hooks API is still good for that. But for application state, Redux is a great choice, when used wisely.
Leave a Reply