React - Redux App : Handle Events in Stateless Components - javascript

I asked same question with different form before but haven't reached proper solution yet. I want to avoid refreshing page when I delete data and define it with stateless component. There is my sample code:
BookListElement.js
import React from 'react';
import { connect } from 'react-redux';
import BookList from '../components/bookList';
import { deleteBook } from '../store/actions/projectActions';
const BookListElement = ({books, deleteBook}) => {
if(!books.length) {
return (
<div>
No Books
</div>
)
}
return (
<div>
{Array.isArray(books) ? books.map(book => {
return (
<BookList book={book} deleteBook={deleteBook} key={book._id} />
);
}): <h1>something wrong.</h1>}
</div>
);
};
const mapStateToProps = state => {
return {
books: state.books
};
};
const mapDispatchToProps = dispatch => {
return {
deleteBook: _id => {
dispatch(deleteBook(_id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookListElement);
bookList.js
import React from 'react';
const styles = {
borderBottom: '2px solid #eee',
background: '#fafafa',
margin: '.75rem auto',
padding: '.6rem 1rem',
maxWidth: '500px',
borderRadius: '7px'
};
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
const handleClick = (e) => {
e.preventDefault();
deleteBook(_id);
}
return (
<form>
<div className="collection-item" style={styles} key={_id}>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button className="btn waves-effect waves-light" onClick={handleClick}>
<i className="large material-icons">delete_forever</i>
</button>
</div>
</form>
);
};
export default BookList;
action.js
export const deleteBookSuccess = _id => {
return {
type: DELETE_BOOK,
payload: {
_id
}
}
};
export const deleteBook = _id => {
return (dispatch) => {
return axios.delete(`${apiUrl}/${_id}`)
.then(response => {
dispatch(deleteBookSuccess(response.data))
})
.catch(error => {
throw(error);
});
};
};
reducer.js
const projectReducer = (state = [], action) => {
case DELETE_BOOK:
let afterDelete = state.filter(book => {
return book._id !== action.payload._id
});
return afterDelete;
}
As you can see, I defined handleClick into onClick over bookList.js and page still needs to refresh after deleting data. How should I overcome that issue properly as a junior ?

I think the problem is you haven't set a type for <button>. For most browsers, if no type is specified it's set to <button type='submit'>.
Set it to <button type='button'> inside your BookList to avoid submitting the form.

The issue is that the native <form> submit event is still happening causing the page to refresh on button click. Try the following to prevent the native form by targeting the onSubmit event on the <form> element. This which would allow you to preventDefault() on the submit event.
// ...
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
const handleSubmit = (e) => {
e.preventDefault();
deleteBook(_id);
}
return (
<form onSubmit={handleSubmit}>
<div className="collection-item" style={styles} key={_id}>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button type="submit" className="btn waves-effect waves-light">
<i className="large material-icons">delete_forever</i>
</button>
</div>
</form>
);
};
export default BookList;
Note: with the onSubmit on the <form> element, you need to remove onClick from the button. With <button type="submit", clicking the button will cause a form submit event to occur which would be handled in handleSubmit.
Update:
Given you are using React Router, you need to make sure you are using withRouter for Redux Integration. Try wrappping your BookListElement with the withRouter higher order component. This is assuming you are using react-router-dom:
import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import BookList from '../components/bookList';
import { deleteBook } from '../store/actions/projectActions';
// ...
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(BookListElement));
Hopefully that helps!

Your React code works as expected: https://glitch.com/edit/#!/so-52794886
If the Book List doesn't update after deleting one of the book, it might be that the _id returned from the server is of type string and _ids in the redux store are of type number, so filter predicate never matches and the item is never deleted from the list.

Related

Reducer/Context Api

So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example

How to properly pass useReducer actions down to children without causing unnecessary renders

I can't quite figure out the optimal way to use useReducer hook for data management. My primary goal is to reduce (heh) the boilerplate to minimum and maintain code readability, while using the optimal approach in terms of performance and preventing unnecessary re-renders.
The setup
I have created a simplified example of my app, basically it's a <List /> component - a list of items with possibility to select them, and a <Controls /> component which can switch item groups and reload the data set.
List.js
import React, { memo } from "react";
const List = ({ items, selected, selectItem, deselectItem }) => {
console.log("<List /> render");
return (
<ul className="List">
{items.map(({ id, name }) => (
<li key={`item-${name.toLowerCase()}`}>
<label>
<input
type="checkbox"
checked={selected.includes(id)}
onChange={(e) =>
e.target.checked ? selectItem(id) : deselectItem(id)
}
/>
{name}
</label>
</li>
))}
</ul>
);
};
export default memo(List);
Controls.js
import React, { memo } from "react";
import { ItemGroups } from "./constants";
const Controls = ({ group, setGroup, fetchItems }) => {
console.log("<Controls /> render");
return (
<div className="Controls">
<label>
Select group
<select value={group} onChange={(e) => setGroup(e.target.value)}>
<option value={ItemGroups.PEOPLE}>{ItemGroups.PEOPLE}</option>
<option value={ItemGroups.TREES}>{ItemGroups.TREES}</option>
</select>
</label>
<button onClick={() => fetchItems(group)}>Reload data</button>
</div>
);
};
export default memo(Controls);
App.js
import React, { useEffect, useReducer } from "react";
import Controls from "./Controls";
import List from "./List";
import Loader from "./Loader";
import { ItemGroups } from "./constants";
import {
FETCH_START,
FETCH_SUCCESS,
SET_GROUP,
SELECT_ITEM,
DESELECT_ITEM
} from "./constants";
import fetchItemsFromAPI from "./api";
import "./styles.css";
const itemsReducer = (state, action) => {
const { type, payload } = action;
console.log(`reducer action "${type}" dispatched`);
switch (type) {
case FETCH_START:
return {
...state,
isLoading: true
};
case FETCH_SUCCESS:
return {
...state,
items: payload.items,
isLoading: false
};
case SET_GROUP:
return {
...state,
selected: state.selected.length ? [] : state.selected,
group: payload.group
};
case SELECT_ITEM:
return {
...state,
selected: [...state.selected, payload.id]
};
case DESELECT_ITEM:
return {
...state,
selected: state.selected.filter((id) => id !== payload.id)
};
default:
throw new Error("Unknown action type in items reducer");
}
};
export default function App() {
const [state, dispatch] = useReducer(itemsReducer, {
items: [],
selected: [],
group: ItemGroups.PEOPLE,
isLoading: false
});
const { items, group, selected, isLoading } = state;
const fetchItems = (group) => {
dispatch({ type: FETCH_START });
fetchItemsFromAPI(group).then((items) =>
dispatch({
type: FETCH_SUCCESS,
payload: { items }
})
);
};
const setGroup = (group) => {
dispatch({
type: SET_GROUP,
payload: { group }
});
};
const selectItem = (id) => {
dispatch({
type: SELECT_ITEM,
payload: { id }
});
};
const deselectItem = (id) => {
dispatch({
type: DESELECT_ITEM,
payload: { id }
});
};
useEffect(() => {
console.log("use effect on group change");
fetchItems(group);
}, [group]);
console.log("<App /> render");
return (
<div className="App">
<Controls {...{ group, fetchItems, setGroup }} />
{isLoading ? (
<Loader />
) : (
<List {...{ items, selected, selectItem, deselectItem }} />
)}
</div>
);
}
Here's the complete sandbox.
The state is managed in a reducer, because I need different parts of state to work and change together. For example, reset selected items on group change (because it makes no sense to keep selections between different data sets), set loaded items and clear loading state on data fetch success, etc. The example is intentionally simple, but in reality there're many dependencies between different parts of state (filtering, pagination, etc.), which makes reducer a perfect tool to manage it - in my opinion.
I've created helper functions to perform different actions (for ex., to reload items or to select/deselect). I could just pass down the dispatch to children and create action objects there, but this turns everything into a mess really quickly, esp. when multiple components must perform same actions.
Problem 1
Passing down reducer action functions to child components causes them to re-render on any reducer update.
Case 1: When I select an item in <List />, the <Controls /> is
re-rendered.
Case 2: When I reload the data on Reload button click, the <Controls /> is
re-rendered.
In both cases, the <Controls /> only actually depends on group prop to render, so when it stays the same - the component should not re-render.
I've investigated it and this happens because on each <App /> re-render these action functions are re-created and treated as new prop values for child components, so for React it's simple: new props => new render.
Not ideal solution to this is to wrap all action functions in useCallback, with dispatch as a dependency, but this looks like a hack to me.
const setGroup = useCallback(
(group) => {
dispatch({
type: SET_GROUP,
payload: { group }
});
},
[dispatch]
);
In a simple example it does not look too bad, but when you have dozens of possible actions, all wrapped in useCallback, with deps arrays - that does not seem right.
And it requires to add even more deps to useEffect (which is another problem).
Here's a "fixed" version with useCallback.
Problem 2
I cannot fully extract reducer action functions outside the <App /> component, because in the end they must be used inside a React component with the dispatch (because it's a hook).
I can of course extract them to a separate module and pass dispatch as a first argument:
in actions.js
// ...
export const fetchItems = (dispatch, group) => {
dispatch({ type: FETCH_START });
fetchItemsFromAPI(group).then((items) =>
dispatch({
type: FETCH_SUCCESS,
payload: { items }
})
);
};
// ...
and then in child components do this:
import { fetchItems } from './actions';
const Child = ({ dispatch, group }) => {
fetchItems(dispatch, group);
// ...
};
and reduce my <App /> to this:
// ...
const App = () => {
const [{ items, group, selected, isLoading }, dispatch] = useReducer(
itemsReducer,
itemReducerDefaults
);
useEffect(() => {
fetchItems(dispatch, group);
}, [group, dispatch]);
return (
<div className="App">
<Controls {...{ group, dispatch }} />
{isLoading ? <Loader /> : <List {...{ items, selected, dispatch }} />}
</div>
);
};
but then I have to pass around the dispatch (minor issue) and always have it in arguments list. On the other hand, it fixes the Problem 1 as well, as dispatch does not change between renders.
Here's a sandbox with actions and reducer extracted.
But is it optimal, or maybe I should use some entirely different approach?
So, how do you guys use it? The React docs and guides are nice and clean with counter increments and ToDo lists, but how do you actually use it in real world apps?
React-redux works by also wrapping all the actions with a call to dispatch; this is abstracted away when using the connect HOC, but still required when using the useDispatch hook. Async actions typically have a function signature (...args) => dispatch => {} where the action creator instead returns a function that accepts the dispatch function provided by redux, but redux requires middleware to handle these. Since you are not actually using Redux you'd need to handle this yourself, likely using a combination of both patterns to achieve similar usage.
I suggest the following changes:
De-couple and isolate your action creators, they should be functions that return action objects (or asynchronous action functions).
Create a custom dispatch function that handles asynchronous actions.
Correctly log when a component renders (i.e. during the commit phase in an useEffect hook and not during any render phase in the component body. See this lifecycle diagram.
Pass the custom dispatch function to children, import actions in children... dispatch actions in children. How to avoid passing callbacks down.
Only conditionally render the Loader component. When you render one or the other of Loader and List the other is unmounted.
Actions (actions.js)
import {
FETCH_START,
FETCH_SUCCESS,
SET_GROUP,
SELECT_ITEM,
DESELECT_ITEM
} from "./constants";
import fetchItemsFromAPI from "./api";
export const setGroup = (group) => ({
type: SET_GROUP,
payload: { group }
});
export const selectItem = (id) => ({
type: SELECT_ITEM,
payload: { id }
});
export const deselectItem = (id) => ({
type: DESELECT_ITEM,
payload: { id }
});
export const fetchItems = (group) => (dispatch) => {
dispatch({ type: FETCH_START });
fetchItemsFromAPI(group).then((items) =>
dispatch({
type: FETCH_SUCCESS,
payload: { items }
})
);
};
useAsyncReducer.js
const asyncDispatch = (dispatch) => (action) =>
action instanceof Function ? action(dispatch) : dispatch(action);
export default (reducer, initialArg, init) => {
const [state, syncDispatch] = React.useReducer(reducer, initialArg, init);
const dispatch = React.useMemo(() => asyncDispatch(syncDispatch), []);
return [state, dispatch];
};
Why doesn't useMemo need a dependency on useReducer dispatch function?
useReducer
Note
React guarantees that dispatch function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
We want to also provide a stable dispatch function reference.
App.js
import React, { useEffect } from "react";
import useReducer from "./useAsyncReducer";
import Controls from "./Controls";
import List from "./List";
import Loader from "./Loader";
import { ItemGroups } from "./constants";
import {
FETCH_START,
FETCH_SUCCESS,
SET_GROUP,
SELECT_ITEM,
DESELECT_ITEM
} from "./constants";
import { fetchItems } from "./actions";
export default function App() {
const [state, dispatch] = useReducer(itemsReducer, {
items: [],
selected: [],
group: ItemGroups.PEOPLE,
isLoading: false
});
const { items, group, selected, isLoading } = state;
useEffect(() => {
console.log("use effect on group change");
dispatch(fetchItems(group));
}, [group]);
React.useEffect(() => {
console.log("<App /> render");
});
return (
<div className="App">
<Controls {...{ group, dispatch }} />
{isLoading && <Loader />}
<List {...{ items, selected, dispatch }} />
</div>
);
}
Controls.js
import React, { memo } from "react";
import { ItemGroups } from "./constants";
import { setGroup, fetchItems } from "./actions";
const Controls = ({ dispatch, group }) => {
React.useEffect(() => {
console.log("<Controls /> render");
});
return (
<div className="Controls">
<label>
Select group
<select
value={group}
onChange={(e) => dispatch(setGroup(e.target.value))}
>
<option value={ItemGroups.PEOPLE}>{ItemGroups.PEOPLE}</option>
<option value={ItemGroups.TREES}>{ItemGroups.TREES}</option>
</select>
</label>
<button onClick={() => dispatch(fetchItems(group))}>Reload data</button>
</div>
);
};
List.js
import React, { memo } from "react";
import { deselectItem, selectItem } from "./actions";
const List = ({ dispatch, items, selected }) => {
React.useEffect(() => {
console.log("<List /> render");
});
return (
<ul className="List">
{items.map(({ id, name }) => (
<li key={`item-${name.toLowerCase()}`}>
<label>
<input
type="checkbox"
checked={selected.includes(id)}
onChange={(e) =>
dispatch((e.target.checked ? selectItem : deselectItem)(id))
}
/>
{name}
</label>
</li>
))}
</ul>
);
};
Loader.js
const Loader = () => {
React.useEffect(() => {
console.log("<Loader /> render");
});
return <div>Loading data...</div>;
};

Why won't it work this way to display what the user has clicked to appear in the modal?

I want whatever the user clicks to appear in the modal. In this case, I want a random name to appear in the modal via displayPerson() fat arrow function upon the user clicking <Button/>.
In between <Modal/>, I'm trying to display it, but it's not working. It's just a blank screen.
The modal has no issues in terms of it being toggled and/or closing with the use of redux. The only issue I'm facing is what should appear inside the modal only when the user clicks the button.
How come it won't work the way I'm doing it?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Modal from 'react-modal';
import Aux from '../../../../hoc/Aux';
import Button from '../Buttons/Button';
import CheckoutButton from '../../../../components/UI/buttons/CheckoutButton/CheckoutButton';
import { CLOSE_MODAL, OPEN_MODAL } from "../../../../store/action/NoNameAction";
class Main extends Component {
state = {
isClicked: false
}
componentWillMount() {
Modal.setAppElement('body');
}
displayPerson = () => {
this.setState({isClicked: true});
if(this.state.isClicked) {
return(
<p>a random name</p>
);
}
}
render() {
return (
<Aux>
<Button clicked={() => this.props.thisButtonChosen() && this.displayPerson()} label={"This button"}/>
<CheckoutButton clicked={() => this.props.openModalRedux()}/>
<Modal isOpen={this.props.isOpen}>
<p>{this.displayPerson}</p>
<button onClick={() => this.props.closeModalRedux()}>Close</button>
</Modal>
</Aux>
);
}
}
const mapStateToProps = state => {
return {
isOpen: state.global.isModalOpen
}
};
const mapDispatchToProps = dispatch => {
return {
thisButtonChosen: () => dispatch({type: THIS_BUTTON_CHOSEN}),
// Modal handlers
openModalRedux: () => dispatch({type: OPEN_MODAL}),
closeModalRedux: () => dispatch({type: CLOSE_MODAL})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
this.displayPerson is returning a value depending on the next state update, but setState is asynchronous.
You can use the callback in order to get the new state:
displayPerson = () => {
this.setState({isClicked: true}, () => {
if(this.state.isClicked) {
return(
<p>a random name</p>
);
}
});
}
but even with this approach you could have some issues.
I recommend storing the random name in the state as well.
displayPerson = () => {
const randomUserName = "some random user name...";
this.setState({isClicked: true, randomName: randomUserName});
}
And just use it in render:
<p>{this.state.randomUserName}</p>
Of course you'll need to trigger displayPerson

Pass Dispatch to onClick Event Redux

I'm digging into my first react/redux application and I've been having quite a bit of trouble mapping my dispatch actions to onClick events in my components.
I've tried a couple of variations of trying to bind the onClick Event to the dispatch, but I always end up with either :
ReferenceError: onMovieClick is not defined
or alternatively when I do end up binding a function correctly I'll get an error related to dispatch is not defined.
My Goal
I'm trying to implement a filter(delete) from store function
actions/movieActions.js
import * as actionTypes from './actionTypes'
export const createMovie = (movie) => {
return {
type: actionTypes.CREATE_MOVIE,
movie
}
};
export const deleteMovie = (id) => {
console.log('action triggered. movie index:' + id)
return {
type: actionTypes.DELETE_MOVIE,
id
}
}
reducers/movieReducers.js
export default (state = [], action) => {
switch (action.type){
case 'CREATE_MOVIE':
return [
...state,
Object.assign({}, action.movie)
];
case 'DELETE_MOVIE':
return [
state.filter(({ id }) => id !== action.id)
]
default:
return state;
}
};
components/MovieList.js
import React from 'react'
import Slider from 'react-slick'
import { dispatch, connect } from 'react-redux'
import {Icon} from 'react-fa'
import { deleteMovie } from '../../actions/movieActions'
import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import './MovieList.scss'
class MovieList extends React.Component{
constructor(props){
super (props)
}
handleClick(id) {
dispatch(deleteMovie(id))
}
onMovieClick(id){
dispatch.deleteMovie(id)
}
render () {
// Settings for slick-carousel
let settings = {
infinite: true,
speed: 500
}
return (
<div className='col-lg-12'>
{this.props.movies.map((b, i) =>
<div key={i} className="col-lg-2">
<Slider {...settings}>
{b.images.map((b, z) =>
<div className="img-wrapper">
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
onMovieClick(this.props.movies[i].id)
}/>
<img className="img-responsive" key={z} src={b.base64}></img>
</div>
)}
</Slider>
<div className="text-left info">
<h2>{b.title}</h2>
<p>{b.genre}</p>
</div>
</div>
)}
</div>
)
}
}
// map state from store to props
const mapStateToProps = (state) => {
return {
movies: state.movies
}
};
// Map actions to props
const mapDispatchToProps = (dispatch) => {
return {
onMovieClick: (id) => {
dispatch(deleteMovie(id))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList)
Would love some advice if anyone has a moment.
Since you are passing onMovieClick through connect, you can actually invoke it from the MovieList component props. First, I would remove the onMovieClick method definition in your MovieList component and then use this.props.onMovieClick in the onclick handler of Icon like so:
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
this.props.onMovieClick(this.props.movies[i].id)
}/>

Disable a button onClick with Redux/React

Im pretty new to redux & react; I want to disable a button onClick to prevent multiple copies of data being saved. My button is in the footer component:
import React from 'react';
const Footer = React.createClass({
render () {
const { save, saveAs, onLeave, edit, disableButtons } = this.props;
return (
<footer className="footer">
<button onClick={ save.bind(this, onLeave) } className="btn btn-secondary">{__( 'Save' )}</button>
{ edit && <div onClick={ saveAs.bind(this, onLeave) } className="btn btn-primary save-as">{__( 'Save As' )}</div> }
</footer>
)
}
});
export default Footer;
In react redux I have the actions set up as:
export const SaveButton = bool => ({ type: ActionTypes.SAVE_BUTTON, bool})
export const SaveAsButton = bool => ({ type: ActionTypes.SAVE_AS_BUTTON, bool})
Reducer:
const disableButtons = ( state=false, action ) => {
switch ( action.type ) {
case ActionTypes.SAVE_AUDIENCE:
return action.bool
case ActionTypes.SAVE_AS_AUDIENCE:
return action.bool
}
}
The MapDispatchToProps is done in the connect function in a higher level container:
export default connect(({ onLeave, edit, disableButtons }) => Object.assign({}, { onLeave, edit, disableButtons }), allActions)(Module);
How can I dispatch the saveButton/saveAsButton action to toggle whether the button is disabled in the footer component? Is this the wrong way to go about this? Thank you!

Categories