Handling button state and actions in redux - reactjs

I am new to react and redux.
Overview of app: I have a ToggleButtonGroup with two buttons. When a button is click I want to output a table for the respective button. The tables we filtered based on the button click.
Question: I am not sure how to setup an action, state and reducer in my project for the button functionality. My button is a component. Is it best practice to use actions and reducers for buttons? How would I pass these actions to other components? Any examples or resources is appreciated.
This is my Button.tsx file
import React, { Component, useState } from 'react'
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
import ToggleButton from 'react-bootstrap/ToggleButton';
function ToggleButtonGroupControlled() {
const [value, setValue] = useState([1, 2]);
return (
<ToggleButtonGroup type="checkbox" value={value} onChange={() => setValue(value)}>
<ToggleButton value={1}>PA Probes</ToggleButton>
<ToggleButton value={2}>Convential Probes</ToggleButton>
</ToggleButtonGroup>
);
}
export class Buttons extends Component {
render() {
return (
<div>
<ToggleButtonGroupControlled />
</div>
)
}
}
export default Buttons
This is my types.ts file
export const TOGGLE_PA_PROBES = 'TOGGLE_PA_PROBES';
export const TOGGLE_CONVENTIONAL_PROBES = 'TOGGLE_CONVENTIONAL_PROBES';
This is buttonAction.ts file
import{ TOGGLE_PA_PROBES, TOGGLE_CONVENTIONAL_PROBES } from './types';
export function togglePAProebs(){
}
export function toggleConventionalProbes(){
}
This is my buttonReducers.ts file
import{ TOGGLE_PA_PROBES, TOGGLE_CONVENTIONAL_PROBES } from '../Actions/types';
export function ButtonReducer(state, action){
switch(action.type){
default:
return state;
}
}

I will assume you know the basics of redux and react-redux. In short, you know that an action is dispatched to a store.
In a real world situation, it is as if you change a channel on your TV, (1) you dispatch a CHANGE_CHANNEL action to your remote control, (2) the remote control will receive the action dispatched and send that action with the channel information to your TV (reducer), then (3) your TV (reducer) will talk to your cable provider (store), and return the channel data back if it is available to you.
That flow is important because it can be used in your example as well. To begin with, imagine you have a store with some probes. On toggling your checkbox, you will dispatch an action FILTER, which will filter out the probes that need to be shown from a database list, for example, then call your reducer with the filtered probes to update your store and send that data back to your dispatcher.
When you first load your page, I also imagine you want to show the list of all your probes, so you would also need a FETCH action to be dispatched upon componentDidMount. Having that in mind, we can come up with a types file like this:
types.js
export const TOGGLE_PROBES = "TOGGLE_PROBES";
export const LOAD_PROBES = "LOAD_PROBES";
Now that the types are defined, we can create our actions,
import { TOGGLE_PROBES, LOAD_PROBES } from "./types";
const dbProbes = [
{ title: "pa probe 1", type: 1 },
{ title: "pa probe 2", type: 1 },
{ title: "conditional probe 1", type: 2 },
{ title: "conditional probe 1", type: 2 }
];
function toggleProbes(filtered) {
return {
type: TOGGLE_PROBES,
filtered: filtered
};
}
function loadProbes(probes) {
return {
type: LOAD_PROBES,
probes: probes
};
}
export function fetchProbes() {
return function(dispatch) {
dispatch(loadProbes(dbProbes));
};
}
export function filterProbes(filter) {
return function(dispatch) {
const filtered = dbProbes.filter(probe => filter.includes(probe.type));
dispatch(toggleProbes(filtered));
};
}
Notice that I created a fake list of probes called dbProbes. In a real world situation, you would probably be reaching out to a database to filter your probes.
After having all of the actions set up, you can finally work on the reducer and update your state accordingly,
import { TOGGLE_PROBES, LOAD_PROBES } from "./types";
const initialState = {
probes: []
};
function ButtonReducer(state = initialState, action) {
switch (action.type) {
case TOGGLE_PROBES:
return { ...state, probes: action.filtered };
case LOAD_PROBES:
return { ...state, probes: action.probes };
default:
return state;
}
}
export default ButtonReducer;
In order to be able to dispatch this in your components, you do need to set redux up. In your index.js, you could accomplish that by using both react, react-redux and react-thunk. React-thunk is a middleware which is often used with redux for async calls.
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import "./styles.css";
import Buttons from "./Buttons";
import rootReducer from "./store/reducer";
const store = createStore(rootReducer, applyMiddleware(thunk));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<Buttons />
</Provider>,
rootElement
);
First, in your Button components, maybe you just want to show the initial list first, so to do that you need to use a react-redux high order component called connect, which will, as the name states, connect your store and map your store's state to dispatchers and props.
import React, { Component } from "react";
import ToggleButtonGroupControlled from "./ToggleButtonGroupControlled";
import { connect } from "react-redux";
import { fetchProbes } from "./store/action";
export class Buttons extends Component {
componentDidMount() {
this.props.fetchProbes();
}
render() {
let probes = null;
if (this.props.probes) {
probes = this.props.probes.map(probe => <li>{probe.title}</li>);
}
return (
<div>
<ToggleButtonGroupControlled />
<ul>{probes}</ul>
</div>
);
}
}
function mapStateToProps(state) {
return {
probes: state.probes
};
}
function mapDispatchToProps(dispatch) {
return {
fetchProbes: () => dispatch(fetchProbes())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Buttons);
Finally, your toggle button component can properly dispatch a TOGGLE_FILTER action to your actions.js, which will filter the probes, then send the action to reducer, which will update your state and return the updated state, which at this point is mapped to your component props in your Button.js component.
import React, { useState } from "react";
import { ToggleButtonGroup } from "react-bootstrap";
import { ToggleButton } from "react-bootstrap";
import { connect } from "react-redux";
import { filterProbes } from "./store/action";
function ToggleButtonGroupControlled(props) {
const [value, setValue] = useState([1, 2]);
const toggleChangeHandler = newValue => {
setValue(newValue);
props.filterProbes(newValue);
};
return (
<ToggleButtonGroup
type="checkbox"
value={value}
onChange={e => toggleChangeHandler(e)}
>
<ToggleButton value={1}>PA Probes</ToggleButton>
<ToggleButton value={2}>Convential Probes</ToggleButton>
</ToggleButtonGroup>
);
}
function mapDispatchToProps(dispatch) {
return {
filterProbes: types => dispatch(filterProbes(types))
};
}
export default connect(
null,
mapDispatchToProps
)(ToggleButtonGroupControlled);
The cool thing about redux is that it allows for a global state, so you dispatch and change your list of probes from the ToggleButtonGroupControlled component, but that store (list of probes) it's also available for other components that you decide to connect to your store, in this case, Button.js.
You can check the final code running and ready for your problem here:
To understand more about redux-thunk, go here: https://github.com/reduxjs/redux-thunk
Also, there's a free redux book here that you could maybe take a quick look at: https://leanpub.com/redux-book

Redux might be overkill for this example, it all depends however at what level in the component hierarchy your table lives in relation to your ToggleButtonGroupControlled component.
It would help in your question to see the table component, is it in a separate file / route?
I would start simple and pass the value up out of your ToggleButtonGroupControlled component by creating an onChange prop. Right now your ToggleButtonGroupControlled component doesn't add much more functionality
than the third party ToggleButtonGroup component so it's a little hard to reason about.
Also if you do choose to go the redux route because you need this data in the global state then you can probably forego the local state you have here and select it from your redux store and pass into the component

Related

Redux doesn't work in React Native - class component

this view doesn't work with redux, i need use redux dispatch inside react class component, but i get error
"Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons:\n1. You might have mismatching versions of React and the
renderer (such as React DOM)\n2. You might be breaking the Rules of
Hooks\n3. You might have more than one copy of React in the same
app\nSee https://reactjs.org/link/invalid-hook-call for tips about how
to debug and fix this problem."
inspectionView.jsx
import React from "react";
import {
Text,
View,
} from "react-native";
import { useSelector, useDispatch } from 'react-redux';
import {
incrementByAmount,
selectCount
} from '../redux/slice.js';
export default class InspectionList extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
dispatch = useDispatch();
count = useSelector(selectCount);
render() {
return (
<View >
<Text>inspection view</Text>
<button
onClick={() => dispatch(incrementByAmount("hello world"))}
>
Add Amount
</button>
<Text>{{count}}</Text>
</View>
);
}
}
Slice.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
value: 0,
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
incrementByAmount: (state, action) => {
state.value = action.payload
},
},
})
// Action creators are generated for each case reducer function
export const {incrementByAmount } = counterSlice.actions
export const selectCount = (state) => state.counter.value;
export default counterSlice.reducer
store.js
import { configureStore } from '#reduxjs/toolkit'
import counterReducer from './slice'
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
The error is correct. You can never call any hook inside of a class component, regardless of whether it's a React-Redux hook, a React built-in hook, or any other hook. Only function components can call hooks.
My main advice here would be to write function components instead. There's no good reason to be writing class components today.
See the new React beta docs to learn how to use function components and hooks:
https://beta.reactjs.org
If for some reason you must use class components (and the odds are you don't need to), you can still use the older React-Redux connect API:
https://react-redux.js.org/tutorials/connect

React-Redux - How to redirect the page after storing the data in redux store?

I am trying out a react-redux sample code where I would like to add a course in one form upon clicking 'Add Course', I want to update the store and redirect to a new page with the list of courses.
But for some reason, the redirect happen after calling the redux action creator. It stays in the same page.
Any ideas how to redirect the results to a different page?
import React from "react";
import { connect } from "react-redux";
import * as courseActions from "../../redux/actions/courseActions";
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import history from './history'
class CoursesPage extends React.Component {
state = {
course: {
title: "",
},
};
handleSubmit = (event) => {
event.preventDefault();
this.props.actions.loadCourses.createCourse(this.state.course).then(() => {
alert('Inside Promise')
history.push('/AllCourses'); //This doesn't get executed.
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<h2>Courses</h2>
<h3>Add Course</h3>
<input type="submit" value="Add Course" />
{this.props.courses.map((course) => (
<div key={course.title}>{course.title}</div>
))}
</form>
<hr />
</div>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
courses: state.courses,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
loadCourses: bindActionCreators(courseActions, dispatch),
},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
Action Code:
import * as types from "./actionTypes";
export function createCourse(course) {
return { type: types.CREATE_COURSE, course };
}
Reducer:
import * as types from "../actions/actionTypes";
export default function courseReducer(state = [], action) {
debugger;
switch (action.type) {
case types.CREATE_COURSE:
return [...state, { ...action.course }];
default:
return state;
}
}
history.js
import createHistory from 'history/createHashHistory'
export default createHistory()
You can create a custom middleware to do so:
const hasAddedData = (state) => {
// Your checks
}
const redirectMiddleware = history => storeAPI => next => action => {
console.log('dispatching', action)
const result = next(action);
// If the action is the one you want to trigger the redirect (action.type)
// and the state pass the checks (storeAPI.getState()),
// do the redirect.
//
// This would be like:
if (action.type === types.CREATE_COURSE && hasAddedData(storeAPI.getState())) {
history.push(destinationPath);
}
// You must return the result of next(action) to avoid breaking Redux.
return result;
}
And wherever you create your Redux Store:
// history should already be provided by React Router.
const middlewareEnhancer = applyMiddleware(redirectMiddleware(history))
const store = createStore(yourRootReducer, middlewareEnhancer)
If you need to check the previous state too, just set a const with storeAPI.getState() before running next(action). You can expand your middleware with other redirect checks or scenarios you need for additional actions.
WARNING: I wanted to give you the vanilla code solution, but keep in mind these three things:
This is a task that is probably better and opinionatedly made by a library (check connected-react-router).
Also, instead of making a custom middleware for action specific tasks, you can use a widely accepted middleware library such as redux-saga.
Think about your app workflow. Do you need additional state properties (done flags, selection properties...)? Are all the CREATE_COURSE actions going to redirect or only a fraction of them? Will a specific REDIRECT action make things easier for you? Do you really need an imperative redirect or would it be possible, with the right state structure, a declararive Redirect with React Router component?

Logic in component or mapStateToProps

If MyComponent gets data from the redux store, but organises it in some way first before mapping it, should that organisation be done in the component or mapStateToProps function and why?
const MyComponent = ({ data }) => {
// IN HERE?
return (
<div>
{data.map((d) => (...))}
</div>
);
};
const mapStateToProps = (state) => {
const output = state.data
// OR HERE?
return { data: output };
};
export default connect(mapStateToProps)(MyComponent);
Hello have a nice day.
i think is better have a file with all the logic to conect with redux, so every time i need to connect with redux i create a file that name is ComponentNameContainer.jsx, this file looks like that:
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import Row from '../components/Row';
import {doSomething} from '../redux/somethingActions'
// here the imports of function from your actions
export default withRouter(connect(
(state, ownProps) => {
return {
// props that u need from redux
// example: state.sessionReducer.user
}
},
{
// functions that u need from redux
//example: doSomething
}
)(Row))
i have a folder call containers to store all the container files to keep track of the components that are connected with redux.

How to dispatch an action when a button is clicked?

//action code
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const clearCompleted = () => {
return{
type: CLEAR_COMPLETED
}
}
//reducer code
case CLEAR_COMPLETED:
return state.map(todo => {if (todo.completed)
{return {...todo, show:false}}
else {return todo}})
Problem dispatching action on Todo application in react-redux.
import React from 'react'
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
const ClearButton = ({dispatch}) => {
return(
<button fluid onClick={e => {dispatch(clearCompleted())}}>
Clear Completed
</button>
)
}
export default ClearButton
Trying to change the store by clicking on Clear Completed Button. Clear Completed Button should remove the completed todos from the store and todo list should be updated. I am trying to call 'clearCompleted' action with Clear Completed Button.
The difficulty you're having here is that your component doesn't know anything about the Redux store, and the dispatch function will not be in its props. The most basic way you can make dispatch available would be this:
export default connect()(ClearButton)
This will allow you to use dispatch(clearCompleted()) without messing around further with mapDispatchToProps. You'd have to change its definition so it's not a stateless component though.
However, you should probably ask yourself whether a tiny button really needs connect at all? You could probably just pass the correct function down from the containing component:
// TodoList.js
class TodoList extends Component {
render () {
return (
...
<ClearButton clearCompleted={this.props.clearCompleted} />
)
}
}
const mapStateToProps = state => ({
// ...
})
const mapDispatchToProps = dispatch => ({
clearCompleted: () => dispatch(clearCompleted())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Then the function will be in ClearButton's props without it needing to be connected:
<button onClick={this.props.clearCompleted}>
You can do it by wrapping your component in connect.
connect accepts two arguments as first call, mapStateToProps for mapping your store properties into your component's props and mapDispatchToProps for mapping action creators into your component's props. It's also followed by another call to that function with the Component name of yours written in class syntax.
If you insist in using stateless components with connect, you can use compose utility from redux.
import React from 'react'
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
class ClearButton extends React.Component {
render() {
const {clearCompleted} = this.props;
return(
<button fluid onClick={clearCompleted}>
Clear Completed
</button>
)
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ clearCompleted }, dispatch);
export default connect(null, mapDispatchToProps)(ClearButton);

Integrating Dispatch Actions in Container Component Pattern

So I'm completely confused on how to integrate the Container and Component Pattern. I've been reviewing examples all morning and nothing seems to be clicking. How I have been worked with React previously on my first project was fetch the data within my view components and then pass that data down as props using the #connect which works, but in an "automagically" way to me at this time.
import React;
...
import {action} from 'path/to/action.js';
#connect((store) => {return{ key: store.property}});
export class Component{
componentWillMount(){
this.props.dispatch(action());
}
}
As I'm working more with React I want to learn the more "correct" way of building out with Redux and understand on a deeper level what is happening.
What I have setup is
index.jsx (This renders all of my HOCs)
|
App.jsx (Container)
|
Auth.jsx (Component)
|
Layout.jsx (Component) - Contains app content
--or--
AuthError.jsx (Component) - 401 unauthenticated error page
Authentication is handled through an outside resource so this app will not control anything with Logging in or out. There will be no log in/out states simply receiving an object from an API that identifies the User Role & Authenticated Boolean.
What I would like to happen is when the App loads, it will fetch data from a mock API, JSON Server. From there it will render the Auth component. The Auth component will take in props from App.jsx and either render the Layout.jsx or AuthError.jsx.
Where I'm running into issues is how this should be integrated. I'm going to omit lines of code I don't think absolutely pertain to the question.
store.js
import { applyMiddleware, combineReducers, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import promise from 'redux-promise-middleware';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducer from './reducers';
const middleware = applyMiddleware(promise(), thunk, createLogger());
export default createStore(reducer, composeWithDevTools(middleware));
index.jsx
import React from 'react';
import store from './store.js';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App.jsx';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { authenticateUser } from '../actions/authActions.js';
import Auth from '../components/Auth.jsx';
class App extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false // this needs to be set
};
}
componentWillMount() {
console.log('APP PROPS', this.props);
// this.props.actions.authenticateUser();
authenticateUser(); // this runs but doesn't run the dispatch function
// What I think needs to happen here Dispatch an Action and then setState referring back to how I would previous build with React Redux.
}
render() {
return (
<Auth app_name={ApplicationName} authenticated={this.state.authenticated} {...this.props} />
);
}
}
const mapStateToProps = state => {
console.log('redux store auth state', state);
return {
auth: state.auth
};
};
const mapDispatchToProps = dispatch => {
return { actions: bindActionCreators(authenticateUser, dispatch) };
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Auth.jsx
import React from 'react';
import { Route } from 'react-router-dom';
import AuthError from './AuthError.jsx';
import Layout from './Layout.jsx';
export default function Auth(props) {
console.log('AUTH PROPS', props);
const renderLayout = () => {
if (props.authenticated == true) {
return <Layout app_name={props.app_name} />;
} else {
return <AuthError />;
}
};
return <Route path="/" render={renderLayout} />;
}
authReducer.js
export default function reducer(
state = {
authenticated: null
},
action
) {
switch (action.type) {
case 'AUTH_SUCCESSFUL': {
return {
...state,
authenticated: action.payload.authenticated
};
break;
}
case 'AUTH_REJECTED': {
return {
...state,
authenticated: false
};
}
}
return state;
}
authActions.js
import axios from 'axios';
export function authenticateUser() {
console.log('authenticate user action has been called');
return function(dispatch) {
// nothing runs within this block so it's leading me to believe nothing is being `dispatch`ed
console.log('dispatch', dispatch);
axios
.get('localhost:3004/auth')
.then(response => {
dispatch({ type: 'AUTH_SUCCESSFUL', payload: response.data });
console.log('response', response);
})
.catch(err => {
dispatch({ type: 'AUTH_REJECTED', payload: err });
console.log('error', err);
});
};
}
Right now inside of App.jsx I can console the state of the authReducer and I can call authenticateUser() in my actions. But when I call authenticateUser() the return dispatch function doesn't run. Should I be dispatching the auth action in App.jsx? Or should I be dispatching the auth in Auth.jsx as a prop to then have App.jsx fetch the data? Just a bit lost on breaking this apart and what piece should be doing what work.
I'll do a brief explanation about it to help you to understand those patterns and don't get in confusion anymore (I hope).
So, let's forget reducers for a moment to focus on container, action creator and component pattern.
Component
A lot of people implement components by wrong way when using it with redux application.
A better component approach for redux is, implement it with stateless pattern (see Functional Components). Let's see in practice:
// components/Subscribe.js
import React from 'react'
import PropTypes from 'prop-types'
const Subscribe = ({text, confirmSubscription}) =>
<div>
<p>{text}</p>
<button onClick={confirmSubscription}>Confirm</button>
</div>
Subscribe.propTypes = {
subtitle: PropTypes.string.isRequired
}
Subscribe.defaultProps = {
subtitle: ''
}
export default Subtitle
This allows you to optimize component footprint because they have less features than stateful components (or class components), so you will win some performance and keep focused on component objective.
Container
In other hand, Container is a kind of component with some logical implementation. Container is a pattern created to bind React and Redux, because both should't interact directly. This means, a Container render the component, handle some component events (for example, form onSubmit) and feed components with application state. So, the Container is the best place to interact with Redux. (react-redux)[https://github.com/reactjs/react-redux] and Redux make this task a bit easier. So a simple Container to feed and capture interactions on Subscribe component could be like this:
// containers/SubscribeContainer.js
import React from 'react'
import PropTypes from 'prop-types'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { confirmSubscription } from 'actions/subscription'
import Subscribe from 'components/Subscribe'
const mapStateToProps = state => ({
text: state.subscription.text
})
const mapDispatchToProps = dispatch =>
bindActionCreators({
confirmSubscription
}, dispatch)
const Container = connect(mapStateToProps, mapDispatchToProps)
export default Container(Subscribe)
Action Creator
An action creator (or action creators), is just a collection of or a function where return an action. Simple like that:
// actions/subscription
export const CONFIRM_SUBSCRIPTION = 'actions.confirmSubscription'
export function confirmSubscription() {
return {
type: CONFIRM_SUBSCRIPTION
}
}
For now, we have the triad pattern, Component, Container and Action Creator implemented, from here, you just need two more things to make this working with Redux.
Create a subscription store.
Handle CONFIRM_SUBSCRIPTION (in case to update app's state)
Return a new state
The magic will happen when you return a new state from any reducer, the mapStateToProps will be called and you will receive the new state as argument and from there, React will update your components when necessary, in case of those components are stateless, PureComponent (works only with single level states and props) or custom shouldComponentUpdate.
Another thing to keep on mind is to not do fetch or async execution inside Components, Containers and Action Creators, instead, you can use middleware like redux-thunk to compose a custom middeware to capture actions and handle that before be sent to reducers.
your authenticateUser returns a function, you need to literally run the function. The right way to do that is to add a property in your mapDispatchToProps
const mapDispatchToProps = dispatch => {
return { authenticateUser: () => dispatch(authenticateUser()) };
};
Then, in your componentWillMount function, call
this.props.authenticateUer()
Check this

Resources