I use react and redux in my web app. It's the simple app which has 4 components, one reducer and 3 actions. After I add a new entry to list, react renders component of list (the listItem), then re-renders the whole app. What is the cause of re-rendering whole app after rendering one component?
Updated:
App container:
class App extends Component {
static propTypes = {
groups: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
render() {
return (<div>
<Header addGroup={this.props.actions.addGroup} />
<List groups={this.props.groups} />
</div>
);
}
}
function mapStateToProps(state) {
return { groups: state.groups };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(AppActions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Reduser:
export default function groupDiseases(state = initialState, action){
switch (action.type) {
case ADD_GROUP:
return [
{
id: '',
name: action.name
},
...state
];
case DELETE_GROUP:
return state.filter(group =>
group.id !== action.id
);
case EDIT_GROUP:
return state.map(group => (group.id === action.id ? { id: action.id, name: action.name } : group));
default:
return state;
}
}
Components:
export default class Add extends Component {
static propTypes = {
addGroup: PropTypes.func.isRequired
}
componentDidMount() {
this.textInput.focus();
}
handleAdd = () => {
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
render() {
return (
<form className="add_form">
<input
type="text"
className="add__name"
defaultValue=""
ref={(input) => this.textInput = input}
placeholder="Name" />
<button
className="add__btn"
ref="add_button"
onClick={this.handleAdd}>
Add
</button>
</form>
);
}
}
export default class ListGroups extends Component {
static propTypes = {
groups: PropTypes.array.isRequired
};
render() {
let data = this.props.groups;
let groupTemplate = <div> Группы отсутствуют. </div>;
if (data.length) {
groupTemplate = data.map((item, index) => {
return (
<div key={index}>
<Item item={item} />
</div>
);
});
}
return (
<div className="groups">
{groupTemplate}
<strong
className={'group__count ' + (data.length > 0 ? '' : 'none')}>
Всего групп: {data.length}
</strong>
</div>
);
}
}
It's likely due to the fact that you are letting the <form> continue its default behavior, which is to submit to a targeted action. Take a look at the w3c spec for buttons:
http://w3c.github.io/html-reference/button.html
Specifically, a button with no type attribute will default to submit.
So your button is telling the form to submit, with the target being the current page since none is provided. In your handleAdd method, you can do something like:
handleAdd = (event) => {
event.preventDefault(); // prevent default form submission behavior
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
Or you can modify your button to have type="button".
Related
I am trying to learn Redux by simply add/delete users. I have an action 'ADD_PROFILE', with payload : name,account-number. On clicking add button, I wanted to update the store, hide the 'add user' form and show a message 'User added successfully'. If it is in React, I can have a boolean state variable, update/reset variable and switch the views. If I wanted to do the same using Redux bit not sure how.
This is what I tried :
Action
export const addProfile = (name, account_number) => {
console.log(name, account_number)
return{
type :'ADD_PROFILE',
payload : {
name : name,
account_number : account_number
}
};
}
Reducer:
export default (profilesList=[],action) => {
switch(action.type){
case 'ADD_PROFILE':
return [...profilesList, action.payload]
case 'DELETE_PROFILE':
return profilesList.filter(name => name!== action.payload.name)
default:
return profilesList;
}
}
AddView.js
import React from 'react';
import { connect } from 'react-redux';
import { addProfile } from '../actions';
class AddView extends React.Component{
constructor(props) {
super(props);
this.state={
isProfileAdded: false
};
}
addValuesView(){
return(
<div>
Name : <input type="text" value={this.props.profiles.name} ref={el => (this.nameInputRef = el)}/>
Account Number : <input type="text" value={this.props.profiles.account_number} ref={el => (this.accountInputRef = el)}/>
<button onClick={() => {
this.setState(isProfileAdded=true),
this.props.addProfile(this.nameInputRef.value,this.accountInputRef.value)
}
}>Add</button>
</div>
);
}
profileAddedView(){
return(
<div>Profile added succesfully</div>
)
}
view(){
return !this.props.profiles.isProfileAdded ? this.addValuesView() : this.profileAddedView()
}
render(){
console.log(this.state)
return this.view()
}
}
const mapStateToProps = (state) => {
return { profiles : state.profiles }
}
const mapDispatchToProps = dispatch => ({
onAddProfile: dispatch(addProfile())
});
export default connect(mapStateToProps, {addProfile}) (AddView);
App.js
import React from 'react';
import AddView from './AddView';
const App = () =>{
return (
<div className="ui container">
<AddView />
</div>
);
}
export default App;
Method this.setState should receive an object:
() => {
this.setState({ isProfileAdded: true});
this.props.addProfile(this.nameInputRef.value, this.accountInputRef.value);
}
Hello I am trying to get the reducer "removePlayer" to update the state it generates to have a selectedPlayerIndex of -1 but I can't seem to get it to work, I've looked for similar questions but couldn't find anything that is informative enough to answer my question.
Here is the code
import * as PlayerActionTypes from '../actiontypes/player';
const initialState = {
players: [{
name: 'Jim Hoskins',
score: 31,
created: '11/8/2016',
updated: '11/9/2016'
},
{
name: 'Andrew Chalkley',
score: 20,
created: '11/9/2016',
updated: '11/10/2016'
},
{
name: 'Alena Holligan',
score: 50,
created: '11/11/2016',
updated: '11/12/2016'
}
],
selectedPlayerIndex: -1
}
export default function Player(state=initialState, action) {
switch(action.type) {
case PlayerActionTypes.REMOVE_PLAYER:
const removePlayerList = [
...state.players.slice(0, action.index),
...state.players.slice(action.index + 1)
];
return {
...state,
players: removePlayerList,
selectedPlayerIndex: -1
}
case PlayerActionTypes.SELECT_PLAYER:
return {
...state,
selectedPlayerIndex: action.index
}
default:
return state;
}
}
here is the top level component
class Scoreboard extends Component {
static propTypes = {
players: PropTypes.array.isRequired,
selectedPlayerIndex: PropTypes.number.isRequired
};
render() {
const { dispatch, players, selectedPlayerIndex } = this.props;
const addPlayer = bindActionCreators(PlayerActionCreators.addPlayer, dispatch);
const removePlayer = bindActionCreators(PlayerActionCreators.removePlayer, dispatch);
const updatePlayerScore = bindActionCreators(PlayerActionCreators.updatePlayerScore, dispatch);
const selectPlayer = bindActionCreators(PlayerActionCreators.selectPlayer, dispatch)
const playerComponents = players.map((player, index) => (
<Player
index={index}
name={player.name}
score={player.score}
key={player.name}
updatePlayerScore={updatePlayerScore}
removePlayer={removePlayer}
selectPlayer={selectPlayer}
/>
));
return (
<div className="scoreboard">
<Header players={players} />
<div className="players">
{ playerComponents }
</div>
<AddPlayerForm addPlayer={addPlayer} />
<div className="player-detail">
<PlayerDetail players={players} selectedPlayerIndex={selectedPlayerIndex} />
</div>
</div>
);
}
}
const mapStateToProps = state => (
{
players: state.players,
selectedPlayerIndex: state.selectedPlayerIndex
}
);
export default connect(mapStateToProps)(Scoreboard);
Here is the component that child component Where remove_player is dispatched
import React, { PropTypes } from 'react';
import Counter from './Counter';
const Player = props => (
<div className="player">
<div className="player-name" onClick={() => props.selectPlayer(props.index)}>
<a className="remove-player"
onClick={() => props.removePlayer(props.index)}>
✖
</a>
{props.name}
</div>
<div className="player-score">
<Counter
index={props.index}
updatePlayerScore={props.updatePlayerScore}
score={props.score}
/>
</div>
</div>
);
I've tried to keep the code to only what I think is necessary, if you need to see other parts of the app just let me know.
It looks like you've got a nested onClick event. When you click on removePlayer it propagates up to the selectPlayer. This is due to event bubbling. You need to stop the event from propagating up to the selectPlayer component.
event.stopPropagation();
You'll need to change the removePlayer onClick function to something like:
function(event) {
props.removePlayer(props.index);
event.stopPropagation();
}
More information on event bubbling and capturing can be found here
I am getting “TypeError: Cannot read property 'props' of undefined”
when i try to fire my function onChange from a deeper component. I can fire the action from the DemoForm component, but then I cannot pass in my value as it becomes undefined so, I am trying to make a function that takes in the event information and then I am firing my action but it says props is undefined, when I do a debugger and check on the console, its all there
// App.js
class App extends Component {
constructor(props) {
super(props)
}
handleThis(e){
this.props.SomeAction
}
render() {
return (
<div className="App">
<DemoForm state={this.props} someFunction={this.handleThis }/>
<AnotherForm/>
</div>
);
}
}
const mapStateToProps = (reduxState) => {
return reduxState;
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default connect(
mapStateToProps, mapDispatchToProps
)(App)
// Demo.js
let DemoForm = ({ handleSubmit, submitting, state }) =>
<form onSubmit={handleSubmit(showResults)}>
<Field name="value" label="Value" component={RenderInput} onChange={(e) => this.props.someFunction(e.target.value) } />
<button type="submit"> Submit </button>
{console.log("Demo state >>>>> ", {state})}
</form>
DemoForm = reduxForm({
form: 'demo',
destroyOnUnmount: false,
validate
})(DemoForm)
export default DemoForm
// RenderInput
const RenderInput = createRenderer((input, label, onChange) => {
return <input {...input}/>
})
export default RenderInput
// createRenderer
const createRenderer = render => ({ input, meta, label, ...rest }) => {
return (
<div>
{/* <pre> {JSON.stringify(input, null, 2) }</pre> */}
<label> {label}</label>
{render(input, label, rest)}
{
meta.touched &&
<span className="text-danger"> {meta.error} </span>
}
</div>
)
}
export default createRenderer
// REDUCER
const initialState = {
todos: [],
count: 0,
demoPercent: 0,
anotherPercent : 0
}
export default function rootReducer(state = initialState, action) {
if(action.type === "INC"){
console.log("incrementing count")
let newState = {...state}
newState.count++
return {
...newState
}
}
if(action.type === "GET_PERCENT"){
console.log("getting balance percent", action.payload)
let newState = {...state}
newState.demoPercent = action.payload;
newState.anotherPercent = 100 - action.payload;
return {
...newState
}
}
return state;
}
// ACTION
export function increase(){
console.log("i am INC action firing")
return {
type: "INC"
}
}
export function getPercent(value){
console.log(value) //value is undefined
return {
type: "GET_PERCENT",
paypoad : value
}
}
You need to bind the handler in your controller, to do that just change your App component constructor to:
constructor(props) {
super(props);
this.handleThis = this.handleThis.bind(this);
}
You have to bind your function... And the best way to bind function is to use arrow functions
render() {
return (
<div className="App">
<DemoForm state={this.props} someFunction={(e) => this.handleThis(e)}/>
<AnotherForm/>
</div>
);
}
}
I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html
I am using react and redux.
I have a Container component defined as so:
import { connect } from 'react-redux';
import {addTag} from 'actions';
import ExpenseTagsControl from './expense_tags_control'
const mapStateToProps = (state, own_props={selected_tags:[]}) => {
return {
tags_list: state.tags.tags_list
};
};
const mapDispatchToProps = (dispatch) => {
return {
addTag: (tag_name) => {
dispatch(addTag(tag_name))
}
};
};
const AddExpenseTagsContainer = connect(
mapStateToProps,
mapDispatchToProps
)(ExpenseTagsControl);
export default AddExpenseTagsContainer;
The container wraps a presentational component which is defined as so:
// expense_tags_control.js
import React, {Component, PropTypes} from 'react';
import ChipInput from 'material-ui-chip-input';
import Chip from 'material-ui/Chip';
import Avatar from 'material-ui/Avatar';
import Tag from 'common/svg_icons/tag';
import AutoComplete from 'material-ui/AutoComplete'
import _ from 'underscore';
class ExpenseTagsControl extends React.Component {
constructor(props) {
super(props);
this.state = {
chips: []
};
};
handleAdd(chip) {
// If the chip does not already exist, add it. the id here will be a dummy value that is not there in the tags_list
if (!(_.contains( _.map(this.props.tags_list, (tag) => tag.id), chip.id))) {
this.props.addTag(chip.name);
}
// This is wrong.
this.setState({
chips: [...this.state.chips, chip]
});
};
handleDelete(chip) {
this.setState({
chips: this.state.chips.filter((c) => c !== deletedChip)
});
};
chipRenderer({ text, value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) {
const style = {
margin: '8px 8px 0 0',
float: 'left',
pointerEvents: isDisabled ? 'none' : undefined
};
return (
<Chip key={key} style={style} onTouchTap={handleClick} onRequestDelete={handleRequestDelete}>
<Avatar size={24} icon={<Tag />} />
{text}
</Chip>
);
};
render() {
return (
<ChipInput
hintText="Tags"
value={this.state.chips}
onRequestAdd={(chip) => this.handleAdd(chip)}
onRequestDelete={(deletedChip) => this.handleDelete(deletedChip)}
fullWidth={true}
dataSourceConfig={{ text: 'name', value: 'id' }}
dataSource={this.props.tags_list}
chipRenderer={this.chipRenderer}
openOnFocus={false}
filter={AutoComplete.fuzzyFilter}
onRequestDelete={console.log("Deleted")}
/>);
};
};
ExpenseTagsControl.PropTypes = {
tags_list: PropTypes.array.isRequired,
addTag: PropTypes.func.isRequired,
value: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired
};
export default ExpenseTagsControl;
The presentational component above, maintains a state, which indicates the chips that have been selected.
The ChipInput component allows you to select chips which are objects with an id, and a name, defined from a pre-existing data source. The component also allows you to add a new chip by typing in the name. If the typed in name does not exist in the data source, it is added to the data source.
My Problem
The id of the newly added chip is assigned once the addTag() action is dispatched. How do I get the value of the result of the action that was just dispatched?
I thought about working around this by maintaining the state of the ChipInput in the global state, and manipulate the global state upon dispatching the addTag() action. But that feels like too much overhead.
If what I understand is correct, you might want something like this:
class ExpenseTagsControl extends React.Component {
// ...
/*
* assuming your reducers are working fine and 'addTag'
* has updated global 'state.tags.tags_list'
*/
componentWillReceiveProps(nextProps) {
this.setState({ chips: this.nextProps.tags_list });
}
// ...
}
NB: You might need to optimize calling setState inside componentWillReceiveProps based on some conditions to avoid unnecessary re-render.
From what I understand, the OP's problem is how to dispatch an action to modify the redux store and at the same time update the component's local state.
Edit: added a working example
const initialState = {
tags: ['hello', 'hi', 'howdy']
}
function reducer(state = {}, action) {
switch (action.type) {
case 'ADD_TAG':
return {
...state,
tags: [
...state.tags,
action.payload.tag
]
}
default:
return state;
}
}
const store = Redux.createStore(reducer, initialState);
const addTag = (tag) => ({
type: 'ADD_TAG',
payload: {
tag
}
})
class Chips extends React.Component {
constructor(props) {
super(props);
this.chipToAdd = false;
this.state = {
chips: []
}
this.handleAdd = this.handleAdd.bind(this);
}
componentWillReceiveProps(nextProps) {
console.log(this.chipToAdd);
if (this.chipToAdd) {
this.setState({
chips: [...this.state.chips, this.chipToAdd]
}, (this.chipToAdd = false));
}
}
handleAdd(chip) {
if (this.props.tags.filter(tag => tag === chip).length === 0) {
this.chipToAdd = chip;
this.props.addTag(chip);
} else {
if (this.state.chips.filter(existingChip => existingChip === chip).length === 0) {
this.setState({
chips: [...this.state.chips, chip]
});
}
}
}
render() {
return <div >
< h3 > Tags added in component 's chip state</h3>
<ul>
{this.state.chips.map((chip, index) => <li key={index}>{chip}</li>)}
</ul>
<hr />
<h3>Tags in Redux Store</h3>
{this.props.tags.map(
(tag, index) => <li key={index}>
{tag} <button onClick={() => this.handleAdd(tag)}>Add</button>
</li>
)}
<button onClick={() => this.handleAdd('
new tag - ' + Math.floor((Math.random() * 100) + 1))}>Add a chip with new tag</button>
</div>
}
}
const mapStateToProps = ({ tags = [] }) => ({ tags });
const ConnectedChips = ReactRedux.connect(mapStateToProps, { addTag })(Chips);
class App extends React.Component {
render() {
return <div>
<h1>React/Redux Demo</h1>
<ConnectedChips />
</div>
}
}
const Provider = ReactRedux.Provider;
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('
root ')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/redux#3.6.0/dist/redux.min.js"></script>
<script src="https://unpkg.com/react-redux#4.4.6/dist/react-redux.min.js"></script>
<div id="root"></div>