dispatch action to close modal - reactjs

I have reducer which hides and display a modal component;
import {SHOW_MODAL, HIDE_MODAL } from '../constants/ActionTypes'
import React from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions';
const initialState = {
modalType: null,
modalProps: {}
}
export default function modal(state = initialState, action) {
switch (action.type) {
case 'SHOW_MODAL':
return {
modalType: action.modalType,
modalProps: action.modalProps
}
case 'HIDE_MODAL':
return initialState
default:
return state
}
}
Action that shows modal:
export const showAchievement = (modalProps) => ({ type: types.SHOW_ACHIEVEMENT, ...modalProps })
How can I send a function to my modal component that will dispatch an action 'HIDE_MODAL' :
openAchievementModal(){
this.props.showAchievement({
type: 'SHOW_MODAL',
modalType: 'ADD_ACHIEVEMENT',
modalProps: {
dayId: this.props.day.id,
onChange: this.props.addAchievement
}
})
}
I am using react-modal as wrapper for my modals which are mounted at the top of components:
import React, { Component } from 'react';
import Modal from 'react-modal';
import ModalWrapper from './ModalWrapper.js';
import Select from 'react-select';
export default class AddAchievementModal extends Component {
constructor() {
super();
this.logChange = this.logChange.bind(this);
}
logChange(e) {
this.props.onChange(this.props.dayId, e.label)
this.props.onClose()
}
render() {
console.log(this.props)
var options = [
{ value: 1, label: 'Play Music' },
{ value: 2, label: 'Football' }
];
return (
<span >
<ModalWrapper
onRequestClose={this.props.closeModal}
style={this.props.customStyles}
contentLabel="Modal" >
<h2>Add Achievement</h2>
<Select
name="form-field-name"
value="one"
options={options}
onChange={this.logChange}
/>
</ModalWrapper>
</span>
)
}
}
React modal wrapper:
import Modal from 'react-modal'
import React, { Component } from 'react'
const customStyles = {
content : {
top : '50%',
left : '50%',
right : '50%',
bottom : '30%',
marginRight : '-50%',
transform : 'translate(-50%, -50%)',
borderRadius : '10px',
border : '3px solid #ccc'
},
};
class ModalWrapper extends Component {
constructor() {
super();
this.state = {
modalIsOpen: true
};
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<Modal style={customStyles} isOpen={this.state.modalIsOpen} contentLabel="Model Wrapper" closeModal={this.props.closeModal}>
<header>
<button onClick={this.closeModal}>Close</button>
</header>
{this.props.children}
</Modal>
);
}
}
export default ModalWrapper
To close the modal, do I need to modelIsOpen to false, aswell as dispatching an action HIDE_MODAL ?

The actions should be plain objects; you are mixing them with functions.
What you should instead do is to only pass the callback functions which dispatch actions to the modal component.
You would do this in the smart component. See smart-and-dumb-components article.
onOpenAchievementModal() {
this.props.showAchievement({
...
})
}
onAddAchievementModal(achievement) {
this.props.addAchievement({
...
achievement,
})
}
and then render the modal, something like:
<Modal onOpen={this.onOpenAchievementModal} onClickAchievement={this.onAddAchievementModal}/>

Related

React native, delay api call?

I have a method called: onChangeText
It means every time I type, it will search the remote api.
How do I delay the remote api call? i.e. let user types certain things, then connect the api, rather than connect every key stroke.
onChangeText(title) {
console.log('-- chg text --');
console.log(title);
this.props.searchApi(title);
}
The component:
import React, { Component } from 'react';
import { SearchBar, Divider } from 'react-native-elements';
import { View, ScrollView, Text, StyleSheet, Image} from 'react-native';
import { connect } from 'react-redux';
// action creator
import { searchApi } from './reducer';
class SearchContainer extends Component {
constructor(props) {
super(props);
}
onChangeText(title) {
console.log('-- chg text --');
console.log(title);
this.props.searchApi(title);
}
onClearText(e) {
console.log('-- clear text --');
console.log(e);
}
render() {
const { } = this.props;
const containerStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
const searchStyle = {
width: 300,
height: 45
};
return (
<View
style={containerStyle}
>
<Image
source={require('../../asset/img/logo.png')}
style={{
height: 150,
width: 150
}}
/>
<SearchBar
cancelButtonTitle="Cancel"
placeholder='Search'
containerStyle={searchStyle}
onChangeText={this.onChangeText.bind(this)}
onClearText={this.onClearText.bind(this)}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
};
};
const mapDispatchToProps = dispatch => {
return {
searchApi: () => dispatch(searchApi())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchContainer);
Use lodash debounce. It is used for this exact use case
Sample React example. Should be able to port to native the same way
import React, {Component} from 'react'
import { debounce } from 'lodash'
class TableSearch extends Component {
//********************************************/
constructor(props){
super(props)
this.state = {
value: props.value
}
this.changeSearch = debounce(this.props.changeSearch, 250)
}
//********************************************/
handleChange = (e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}
//********************************************/
render() {
return (
<input
onChange = {this.handleChange}
value = {this.props.value}
/>
)
}
//********************************************/
}

Modal state with react/redux

I'm managing Todo lists in my app. The main view is a page with all the lists displayed as cards. If you click on one of them, you can modify, update, delete stuff through a modal that appears.
I have a TodoLists reducer that store all the TodoLists. I don't know how to handle the modal. Should I use redux or just local state?
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { listsActions } from "../duck";
import NewList from "./NewList";
import Card from "./Card";
import Modal from "./Modal";
class Lists extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
list: {}
};
this.hideModal = this.hideModal.bind(this);
this.renderModal = this.renderModal.bind(this);
}
componentDidMount() {
const { fetchByUserId, user } = this.props;
if (user !== undefined) {
fetchByUserId(user.id);
}
}
hideModal() {
this.setState({
modal: false
});
}
renderModal() {
this.setState({
modal: true
});
}
render() {
const { items } = this.props;
const { modal, list } = this.state;
return (
<div>
<NewProject />
<div className="columns">
{_.map(items, (l) => (
<div
key={l.id}
className="column"
>
<Card
list={l}
onClick={() => this.renderModal(l)}
/>
</div>
))}
</div>
<Modal
className={modal ? "is-active" : ""}
list={list}
onClose={this.hideModal}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const { user } = state.authentication;
const { items, loading, error } = state.lists;
return {
user,
items,
loading,
error
};
};
export default connect(
mapStateToProps,
{ fetchByUserId: listsActions.fetchByUserId }
)(Projects);

Connect() is not passing state from store to child

I am "new to react". I am working on a project where I am creating three buttons named as India, China, Russia. On button click, text of paragraph changes.
For this, I have created 4 Presentational Components, 3 actions, 1 reducer and extra reducer for initial state.
I am trying to send text to paragraph, from store to Presentational Component via connect(). However, it's not working.
My code is as following:
index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';
import App from './components/App';
const store = createStore(rootReducer);
console.log(store.getState());
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
actions/index.js
export const india = text => ({
type: 'INDIA',
text
});
export const china = text => ({
type: 'CHINA',
text
});
export const russia = text => ({
type: 'RUSSIA',
text
});
reducers/country.js
const initialState = {
text: 'ABC',
isIClicked: false,
isCClicked: false,
isRClicked: false
};
const country = (state = initialState, action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false
};
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false
};
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true
};
default:
return state;
}
};
export default country;
components/IndiaBtn.js
import React from 'react';
const IndiaBtn = ({ isIClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isIClicked ? 'white' : 'black',
backgroundColor: isIClicked ? 'blue' : 'white'
}}
>
India
</button>
);
};
export default IndiaBtn;
components/ChinaBtn.js
import React from 'react';
const ChinaBtn = ({ isCClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isCClicked ? 'white' : 'black',
backgroundColor: isCClicked ? 'blue' : 'white'
}}
>
China
</button>
);
};
export default ChinaBtn;
components/RussiaBtn.js
import React from 'react';
const RussiaBtn = ({ isRClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isRClicked ? 'white' : 'black',
backgroundColor: isRClicked ? 'blue' : 'white'
}}
>
Russia
</button>
);
};
export default RussiaBtn;
components/display.js
import React from 'react';
const display = ({ text }) => {
return <div style={{ padding: '16px' }}>{text}</div>;
};
export default display;
components/App.js
import React from 'react';
import IndiaBtnContainer from '../containers/IndiaBtnContainer';
import ChinaBtnContainer from '../containers/ChinaBtnContainer';
import RussiaBtnContainer from '../containers/RussiaBtnContainer';
import DisplayContainer from '../containers/DisplayContainer';
const App = () => {
return (
<div>
<div>
<span><IndiaBtnContainer /></span>
<span><ChinaBtnContainer /></span>
<span><RussiaBtnContainer /></span>
</div>
<div>
<DisplayContainer />
</div>
</div>
);
};
export default App;
containers/IndiaBtnContainer.js
import { connect } from 'react-redux';
import IndiaBtn from '../components/IndiaBtn';
import { india } from '../actions';
const mapStateToProps = state => ({
isIClicked: state.isIClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(india('india'))
});
export default connect(mapStateToProps, mapDispatchToProps)(IndiaBtn);
containers/ChinaBtnContainer.js
import { connect } from 'react-redux';
import ChinaBtn from '../components/ChinaBtn';
import { china } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(china('china'))
});
export default connect(mapStateToProps, mapDispatchToProps)(ChinaBtn);
containers/RussiaBtnContainer.js
import { connect } from 'react-redux';
import RussiaBtn from '../components/RussiaBtn';
import { russia } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(russia('russia'))
});
export default connect(mapStateToProps, mapDispatchToProps)(RussiaBtn);
containers/DisplayContainer.js
import { connect } from 'react-redux';
import display from '../components/display';
const mapStateToProps = state => ({
text: state.text
});
export default connect(mapStateToProps)(display);
Note:
Sorry, for long code. But, I thought it is necessary to understand problem
Focus on Container Components, connect, mapStateToProps, mapDispatchToProps. According to me, problem must be there.
Your reducer returns an array and hence mapStateToProps isn't giving you right values since you expect the state to be an object, what you need is
const initialState = {
text: '',
isIClicked: false,
isCClicked: false,
isRClicked: false,
}
const country = (state=initialState,action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false,
}
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false,
}
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true,
}
default:
return state
}
export default country

How to use redux to add todo item in todo list

I have created action and reducers for my application. I am trying to create a new todo and want to save it in state using redux.
action/index.js
let taskID = 0;
export const addTodo = text => {
return { type: "ADD_TODO", text, id: taskID++ };
};
reducers/todos.js
const todo = (state = {}, action) => {
switch (action.type) {
case "ADD_TODO":
return {
id: action.id,
text: action.text,
status: false
};
default:
return state;
}
};
export default todo;
reducers/index.js
import { combineReducers } from "redux";
import todos from "./todos";
const todoApp = combineReducers({ todo });
export default todoApp;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";
import { Provider } from "react-redux";
import { createStore } from "redux";
import todoApp from "./reducers/todos";
let store = createStore(todoApp);
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById("root")
);
registerServiceWorker();
App.js
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import AppBar from "material-ui/AppBar";
import FloatingActionButton from "material-ui/FloatingActionButton";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import * as strings from "./Strings";
import * as colors from "./Colors";
import styles from "./Styles";
import ContentAdd from "material-ui/svg-icons/content/add";
import Dialog from "material-ui/Dialog";
import FlatButton from "material-ui/FlatButton";
import * as injectTapEventPlugin from "react-tap-event-plugin";
import TextField from "material-ui/TextField";
import { List, ListItem } from "material-ui/List";
import { connect } from "react";
import { addTodo } from "./actions/index";
const AppBarTest = () =>
<AppBar
title={strings.app_name}
iconClassNameRight="muidocs-icon-navigation-expand-more"
style={{ backgroundColor: colors.blue_color }}
/>;
class App extends Component {
constructor(props) {
injectTapEventPlugin();
super(props);
this.state = {
open: false,
todos: [],
notetext: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleCreateNote = () => {
let todos = [...this.state.todos];
todos.push({
id: todos.length,
text: this.state.notetext,
completed: false
});
this.setState({ todos: todos }, () => {
// setState is async, so this is callback
});
this.handleClose();
};
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
_renderTodos() {
return this.state.todos.map(event => {
return (
<ListItem
primaryText={event.text}
key={event.id}
style={{ width: "100%", textAlign: "center" }}
onTouchTap={this._handleListItemClick.bind(this, event)}
/>
);
});
}
_handleListItemClick(item) {
console.log(item);
}
render() {
return (
<MuiThemeProvider>
<div>
<AppBarTest />
<FloatingActionButton
style={styles.fab}
backgroundColor={colors.blue_color}
onTouchTap={this.handleOpen}
>
<ContentAdd />
</FloatingActionButton>
<Dialog
open={this.state.open}
onRequestClose={this.handleClose}
title={strings.dialog_create_note_title}
>
<TextField
name="notetext"
hintText="Note"
style={{ width: "48%", float: "left", height: 48 }}
defaultValue={this.state.noteVal}
onChange={this.handleChange}
onKeyPress={ev => {
if (ev.key === "Enter") {
this.handleCreateNote();
ev.preventDefault();
}
}}
/>
<div
style={{
width: "4%",
height: "1",
float: "left",
visibility: "hidden"
}}
/>
<FlatButton
label={strings.create_note}
style={{ width: "48%", height: 48, float: "left" }}
onTouchTap={this.handleCreateNote}
/>
</Dialog>
<List style={{ margin: 8 }}>
{this._renderTodos()}
</List>
</div>
</MuiThemeProvider>
);
}
}
export default App;
I want to save new todo inside handleCreateNote function, I am not sure how to use store, dispatch here to save it in state. Can anyone help me ?
Change
export default App;
To
function mapStateToProps(state) {
return {
todo: todo
}
}
export default connect(mapStateToProps, actions)(App)
You should also import all the actions using
import * as actions from './action/index';
After all these modify your this function as follows:-
handleCreateNote = () => {
let todos = [...this.state.todos];
let newTodo = {
id: todos.length,
text: this.state.notetext,
completed: false
};
todos.push(newTodo);
this.setState({ todos: todos }, () => {
// setState is async, so this is callback
});
this.props.addTodo(this.state.notetext);
this.handleClose();
};
Also your logic for adding todos is incorrect.
So your action creator should be something like this
let taskID = 0;
export const addTodo = text => {
return {
type: "ADD_TODO",
text: text,
id: taskId++
};
};
Now the reducer also needs to change, so that should be something like this:-
const todo = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
let newTodo = {
id: action.id,
text: action.text,
status: false
};
return [...state, newTodo]
default:
return state;
}
};
export default todo;
I hope this helps.Not the best of implementations, but will solve your issue.

react + redux: Cannot read property 'handleClickMenu' of undefined

I'm new to the reagent and Redux. I am trying to make the menu, but the console getting error:
App.js?eb5a:12 Uncaught TypeError: Cannot read property 'handleClickMenu' of undefined
How to fix the error?
As the payload in a new state record?
App.js:
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import BottomMenu from '../components/BottomMenu'
import * as pageActions from '../actions/PageActions'
import * as userActions from '../actions/UserActions'
import * as bmenuActions from '../actions/BottomMenuActions'
class App extends Component {
render() {
const { user, page, bottomMenu } = this.props
const { getPhotos } = this.props.pageActions
const { handleClickMenu } = this.props.bmenuActions
const { handleLogin } = this.props.userActions
return <div className='row'>
<Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching} error={page.error}/>
<User name={user.name} handleLogin={handleLogin} error={user.error} />
<BottomMenu selectedItem={bottomMenu.selectedItem} bmenuClick={() => handleClickMenu} />
</div>
}
}
function mapStateToProps(state) {
return {
user: state.user,
page: state.page,
bottomMenu: state.bottomMenu
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch),
userActions: bindActionCreators(userActions, dispatch),
bmenuActions: bindActionCreators(bmenuActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
component bottomMenu.js:
import React, { PropTypes, Component } from 'react'
import BottomMenuItem from '../components/BottomMenuItem'
export default class BottomMenu extends Component {
render() {
const { selectedItem, bmenuClick } = this.props;
const menuItems = [{
url: 'home',
name: 'Главная страница'
}, {
url: 'goods',
name: 'Объем поставок'
}, {
url: 'geo',
name: 'География поставок'
}, {
url: 'clients',
name: 'Клиенты и партнеры'
}, {
url: 'production',
name: 'Виды продукции'
}, {
url: 'cost',
name: 'Рассчитайте стоимость'
}, {
url: '',
name: 'Свяжитесь с нами'
}];
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={index === selectedItem} bmenuClick={bmenuClick} /> )}
</ul>
}
}
BottomMenu.propTypes = {
item: PropTypes.object,
selected: PropTypes.bool
}
component BottomMenuItem.js:
import React, { PropTypes, Component } from 'react'
export default class BottomMenuItem extends Component {
bmenu_Click(e){
e.preventDefault()
this.props.bmenuClick(e)
}
render() {
const { item, selected} = this.props
const className = 'footer-menu__li footer-menu__li--' + item.url + (selected ? ' footer-menu__li--current' : '')
return <li className={className}>
{!selected ?
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click}>{item.name}</a>
:
<span className='footer-menu__href'>{item.name}</span>
}
</li>
}
}
BottomMenuItem.propTypes = {
item: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired,
bmenuClick: PropTypes.func.isRequired
}
action: BottomMenuAction.js
export function handleClickMenu(el) {
console.log(el)
return function(dispatch) {
dispatch({
type: 'bmenuClick',
payload:''
})
}
}
A few mistakes in your code
First is a typo where you bindAction as bmenuActions and then in your App.js you are using it with the wrong case as const { handleClickMenu } = this.props.bMenuActions. You need to change that to const { handleClickMenu } = this.props.bmenuActions
Also now since here handleClickMenu is a function you need to bind it while passing down to BottomMenu component like
<BottomMenu selectedItem={bmenu.selectedItem} bmenuClick={() => handleClickMenu} />
Now again in App.js your state is available as bottomMenu and you are resolving it like bmenu. Change it to
const { user, page, bottomMenu } = this.props
Now from BottomMenu component you are sending the props as bmenuClick to the BottomMenuItem component like
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={item === selectedItem} bmenuClick={() => bmenuClick }/> )}
</ul>
but you are using it in your BottomMenuItem component as this.props.handleClickMenu here
bmenu_Click(e){
this.props.handleClickMenu(e)
}
You need to change it as
bmenu_Click(e){
this.props.bmenuClick(e)
}
Thank you, I've done:
<BottomMenuItem key={index} item={item} pos={index} selected={index === selectedItem} bmenuClick={bmenuClick} />
and
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click} data-pos={pos}>{item.name}</a>

Resources