Disable a button onClick with Redux/React - javascript

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!

Related

React.js Display a component with onClick event

I' m new to React and I'm building a simple React app that displays all the nations of the world on the screen and a small search bar that shows the data of the searched nation.
Here an image of the site
But I don't know how to show the country you want to click in the scrollbar.
Here the app.js code:
import React, { Component } from 'react';
import './App.css';
import NavBar from '../Components/NavBar';
import SideBar from './SideBar';
import CountryList from '../Components/SideBarComponents/CountryList';
import Scroll from '../Components/SideBarComponents/Scroll';
import Main from './Main';
import SearchCountry from '../Components/MainComponents/SearchCountry';
import SearchedCountry from '../Components/MainComponents/SearchedCountry';
import Datas from '../Components/MainComponents/Datas';
class App extends Component {
constructor() {
super();
this.state = {
nations: [],
searchField: '',
button: false
}
}
onSearchChange = (event) => {
this.setState({searchField: event.target.value});
console.log(this.state.searchField)
}
onClickChange = () => {
this.setState(prevsState => ({
button: true
}))
}
render() {
const {nations, searchField, button, searchMemory} = this.state;
const searchedNation = nations.filter(nation => {
if(button) {
return nation.name.toLowerCase().includes(searchField.toLowerCase())
}
});
return (
<div>
<div>
<NavBar/>
</div>
<Main>
<div className='backgr-img'>
<SearchCountry searchChange={this.onSearchChange} clickChange={this.onClickChange}/>
<SearchedCountry nations={searchedNation}/>
</div>
<Datas nations={searchedNation}/>
</Main>
<SideBar>
<Scroll className='scroll'>
<CountryList nations={nations} clickFunc/>
</Scroll>
</SideBar>
</div>
);
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(x => this.setState({nations: x}));
}
componentDidUpdate() {
this.state.button = false;
}
}
export default App;
The countryList:
import React from 'react';
import Images from './Images';
const CountryList = ({nations, clickFunc}) => {
return (
<div className='container' style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(115px, 3fr))'}}>
{
nations.map((country, i) => {
return (
<Images
key={country.numericCode}
name={country.name}
flag={country.flag}
clickChange={clickFunc}
/>
);
})
}
</div>
)
}
export default CountryList;
And the images.js:
import React from 'react';
import './images.css'
const Images = ({name, capital, region, population, flag, numericCode, clickChange}) => {
return (
<div className='hover bg-navy pa2 ma1 tc w10' onClick={clickChange = () => name}>
<img alt='flag' src={flag} />
<div>
<h6 className='ma0 white'>{name}</h6>
{capital}
{region}
{population}
{numericCode}
</div>
</div>
);
}
export default Images;
I had thought of using the onClick event on the single nation that was going to return the name of the clicked nation. After that I would have entered the name in the searchField and set the button to true in order to run the searchedNation function.
I thank anyone who gives me an answer in advance.
To keep the actual structure, you can try using onClickChange in Images:
onClickChange = (newName = null) => {
if(newName) {
this.setState(prevsState => ({
searchField: newName
}))
}
// old code continues
this.setState(prevsState => ({
button: true
}))
}
then in onClick of Images you call:
onClick={() => {clickChange(name)}}
Or you can try as well use react hooks (but this will require some refactoring) cause you'll need to change a property from a parent component.
With that you can use useState hook to change the value from parent component (from Images to App):
const [searchField, setSearchField] = useState('');
Then you pass setSearchField to images as props and changes the searchField value when Images is clicked:
onClick={() => {
clickChange()
setSearchField(name)
}}

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

React - Redux App : Handle Events in Stateless Components

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.

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)
}/>

Categories