Angelos Chalaris
Angelos Chalaris

Reputation: 6707

State change does not cause render

I have been trying for a little while to create an application with React & Redux. On application startup, the <App> component will fetch() some data from a JSON API, then call the Redux reducer to set the state with the received data. This works so far and I see the rendered data from the JSON pretty soon after loading the page.

What I am now trying to do is use a <button> to select a specific post from my list of posts, utilizing the reducer and an action. I have tried several things, however either the state of the component remains unchanged or the returned state is the type of the action for some odd reason. Can anyone explain what I'm doing wrong and how to fix it?

actions.js

export const select = () => {
  return { type: 'SELECT' }
};
export const fetchPosts = (posts) => {
  return { type: 'FETCH', posts }
};

reducer.js

const reducer = (state = { data: [], selected: []}, action) => {
  let posts = { data: [], selected: []};
  switch (action.type) {
    case 'FETCH':
      Object.assign(posts.data, action.posts);
      return posts;
    case 'SELECT':
      posts.data = [...state.data];
      posts.selected = ["autumn-eu-dolor-ac-velit"];
      return posts;
    default:
      return state;
  }
};

export default reducer;

App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { select, fetchPosts } from './actions';

// App component
class App extends Component {
  constructor(props){
    super(props);
    this.state = { posts : this.props.posts }
  }
    // Fetch data from JSON file
    componentDidMount() {
    let _this = this;
    fetch("https://jsonbin.io/b/59f721644ef213575c9f6531")
    .then( response => response.json())
    .then( data => {
      let posts = { data: data, selected: [] };
      _this.setState({ posts });
      _this.props.fetchPosts(posts);
    });
  }
  // Render
    render() {
    let posts = '';
    if(this.state.posts && this.state.posts.data){
      if (this.state.posts.selected.length){
        posts = this.state.posts.data.filter(post => {if (this.state.posts.selected.includes(post.id)) return true; return false;}).map(post => {return <p key={"post_"+post.id}>{post.content}</p>;});
      }
      else {
        posts = this.state.posts.data.map(post => {return <p key={"post_"+post.id}>{post.content}</p>;});
      }
    }
    return (
      <div>
        <button onClick = {() => this.props.select()}>Select post</button>
        {posts}
      </div>
    );
  }
}

// Map state to props
const mapStateToProps = (state) =>{
  return { posts: state };
};


// Connect App
export default connect(mapStateToProps, {select, fetchPosts})(App);

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'));

As far as I can tell, the code I have currently does not set the state of the <App/> component, thus it will not rerender. However, using setState() resulted in an object equal to the action passed, so I'm pretty sure something went really wrong there.

Upvotes: 1

Views: 164

Answers (3)

Danny Radden
Danny Radden

Reputation: 11

Your app is re-rendering when you click select, but when the happens its the props that updated, not the state. The way you are using redux here, there is no reason to use component state at all. Just switch all the this.state's in your app to this.props. Also, the object assign is unnecessary, you can just return action.posts;

Upvotes: 1

Neoares
Neoares

Reputation: 439

Since you're using Redux, I suggest you to use the props instead of component's state. Also, I changed your reducer to make it work. I added a default_state (which corresponds to your posts variable inside the reducer) and modified the code inside cases to make it work:

default_state = {
  data: [],
  selected: [],
}

const reducer = (state = default_state, action) => {
  switch (action.type) {
    case 'FETCH':
      return default_state;
    case 'SELECT':
      return {
        ...state,
        selected: ["autumn-eu-dolor-ac-velit"]
      }
    default:
      return state;
  }
};

export default reducer;

In order to handle your actions, use this.props.dispatch. I suggest you to look for some react + redux guides to see the workflow and then apply it to your code.

Upvotes: 2

Jorgen
Jorgen

Reputation: 76

You seem to be confused about what redux is, and how it relates to the react component, but the general idea is right. In your example you are saving your json results into the component's local state, which has nothing to do with redux, and only reading from that local state in your render method. I would read through the whole redux tutorial to see the examples provided there in detail, especially how the redux data passes from the redux store and into the component as props not as state. Drop this.state and this.setState entirely.

Take a look at reducers also, as you are not doing that correctly at the moment, you are resetting state completely.

Your SELECT action should also take in some identifier of the post, so that you can know which post was selected.

<button onClick = {() => this.props.select()}>Select post</button> This does nothing? It doesn't reference a post, how does it know which post to select? Have this button in each post, and pass in the post id to the select function.

Upvotes: 2

Related Questions