When changing props in redux all components are affected - reactjs

When I mount my component CommentFeed it will call getPost() twice because I have two posts with separate CommentFeeds. Since each of those fields is listening to prop changes for comments the last CommentFeed(Post 2)doesn't have any comments and will causes my (Post 1) to have no comments. I was thinking about saving the states of the comments for each CommentFeed, but if I check prevState with nextProps theoretically should still override the comments of Post1 since technically the nextProp is different?
How do I even approach this? Please tell me if this doesn't make sense so I can clarify better
Post 1:
has Comments
Post 2: No comments (the getPost() of this CommentFeed removes the CommentFeed of Post 1)
import React, { Component } from "react";
import PropTypes from "prop-types";
import CommentItem from "./CommentItem";
import { getPost } from "../../oldComp/actions/postActions";
import { connect } from "react-redux";
class CommentFeed extends Component {
constructor(props) {
super(props);
const { postId } = this.props;
this.props.getPost(postId);
}
render() {
const { postId, comments } = this.props;
console.log(comments);
if (comments === undefined) {
return <div />;
} else {
return (
<div>
{postId}
{comments.map(comment => {
return (
<div key={comment._id}>
<CommentItem
key={comment._id}
postId={postId}
comment={comment}
/>
</div>
);
})}
</div>
);
}
}
}
CommentFeed.propTypes = {
comments: PropTypes.array,
getPost: PropTypes.func.isRequired,
comments: PropTypes.array,
postId: PropTypes.string.isRequired
};
const mapStateToProps = state => ({
comments: state.post.post.comments
});
export default connect(
mapStateToProps,
{ getPost }
)(CommentFeed);

I believe if you put getPost() in componentDidMount, it'll only be called once. This method is called a single time when the component first mounts, but won't get called on re-render. Async fetch actions in general go inside this lifecycle.

Related

React, Redux - pass function from component A to other components

import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent

write to local state from props

I understand that the problem is rather trivial, but I can't deal with it, I need your help.
I tried all the solutions in similar questions, but it did not work for me
The bottom line is that when I mount the component, I run fetch and I get a list of articles from my API, but this does not suit me, since I don’t save them in the local state.
Besides, my terrible knowledge of React, I have 2 more problems:
1) When I navigate through the pages, when I return to the articles page, the number of results is duplicated in an arithmetic progression, as I understand it, this is the problem that I keep articles in props, but I need to save it in a local state.
2) From this my second problem expires. I tried everything, but I could not do props.articles -> state.articles, in order to apply this.state.articles.map in the future
//actions
import {FETCH_ALL_ARTICLES} from "../constants";
export const fetchAllArticles = () => {
return (dispatch) => {
let headers = {"Content-Type": "application/json"};
return fetch("/api/articles/", {headers, })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return {status: res.status, data};
})
} else {
console.log("Server Error!");
throw res;
}
})
.then(res => {
if (res.status === 200) {
return dispatch({type: FETCH_ALL_ARTICLES, articles: res.data});
}
})
}
};
//component
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import {articles} from "../actions";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
articles: []
}
console.log(this.props.articles)
};
componentDidMount() {
this.props.fetchAllArticles()
};
render() {
return (
<div>
<Link to='/notes'>Notes</Link>
<h2>All articles</h2>
<hr />
<table>
<tbody>
{this.state.articles.map((article, id) => (
<tr key={`article_${id}`}>
<td>{article.headline}</td>
<td>{article.description}</td>
<td>{article.created}</td>
<td>{article.author.username}</td>
<td>{article.image}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapStateToProps = state => {
return {
articles: state.articles,
}
};
const mapDispatchToProps = dispatch => {
return {
fetchAllArticles: () => {
dispatch(articles.fetchAllArticles())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
// reducer
import {FETCH_ALL_ARTICLES} from "../constants";
const initialState = [];
export default function articles(state=initialState, action) {
switch (action.type) {
case FETCH_ALL_ARTICLES:
return [...state, ...action.articles];
default:
return state;
}
}
Your question is unclear but I will try to explain based on the title 'write to local state from props'.
You can utilize component lifecycles as below to achieve that
componentWillReceiveProps(nextProps) {
if (nextProps.articles) {
this.setState({ articles: nextProps.articles });
}
}
Basically whenever there is an update to this component, this lifecycle method componentWillReceiveProps will get invoked before re-rendering, so we can call setState here and save it to local state.
when I return to the articles page, the number of results is duplicated in an arithmetic progression
This should not happened if you handle your reducer correctly. For example, after you fetch articles from API, clear your array then only store the value you receive from API. But then of course it's all depending on what you want to achieve
Every time your component mounts, you fetch all the articles.
When you fetch all the articles, you add them to your existing Redux state:
return [...state, ...action.articles];
To fix this, you can discard the old articles instead of keeping them:
return [...action.articles];
Or you can avoid fetching articles if they have already been fetched:
componentDidMount() {
if (!this.props.articles || this.props.articles.length === 0) {
this.props.fetchAllArticles()
}
};
You don't need to do anything with local state. Your Redux state is your single source of truth. Keeping another copy of the data in local state serves no purpose.
You can render your articles directly from this.prop.articles in render function.
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import {articles} from "../actions";
class Home extends Component {
componentDidMount() {
this.props.fetchAllArticles()
};
render() {
return (
<div>
<Link to='/notes'>Notes</Link>
<h2>All articles</h2>
<hr />
<table>
<tbody>
{this.props.articles.map((article, id) => (
<tr key={`article_${id}`}>
<td>{article.headline}</td>
<td>{article.description}</td>
<td>{article.created}</td>
<td>{article.author.username}</td>
<td>{article.image}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapStateToProps = state => {
return {
articles: state.articles,
}
};
const mapDispatchToProps = dispatch => {
return {
fetchAllArticles: () => {
dispatch(articles.fetchAllArticles())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Your question is short, but as I understand you are looking to pass the data through props from parent component to child component and then you want to store them into local state.
For that, you need to add constructor into child component and assigned props to state like.
import React,{Component} from 'react';
class ChildComponent extends Component{
constructor(props){
super(props);
this.state={
name: props.name,
email: props.email
}
}
..........
// your child component logic
}
Pass the data through parent component like,
<ChildComponent name={this.state.name} email={this.state.email} />

Either not changing state or not rerendering redux

I am dispatching an action to a reducer to change that state, however nothing is re-rending. Below is some code however since the project is small I'm including a link to the repo on Github here
import React, {Component} from 'react';
import {connect} from 'react-redux';
import * as action from '../action';
import {addTodo} from "../action";
//this is used to get the text from the input to create a new task
let text = "";
class AddTodo extends Component
{
render()
{
return(
<div>
<input type="text" id="taskText" onChange={ () => { text = document.querySelector("#taskText").value;} }/>
<button onClick={this.props.addTodo}>+</button>
</div>
);
}
}
const mapDispatchToProps = (dispatch) =>
{
return (
{
addTodo: () =>
{
//console.log(`making action with text: ${text}`);
addTodo.payload = {text:text, completed:false};
dispatch(addTodo);
}
}
);
};
export default connect(null, mapDispatchToProps)(AddTodo);
Since you haven't shared all the related code here most people don't check your repo. In the future, try to share the related (just related code) here. If you do so, you will get faster and better answers.
I will share how you solve your problems first.
Uncomment TodoList component in the App.js file.
In TodoList component your are using the wrong state. Your todos in the todo reducer.
So:
taskList: state.list.tasks.todo.concat( state.list.tasks.completed ),
You are mutating your state in todo reducer. Don't mutate your state.
Change the related part:
case "ADD_TODO":
return { ...state, tasks: { ...state.tasks, todo: [ ...state.tasks.todo, action.payload ] } };
Other than those problems, you are using some bad practices. For example, why do you keep the text in a variable like that in your AddTodo component? Use the local state here, this is the proper React way.
Also, your action creators are not so properly defined. They are functions, returning an object. Now, your AddTodo component would be like this:
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../action";
class AddTodo extends Component {
state = {
text: "",
}
handleChange = e => this.setState( { text: e.target.value } );
handleSubmit = () => {
const newTodo = { text: this.state.text, completed: false };
this.props.addTodo( newTodo );
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<button onClick={this.handleSubmit}>+</button>
</div>
);
}
}
const mapDispatchToProps =
{
addTodo: actions.addTodo,
};
export default connect( null, mapDispatchToProps )( AddTodo );
Or even, you don't need here a separate mapDispatchToProps if you like. You can use the connect part like this:
export default connect( null, { addTodo: actions.addTodo } )( AddTodo );
Then, your related action creator would be like this:
export const addTodo = newTodo => ({
type: "ADD_TODO",
payload: newTodo
});
So, I suggest reading more good tutorials about Redux :) Just give yourself a little bit more time. Follow some good tutorials until you are sure that you know the best practices and proper ways. For example, if you study Redux, the first rule is not mutating your state. You are doing it everywhere :) Also, try to keep your state simple, not so nested.
Good luck.

use react-redux with connect() and {...this.props}

I cannot figure out, how to make right solution, when I want to call action in my container from other component, by the way I want to use spread operator because I need to pass too many parametrs in my component and don't want describe all of them.
I know I can pass all props from redux store via props, like this example in Menu, but my component too nested, and I have to send props in eighter component in nest
render() {
return (
<div className="wrapper">
<Menu {...this.props} />
</div>
);
}
}
const mapStateToProps = reduxStore => (
{
app: reduxStore.app
}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapStateToProps, mapDispatchToProps)(App);
So, I decided to connect my nested component with redux store, because I need to work from my nested component with store and actions in main container component. But this solution doesn't work, because i use spread operator to my nested component.
render() {
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...this.props} />;
}
And using spread operator is really important because component get too much different parameters from its parent component, and if i don't use {...this.props}, I have to write like this:
render() {
const { to, onlyActiveOnIndex, className, specialIcons } = this.props;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} to={to} specialIcons={specialIcons} onlyActiveOnIndex={onlyActiveOnIndex} className={className} >{this.props.children}</Link>;
}
But also, I have to connect to common redux store, and when I connected, occurs an Error, because of my component use {...this.props} and it get all props, including actions from container and component doesn't know what do with them. I find one solution of this proplem, but I'm not sure that it is right variant. I clone props with spread operators, but delete property that contain new functions (actions) from common store.
render() {
let oldProps = {...this.props};
delete oldProps.appActions;
delete oldProps.app;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...oldProps} >{this.props.children}</Link>;
}
}
const mapState = reduxStore => ({app: reduxStore.app}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapState, mapDispatchToProps)(NavLink);
I'm guessing that I don't understand something basic and global in react-redux or I use bad practice. May be I should use higher order components in React? but now I don't know how to make it better.
Here is a functional example. I made it for a personal project. I removed the useless code for the purpose of the example.
Something you might want to get is eslint, it will show you basic mistake people are making while coding.
For example, it will say that you having declared your PropTypes. In your code, where does it say what app is? Sure it's coming from reduxStore.app but what kind of PropTypes is it?
Also, you shouldn't link all the reduxStore state to your component. You should just import what you really need. In my example, I import only users from state.app.users. If I had more, or want all elements of the reducer state, I would import all of them individually and then declare the props like this:
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
Because JavaScript isn't a typed language, the PropTypes like above help you make typed validation. You can also see the props actions which contains all the functions you import in AppActions in your case.
To see how to use the function from the action afterward, look at my componentWillMount()
import React, { Component, PropTypes } from 'react';
import { ListView} from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as app from '../../actions/appActions';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: ds.cloneWithRows(props.users.toJS()),
};
}
componentWillMount() {
this.props.actions.load();
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
dataSource: ds.cloneWithRows(nextProps.users),
});
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
enableEmptySections
renderRow={
(rowData) => <User haveLunch={rowData.haveLunch} name={rowData.name} />
}
/>
);
}
}
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
function mapStateToProps(state) {
return {
users: state.app.users,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(app, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Hope this will help ya ;)

Handling updates to store with React/Redux and lifecycle events

I'm using React with Redux on my front end and using the Rails API to handle my backend. At present, I am trying to update a list of articles based on user addition of an article. The ArticleForm component fires an action creator that is successfully updating my ArticleList. However, at present the life cycle method componentWillUpdate is firing continuously making axios requests to Rails, and Rails keeps querying my database and sending back the articleList.
Note: I have tried using shouldComponentUpdate as such to no avail, the DOM doesn't update:
// shouldComponentUpdate(newProps){
// return newProps.articleList !== this.props.articleList
// }
My question is: how can I use React's lifecycle methods to avoid this from happening and only happening when my articleList updates. Am I going down the wrong path using lifecycle methods? I'm fairly new to React/Redux so any and all advice is helpful!
I have the following container:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import ArticleForm from './ArticleForm'
import ArticleList from './ArticleList'
import removeArticle from '../actions/removeArticle'
import fetchArticles from '../actions/fetchArticles'
import updateArticleList from '../actions/updateArticleList'
class DumbArticleContainer extends Component {
componentWillMount() {
this.props.fetchArticles()
}
// shouldComponentUpdate(newProps){
// return newProps.articleList !== this.props.articleList
// }
componentWillUpdate(newProps){
if (newProps.articleList.articleList.count !== this.props.articleList.articleList.count){
this.props.updateArticleList()
}
}
render() {
return(
<div>
<ArticleForm />
<ArticleList articleList={this.props.articleList} />
</div>
)
}
}
const ArticleContainer = connect(mapStateToProps, mapDispatchToProps)(DumbArticleContainer)
function mapStateToProps(state) {
return {articleList: state.articleList}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({removeArticle, fetchArticles, updateArticleList}, dispatch);
}
export default ArticleContainer
here is the ArticleForm
import React, { Component, PropTypes } from 'react'
import { reduxForm } from 'redux-form'
import addArticle from '../actions/addArticle.js'
class ArticleForm extends Component {
constructor(props) {
super(props)
this.state = {disabled: true}
}
/* Most article elements are displayed conditionally based on local state */
toggleState(){
this.setState({
disabled: !this.state.disabled
})
}
handleFormSubmit(props) {
event.preventDefault()
const {resetForm} = this.props
this.props.addArticle(props).then( ()=>{
var router = require('react-router')
router.browserHistory.push('/dashboard')
resetForm()
})
}
render() {
const disabled = this.state.disabled ? 'disabled' : ''
const hidden = this.state.disabled ? 'hidden' : ''
const {fields: {title, url}, handleSubmit} = this.props;
return (
<div className="article-form">
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<button className="article-form-btn"
hidden={!hidden}
onClick={this.toggleState.bind(this)}
>
+ Add Article
</ button>
<input className="article-form-input"
hidden={hidden}
type="textarea"
placeholder="Title"
{...title}
/>
<input className="article-form-input"
hidden={hidden}
type="textarea"
placeholder="Paste Link"
{...url}
/>
{ this.state.disabled
? ''
: <input className="article-form-input"
type="submit"
value="Save"
/>
}
</form>
</div>
);
}
}
export default reduxForm({
form: 'articleForm',
fields: ['title', 'url']
},
null,
{ addArticle })(ArticleForm);
and the ArticleList
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import removeArticle from '../actions/removeArticle.js'
import fetchArticles from '../actions/fetchArticles'
import { ListGroup } from 'react-bootstrap'
import { ListGroupItem } from 'react-bootstrap'
class Article extends Component {
render(){
var articleList = this.props.articleList.articleList
return(
<div>
<ListGroup>
{ articleList.slice(articleList.length - 10, articleList.length)
.map( (article) => {
return(
<ListGroupItem href="#" header={article.attributes.title}>
{article.attributes.url}
</ListGroupItem>
)}
)}
</ListGroup>
<div> View All Articles </div>
</div>
)
}
}
const ArticleList = connect(mapStateToProps, mapDispatchToProps)(Article)
function mapStateToProps(state) {
return {articleList: state.articleList}
}
function mapDispatchToProps(dispatch) {
return {removeArticle: bindActionCreators({removeArticle}, dispatch),
fetchArticles: bindActionCreators({fetchArticles}, dispatch)
}
}
export default ArticleList
action creator:
So here is my action creator import axios from 'axios'
import axios from 'axios'
function updateArticleList(){
const url = 'http://localhost:3000/api/v1/articles'
return axios.get(url).then( (response)=> {
return {
type: 'UPDATE_ARTICLE_LIST',
payload: response.data
}
})
}
export default updateArticleList
and reducer:
export default function articleReducer(state = {articleList: []}, action) {
switch(action.type){
case 'FETCH_ARTICLES':
return Object.assign({}, state, {articleList: action.payload.data});
case 'UPDATE_ARTICLE_LIST':
return Object.assign({}, state, {articleList: action.payload.data});
default:
return state
}
}
There is no issue with the store nor the action creators nor the reducers, they are all working pretty well. I can't really replicate the hundreds of queries rails is performing but am happy to include other code should anyone need to see it.
Thanks!
Your mapDispatchToProps is using bindActionCreators wrong. Instead of
function mapDispatchToProps(dispatch) {
return {removeArticle: bindActionCreators({removeArticle}, dispatch),
fetchArticles: bindActionCreators({fetchArticles}, dispatch)
}
}
you should use
function mapDispatchToProps(dispatch) {
return bindActionCreators({removeArticle, fetchArticles}, dispatch);
}
bindActionCreators can, as the name suggests, bind more than one action creator.
This probably won't solve your issue but an answer is the only place I could put this nicely.
Note that you'll need to fix how you're using it as well. No more double names.
I'd like to keep a state called shouldUpdateList. Whenever I fire a action that changes the list(add or update an item to the list), I set shouldUpdateList to true. Then,set it back to false whenever I fire ajax action to fetch the list.
The lifecycle event I use to check shouldUpdateList is componentWillReceiveProps, if it's true I fire a fetch action.
EDIT: I mean keep shouldUpdateList state in Redux store. Something like:
const INIT_STATE = {
list: [],
shouldUpdateList: false
}
then
case Action.ADD_NEW:
//set shouldUpdateList to true
case Action.FETCH_LIST:
//set shouldUpdateList to false
lastly, in component
componentWillReceiveProps(nextProps) {
if(nextProps.shouldUpdateList === true) {
//dispatch action FETCH_LIST
}
}

Resources