React JS with Redux is not updating the state on action triggered - reactjs

I am developing a simple Web application using React JS and the Redux. What I am doing now just updating the state through the actions. But it is not working.
I have the component like this
#connect((store) => {
return {
tweets: store.tweets.tweets
}
})
class TweetListComponent extends React.Component{
constructor(props)
{
super(props);
this.props.dispatch(fetchTweets());
this.state = { tweets : this.props.tweets };
}
render()
{
var todos = this.state.tweets.map((item, index)=>{
return <li>{item}</li>;
})
return (
<div>
<h3>Tweet List</h3>
<ul>
{todos}
</ul>
</div>
);
}
}
module.exports = TweetListComponent;
As you can see in the constructor, I am calling fetchTweets() function which is the Redux action imported. Then getting the tweets property of the state of the TweetsReducer.
This is the tweet reducer definition.
export default function reducer(state={
tweets : [ "this is default" ]
}, action){
switch(action.type)
{
case 'FETCH_TWEETS': {
return { tweets: ["How", "are", "you", "doing"] };
}
}
return state;
}
According to my code, it should display this array, ["How", "are", "you", "doing"] because I called fetchTweets action again in the constructor to update the value. But it is still rendering this array, [ "this is default" ]. How can I solve the issue?

The problem is that you are setting the state based on props in constructor which is only called once and although you dispatch the action in constructor to get tweets the new updated tweets are not immediately available. You should directly use tweets from props instead of copying it to state since setting a state directly derivable from props is an antipattern
class TweetListComponent extends React.Component{
constructor(props)
{
super(props);
this.props.dispatch(fetchTweets());
}
render()
{
var todos = this.props.tweets.map((item, index)=>{
return <li>{item}</li>;
})
return (
<div>
<h3>Tweet List</h3>
<ul>
{todos}
</ul>
</div>
);
}
}

The usage should be like
#connect((store) => {
return {
tweets: store.tweets.tweets
}
}, {
fetchTweets
})
And in component use it as
componentDidMount() {
this.props.fetchTweets();
}
Also directly use tweets from props, don't store it in the state. You are not updating your state when props are changed

Related

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

Call a function on application startup in react

I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.

Fetch data periodically from server and re-render view in reactjs

I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

React Redux re-fetch data using state and state change

I would like to pass data from my state to a fetch call (e.g. search term). When the state changes, it should re-fetch my data.
The lifecycle methods seem to be the place to do this, but when I use them it either does nothing (render is called before state change), or it goes on an endless loop.
Here is my simplified code:
#connect((store) => {
return {
collections : store.collections.collections
}
}, {
fetchCollections
})
export default class CollectionPane extends Component {
constructor(props){
super(props);
this.state = {
term : null
}
}
componentWillMount(){
this.handleFetchCollections(this.state.term); // This loads initial ok
}
componentDidUpdate(){
// this.handleFetchCollections(this.state.term); // This causes loop
}
handleFetchCollections(props, sort){
log('Fetching...', 'green');
this.props.fetchCollections(props, sort);
}
onSearch(){
const term = "test example";
this.setState({
term
})
}
render(){
const { collections } = this.props;
return (
<div>
<a style={{ display: "block", padding: "1em" }} onClick={this.onSearch.bind(this)}>Search</a>
<CollectionList collections={collections} />
</div>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
You should compare your new state with the previous state before performing fetch:
componentDidUpdate(prevProps, prevState) {
if (prevState.term !== this.state.term) {
this.handleFetchCollections(this.state.term);
}
}
With react-redux you should change your state through actions and reducers. Then you can use async actions as described here: http://redux.js.org/docs/advanced/AsyncActions.html

Resources