React component not re-rendering on state change with connect - reactjs

I am creating a todo app where TodoApp is the main Component which contain filterLink a simple link and a VisibleTodoList section, whenever I toggle the todolist it changes its state and re render the filter component which is not using connect but VisibleTodoList(which is using connect) is not re render
class TodoApp extends Component {
render(){
return (
<div className="App">
<input ref={node =>{
this.input=node;
}}/>
<button onClick={
()=>{
store.dispatch(actions.addTodo(count++,this.input.value));
}
}>Add Todo</button>
<FilterLink filter='SHOW_ALL' store={store.getState()}/>
<FilterLink filter='ACTIVE' store={store.getState()}/>
<FilterLink filter='COMPLETED' store={store.getState()}/>
<VisibleTodoList/>
</div>
);
}
}
VisibleTodoList contain
const getVisibleTodos= (visible,todo)=>{
switch(visible){
case 'ACTIVE':
return todo.filter((item)=>item.completed === false)
case 'COMPLETED':
return todo.filter((item) => item.completed === true);
default :
return todo;
}
}
const mapStateToProps=(state)=>({
todo: getVisibleTodos(state.visiblity,state.todo)
})
const mapDispatchToProps=(dispatch)=>{
return {
onTodoClick:(id)=>{
dispatch(actions.toggleTodo(id));
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem)
TodoItem contain
class TodoItem extends Component {
render(){
return(
<ul>
{this.props.todo.map((item)=>
<TodoList key={item.id} {...item} onTodoClick={this.props.onTodoClick}
/>
)}
</ul>
);
}
}
TodoList contain
class TodoList extends Component {
render(){
console.log(this.props)
const {id,text,completed,onTodoClick} =this.props;
return (
<li
style={{textDecoration:completed?'line-through':''}}
onClick={()=>onTodoClick(id)}>{text}</li>
)
}
}
The combine reducer contain todo and visiblity reducer
function visible(state='SHOW_ALL',action){
switch(action.type){
case 'VISIBLITY_FILTER':return action.filter;
default:return state;
}
}
index.js(reducer)
function todo(state=[],action){
switch(action.type){
case 'ADD_TODO':
return[
...state,{
id:action.id,
text:action.text,
completed:false
}
];
case 'TOGGLE_TODO':
let t= state.map(item =>{
if(item.id === action.id){
item.completed =!item.completed;
}
});
return state;
default: {
return state;
}
}
}
const todos=combineReducers({
todo,
visiblity
})
This is the corresponding action creator which is imported as actions
export const toggleTodo=(id)=>{
return{
type:'TOGGLE_TODO',
id:id
}
}
export const addTodo=(count,val)=>{
return {
id:count,
text:val,
type:'ADD_TODO'
}
}

Related

Change done on one element of the list is affected to all elements of the list

I have to display a list of users and on click of a button I want to display a text inside the button. UserHeader is a component that I import from my list of records (PostList). What I realize is that the action gets affected on all the list element once I click the button and I think this is because the reducer's state array gets filled with the record and everytime I display UserHeader it displays the text inside the button. I want to display the text only on the list element I am already dealing with, not the entire list. Please help me how can I do this with redux. I am refering to the selectPost() function onClick of the button on UserHeader
// reducers.js
export const Reducer_posts = (state=[], action) => {
switch (action.type){
case 'FETCH_POSTS':
return action.payload
default:
return state;
}
};
export const Reducer_users = (state=[], action) => {
switch (action.type){
case 'FETCH_USER':
return [...state, action.payload];
default:
return state;
}
};
export const Reducer_select = (state=[], action) => {
switch (action.type){
case 'SELECT_POST':
return action.payload;
case 'DELETE_SELECT':
return null;
default:
return state;
}
};
//UserHeader.js
import React from 'react';
import { connect } from 'react-redux';
import { fetchUser, selectPost, deleteSelect } from '../actions';
class UserHeader extends React.Component {
componentDidMount () {
this.props.fetchUser(this.props.userId);
}
render() {
console.log(this.props.select)
if(!this.props.user) {
return <div> Loading... </div>
}
return <button onClick={this.props.selectPost} className="header"> {this.props.select} </button>
}
}
const mapStateToProps = (state, ownProps) => {
return {
user: state.users.find(user => user.id === ownProps.userId),
select: state.select
};
}
export default connect(mapStateToProps, { fetchUser, selectPost, deleteSelect })(UserHeader);
//PostList.js
import React from 'react';
import { connect } from 'react-redux';
import { fetchPosts, selectPost } from '../actions';
import UserHeader from './UserHeader'
class PostList extends React.Component {
componentDidMount(){
this.props.fetchPosts();
}
renderList() {
return this.props.posts.map(post => {
return (
<div className = "item" key={post.id}>
<i className="large middle aligned icon user" />
<div className="content">
<div className = "description">
<h2> {post.title} </h2>
<p> {post.body} </p>
</div>
<UserHeader userId = {post.userId}/>
</div>
</div>
)
})
}
render(){
return <div className = "ui relaxed divided list"> {this.renderList()} </div>;
};
}
const mapStateToProps = state => {
return { posts: state.posts, select: state.select };
};
export default connect( mapStateToProps, { fetchPosts, selectPost })(PostList);
You should store on the state elements with an id, in this way you can manage it as you like.
Example
// Let's imagine that this is your initial state. State here is an object of an x
// object that contains n-object that represents your button.
//
const state = {
myButtons: [
{
id: "unique_id",
.
.
},
{...}
],
selectedBtn: -1,
}
In this scenario you can easily get the button you want passing the id through the action and so the reducer would be something like:
export const Reducer_posts = (state=[], action) => {
switch (action.type){
case 'FETCH_POSTS':
const newState = Object.assign({}, state);
newState.myButtons.concat(action.payload)
return Object.assign({}, newState)
default:
return state;
}
};
export const Reducer_select = (state=[], action) => {
switch (action.type){
case 'SELECT_POST': // I think this is fired on click on a button
const newState = Object.assign({}, state);
newState.selectedBtn = action.payload.btnId;
return Object.assign({}, newState);
case 'DELETE_SELECT': // when you have to delete a button
const newState = Object.assign({}, state);
newState.myButtons =
newState.myButtons.filter((el) => el.id !== action.payload.id)
return Object.assign({}, newState);
default:
return state;
}
};
Now the same if you have to change the text with a structure like this, you can easily access to this array using filter and getting the button you want. On your code snippet when your code arrived to the reducer it just returned action.payload, and this is actually wrong, because one of the principle of redux says that the state should be immutable. I learned redux from here and I know that can be really difficult at the beginning

Need help to call an action using redux as well as update Component State on a single event of Click?

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

Cannot fire my action from the container page, props undefined error

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

Props return undefined ( React )

I want to access my props.group in my ViewGroup component, but my props return undefined
I tried with a constructor but it's producing the same undefined.
enter image description here
ViewGroup.js
class ViewGroup extends React.Component{
render() {
console.log(this.props.group, ' CONSOLE LOG PROPS GROUP')
return(
<div style={styles.base}>
</div>
)
}
}
DataGroup Component
import ViewGroup from './ViewGroup'
class DataGroup extends React.Component{
render(){
const { dataGroups } = this.props
return(
<div>
<ViewGroup
group={dataGroups}
/>
</div>
)
}
}
const mapStateToProps = state => {
const dataGroups = state.groupReducer.group ? state.groupReducer.group :[]
return {
dataGroups
}
};
export default connect(mapStateToProps)(DataGroup)
Actions.js
export function loadGroup(data){ return { type: LOAD_GROUP, data }};
export default function groupReducer( state= {}, action = {}){
switch (action.type){
case LOAD_GROUP:
return {
...state,
group: action.data
}
default:
return state
}
}
Reducer
const reducer = combineReducers({
Auth,
groupReducer,
persistStore,
form: formReducer
});
I think this: const dataGroups = state.groupReducer.group ? state.groupReducer.group :[] should actually be: const dataGroups = state.group ? state.group :[].

React re-renders whole app after rendering a component

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".

Resources