How to display loaded posts with React and Redux - reactjs

I'm new to React and Redux, and im trying to load posts from the WordPress REST API, and display them in my React App.
I'm using this as an example: https://github.com/jackreichert/a-wp-react-redux-theme
My action to get the posts look like this:
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export function fetchPosts(post_type = 'posts') {
return function (dispatch) {
axios.get(`http://localhost:8080/wp-json/wp/v2/${post_type}`)
.then(response => {
dispatch({
type: FETCH_POSTS,
payload: response.data
});
});
}
}
It's passed to the reducer, which looks like this:
import { FETCH_POSTS } from '../actions';
export default (state = [], action) => {
switch (action.type) {
case FETCH_POSTS:
return action.payload;
default :
state = '';
break;
}
return state;
}
And (though it's only one reducer) I'm combining it, because there are more reducers to follow (just like in the example), and then I'm storing it. Also pretty much the same way as the example.
Im loading everything in my Home.jsx file, which looks like this right now:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import { Link } from 'react-router-dom';
import Header from '../Header';
import {fetchPosts} from '../../actions/';
class Home extends Component{
componentDidMount(){
document.title = 'Test';
}
componentWillMount() {
this.getPosts(this.props, true);
}
componentWillReceiveProps(nextProps) {
this.getPosts(nextProps);
}
getPosts(props, willMount = false) {
if (willMount) {
this.props.fetchPosts();
}
}
render() {
return(
<div>
<Header/>
<main>
<h1>Home</h1>
</main>
</div>
)
}
}
function mapStateToProps({posts}) {
return {posts};
}
export default connect(mapStateToProps, {fetchPosts})(Home);
I think my code above is right. Redux can also 'find' my posts, and logs in my console:
action FETCH_POSTS # 11:11:56.393
prev state Object {posts: ""}
action Object {type: "FETCH_POSTS", payload: Array(2)}
next state Object {posts: Array(2)}
Now I want to know: How can I simply display the posts which are loaded by Redux, in my Home.jsx file.
And after that: How can I configure the route and data, to go to a single post (but that will come later, for now I only want to know an easy but right way how to display the posts)

I have no idea about the structure of your Post object. But it should be something like this.
renderPosts() {
return _.map(this.props.posts, post => {
return (
<li className="list-group-item" key={post.id}>
{post.title}
</li>
);
});
}
Then in your render method merely call it like this.
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
Also notice that I am using map helper from lodash library here. So you need to have following import statement in your component.
import _ from 'lodash';
Hope this helps. Happy coding !

If you implemented you combineReducers correctly then mapStateToPosts should be called with set of posts when it is changed.
import posts from './posts_reducer';
combineReducers({ posts });
To render posts in your Home component you need to modify the render function, see raw implementation below:
render() {
let postsElements = this.props.posts.map(p => {
return <div>p.title</div>
});
return(
<div>
<Header/>
<main>
<h1>Home</h1>
</main>
<div>
{postsElements}
</div>
</div>
)
}
You need to use this.props.posts to access your posts because react-redux maps properties from state to props object of the component using mapStateToProps function that is provided as a 1st argument when calling connect function.
To improve this you can implement you Blog component and replace div with that.
Also, review how it is implemented in the example you provided https://github.com/jackreichert/a-wp-react-redux-theme/blob/master/src/components/main.js in this file posts are rendered.

import React, { Component } from 'react';
import { connect } from 'react-redux'; // glue between react and redux
import Parser from 'html-react-parser';
import { LinkContainer } from 'react-router-bootstrap';
import { ListGroup, ListGroupItem, Image } from 'react-bootstrap';
class BlogPosts extends Component {
render() {
if (!this.props.posts) {
return (<div></div>);
}
return (
<div className="blog-items" >
<ListGroup>
{this.renderBlogPosts()}
</ListGroup>
</div>
)
};
renderBlogPosts() {
if (this.props.posts.posts) { //v1
const posts = this.props.posts.posts; // DRY
return posts.map((post, index) => {
const excerptText = JSON.stringify(post.excerpt);
const excerpt = excerptText.substring(1, excerptText.length-3);
return (
<LinkContainer className="blog-posts-link" key={post.ID} to={ "/blog/post/" + post.ID + '/'}>
{ this.getHtml(
post.ID,
(post.featured_image) ? post.featured_image : '',
post.title,
excerpt
)}
</LinkContainer>
);
});
};
const posts = this.props.posts; // DRY
return posts.map((post, index) => { // v2
return (
<div key={post.id}>
{this.getHtml(
post.id,
(post._embedded['wp:featuredmedia'])
? post._embedded['wp:featuredmedia'][0].media_details.sizes.thumbnail.source_url : '',
post.title.rendered,
post.excerpt.rendered
)}
</div>
);
});
};
getHtml(id, imageSrc, title, excerpt) {
return (
<ListGroupItem className="blog-posts-list-item">
{this.getImage(imageSrc, title)}
<h2 className="blog-posts-title">{ Parser(title) }</h2>
<div className="blog-posts-excerpt">{Parser(excerpt)}</div>
</ListGroupItem>
);
};
getImage(imageSrc, title) {
return (imageSrc === "")
? (<div></div>)
: (<div className="blog-posts-image-div">
<Image className="blog-posts-image" src={imageSrc} alt={title} />
</div>
);
};
};
const mapStateToProps = (state) => ({ posts: state.posts });
export default connect(mapStateToProps) (BlogPosts);

Related

How to get a detailed overview of some articles

import React from 'react';
import axios from 'axios';
import { Card } from 'antd';
class ArticleDetail extends React.Component {
state = {
article: {}
}
componentDidMount() {
const articleID = this.props.match.params.articleID;
axios.get(`http://127.0.0.1:8000/api/${articleID}`)
.then(res => {
this.setState({
article: res.data
});
})
}
render() {
return (
<div>
<Card title={this.state.article.title}>
<p>{this.state.article.content}</p>
</Card>
</div>
)
}
}
export default ArticleDetail;
//Make sure on your article.js file under title link you use ` instead of '
<List.Item.Meta
avatar={<Avatar src={item.avatar} />}
title={<a href={`/${item.id}`}>{item.title}</a>} //here
description={item.description}
/>
{item.content}
</List.Item>`
//And under your routes.js file make sure the (article Id) is the same as the one you used on the Article-detail-view
exact path='/:articleID' component={ArticleDetail}

Component does not rerender when context changes

I've been playing around with the react context api and I'm just not getting why it's not working.
I have a component with a container that should show or hide depending on a valuer stored in context.
This is the component:
import React, { useContext } from 'react';
import ResultsContext from '../../context/results/resultsContext';
const ResultsPanelContainer = () => {
const resultsContext = useContext(ResultsContext);
const { showResults } = resultsContext;
console.log('showResults in ResultsPanelConatiner: ', showResults);
return (
<div
className='container-fluid panel'
style={{ display: showResults ? 'block' : 'none' }}
>
<div className='container'>
<div className='row'>
<div className='col'>
<h1 className='display-4'>Results.Panel.js</h1>
</div>
</div>
</div>
</div>
);
};
export default ResultsPanelContainer;
For completeness, the context is divided up into three sections, the call to the context itself, a 'state' file and a reducer. These are displayed below:
resultsContext.js
import { createContext } from 'react';
const resultsContext = createContext();
export default resultsContext;
ResultsState.js
import React, { useReducer } from 'react';
// import axios from 'axios';
import ResultsContext from './resultsContext';
import ResultsReducer from './resultsReducer';
import { UPDATE_SHOW_RESULTS } from '../types';
const ResultsState = (props) => {
const initialState = {
showResults: false,
};
const [state, dispatch] = useReducer(ResultsReducer, initialState);
const updateShowResults = (data) => {
console.log('updateShowResults - ', data);
dispatch({
type: UPDATE_SHOW_RESULTS,
payload: data,
});
};
return (
<ResultsContext.Provider
value={{
showResults: state.showResults,
updateShowResults,
}}
>
{props.children}
</ResultsContext.Provider>
);
};
export default ResultsState;
resultsReducer.js
import { UPDATE_SHOW_RESULTS } from '../types';
export default (state, action) => {
switch (action.type) {
case UPDATE_SHOW_RESULTS:
return {
...state,
showResults: action.payload,
};
default:
return state;
}
};
The change is triggered by a button click in a separate component and this does trigger an update in the context as shown when you log it to the console. However, the component is not rerendering.
I understand from reading various answers on here that changing context doesn't trigger a rerender of all child components in the same way that setState does. However, the component displaying this is calling the context directly so as far as I can see the rerender should take effect.
Am I missing something glaringly obvious?
Thanks in advance.
Stef
Forget the above... I'm an idiot - wrapped the two separate parts of the app in two separate instances of ResultsState which weren't communicating. Did this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
</ResultsState>
<ResultsState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Instead of this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Hope this is useful for someone else...

Dealing with bloated components in React

Can someone provide a clean example, or article dealing with bloated React code?
I have a HOC function on the main App.js file but I don't know how to "redeclare" the extended component to export. Then, the other components are not sharing the render. I already have Redux on one of the components called Counter. That is working.
App.js
import { connect } from 'react-redux'
import Counter from './components/Counter';
import Actions from './components/Actions';
function mapStateToProps(state) {
return {
countValue: state.count
};
}
//Action
var increaseAction = { type:"increase" }
var decreaseAction = { type:"decrease" }
// Map Redux actions to component props.
function mapDispachToProps(dispach) {
return {
// etc..
}
// The HOC
var connectedComponent = connect (
mapStateToProps,
mapDispachToProps
)(Counter);
export default connectedComponent;
Actions.js:
import React, {Component} from 'react';
import App from '../App';
import Counter from './Components/Counter';
import './App.css';
class Actions extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
fetch(
"/posts"
)
.then(response => response.json())
.then(data => this.setState({ response: data }))
}
render() {
return (
<div className="App">
<header className="App-header">
{/* <img src={logo} className="App-logo" alt="logo" /> */}
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <div>
<ul>
<App>
`<Counter>`
<li>{resIndex.title} </li> // <- is this not bloated?
<li>{resIndex.author} </li>
</Counter>
</App>
</ul>
</div>
)}
</header>
</div>
)
}
}
export default Actions;

How can I tell my React app which component I want to update in Firestore?

I am working on a React app that renders components from an array of objects stored in a Firestore document. It is essentially a social media app, and I am trying to develop the 'Like' button to update the 'likes' property on the firestore document for each user post. The problem I am encountering is that since the 'LiveFeed' renders all of the posts using .map, I am having trouble telling my function which post I want to update.
I am able to use the firestore.update method in my action to update all of the likes property on my "posts" collection, but I am not able to isolate a single one that a user would click 'like' on.
Here is the POST component which renders all of the posts from the post collection in firestore using a map method in the parent component
import React from 'react';
import styles from '../../scss/styles.scss';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {newLike} from '../../actions/postActions';
import moment from 'moment';
function Post(props){
const { post, newLike } = props;
console.log(post) //return
function handleNewLike(post){
const currentpost = post;
newLike(post);
}
return(
<div className="container section post">
<div className="card post-card">
<span className="card-title"><h5>{post.authorFirstName} {post.authorLastName}</h5></span>
<div className="card-content center">
<p className="black-text">{post.content}</p>
</div>
<div className="card-action">
<button className="waves-effect waves-light btn" onClick={handleNewLike}>LIKE</button>
<p>likes: {post.likes}</p>
</div>
<div>
<p className="grey-text center">{moment(post.createdAt.toDate()).calendar()}</p>
</div>
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return{
newLike: (post) => dispatch(newLike(post))
}
}
export default connect(null, mapDispatchToProps)(Post);
Here is the action where I want to update a unique Post likes property (the first function is what creates a post
import * as types from './../constants/ActionTypes';
export const createPost = (post) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
console.log(post)
const firestore = getFirestore();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth;
firestore.collection('posts').add({
...post,
authorFirstName: profile.firstName,
authorLastName: profile.lastName,
authorId: authorId,
createdAt: new Date()
}).then(()=> {
dispatch({type: types.CREATE_POST, post});
}).catch((err) => {
dispatch({ type: types.CREATE_POST_ERROR, err});
});
};
};
export const newLike = (post) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
console.log(post)
const firestore = getFirestore();
firestore.collection('posts').doc(post.id).update({
likes: +1
}).then(()=> {
dispatch({type: types.NEW_LIKE, post});
}).catch((err) => {
dispatch({ type: types.NEW_LIKE_ERROR, err});
});
};
};
Here is the component that maps the array of Posts to the DOM
import React from 'react';
import Post from './Post';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
function LiveFeed(props){
const { posts } = props;
return(
<div className="liveFeed">
<div>
{posts && posts.map(post => {
return (
<Link to={'/post/' + post.id} key ={ post.id }>
<Post post={post}/>
</Link>
)
})}
</div>
</div>
);
}
export default LiveFeed;
You could use React.memo for your Post component. And this will tell React to update component only if props have been changed:
const Post = React.memo(function Post(props) {
/* only rerenders if props change */
});
I was overlooking a simple mistake, and not passing the correct argument through the function

Why am I getting a blank screen while following React JS?

I am following the current tutorial:
Youtube tutorial at 12:51 mins.
I expect to see bunch of posts on my screen but my screen remains blank.
It appears I have followed everything told in the tutorial.
import React, { Component } from 'react';
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentWillMount() {
fetch('https://jsonplaceholder.typicode.posts')
.then(res => res.json())
.then(data => this.setState({posts: data}))
}
render() {
const postItems = this.state.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<h1>Posts</h1>
{ postItems }
</div>
);
}
}
export default Posts;
and
import React, { Component } from 'react';
import './App.css';
import Posts from './components/Posts'
class App extends Component {
render() {
return (
<div className="App">
<Posts />
</div>
);
}
}
export default App;
My browser screen remains blank and I do not see any errors on console.
What am I missing ?
Don't know about the tutorial but it looks outdated...
Here is your App.js (parent component):
import React, { Component } from 'react';
import Posts from './Posts';
export default class App extends Component {
state = { posts: [] };
//fetch the posts and store them in the state
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(posts => this.setState({ posts }))
.catch(error => console.log(error));
}
render() {
return (
<div>
{/* pass the state (posts) as props to Posts */}
<Posts posts={this.state.posts} />
</div>
);
}
}
Here is your Posts.js:
import React from 'react';
// No need for a class based comp
// destructure the props and you have all your data
const Posts = ({ posts }) => (
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<hr />
</div>
))}
</div>
);
export default Posts;
Live Demo: https://jsfiddle.net/ctszdue9/7/
Try putting side effect/ api call inside
componentDidMount() { }
Change URL
https://jsonplaceholder.typicode.posts/
TO
https://jsonplaceholder.typicode.com/posts

Resources