Multiple subreddits #3
@@ -11,8 +11,6 @@ function App() {
|
||||
<div className="content-container">
|
||||
|
||||
<div className="feed">
|
||||
{/* To do: import posts from post directory */}
|
||||
{/* Map post data onto individual post cards, handle undefined values */}
|
||||
<Feed />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,51 +1,106 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import postsSlice, { fetchBySub, updatePosts, selectPosts } from "./postsSlice";
|
||||
import { fetchBySub } from "./postsSlice";
|
||||
import { selectAllSubs } from "../reddit/redditSlice";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { v4 } from "uuid";
|
||||
import Post from "./Post";
|
||||
|
||||
export default function Feed() {
|
||||
const [feed, setFeed] = useState(null);
|
||||
const [endpoints, setEndpoints] = useState(null); // Expects to receive an array of endpoints from which to fetch the posts
|
||||
const [data, setData] = useState(null); // Receives data from getPosts and them maps it onto Post components
|
||||
const [feed, setFeed] = useState(null); // Expects to receive an array of Post components mapped with data from fetchBySub
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const subs = useSelector(selectAllSubs); // Selects subreddits from redditSlice
|
||||
const subArray = Object.values(subs); // Places the values within an array
|
||||
|
||||
|
||||
|
||||
useEffect(() => { // this useEffect loop pulls the endpoints from the selected subreddits and stores them as an array in "endpoints"
|
||||
const prepareData = () => {
|
||||
let myEndpoints = [];
|
||||
for (let sub of subArray) {
|
||||
myEndpoints.push(sub.access);
|
||||
}
|
||||
setEndpoints(myEndpoints);
|
||||
}
|
||||
|
||||
if (subArray) {
|
||||
prepareData();
|
||||
}
|
||||
}, [setEndpoints]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => { // once this is done, this loop pulls posts from each endpoint
|
||||
let isActive = true;
|
||||
|
||||
const getPosts = async() => {
|
||||
let myPosts = await dispatch(fetchBySub('https://www.reddit.com/r/cats.json'));
|
||||
myPosts = myPosts.payload;
|
||||
console.log(myPosts);
|
||||
|
||||
if (typeof myPosts === 'object' && isActive) {
|
||||
let newFeed = [];
|
||||
for (let post of myPosts) {
|
||||
newFeed.push(
|
||||
<Post
|
||||
title={post.data.title}
|
||||
author={post.data.author}
|
||||
subreddit={post.data.subreddit}
|
||||
ups={post.data.ups}
|
||||
comments={post.data.num_comments}
|
||||
time={post.data.created_utc}
|
||||
id={v4()}
|
||||
media={post.data.post_hint === 'image' && post.data.url}
|
||||
permalink={post.data.permalink}
|
||||
selftext={post.data.selftext}
|
||||
video={post.data.is_video ? post.data.media.reddit_video.fallback_url : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
setFeed(newFeed);
|
||||
const getPosts = async(arr) => {
|
||||
if (endpoints) {
|
||||
const mappedResults = arr.map(each => dispatch(fetchBySub(each)));
|
||||
return Promise.all([
|
||||
...mappedResults
|
||||
]).then((results) => setData(results)); // data is now set to an object (returned promise)
|
||||
}
|
||||
};
|
||||
getPosts();
|
||||
}
|
||||
|
||||
getPosts(endpoints); // calls this logic on the current value of endpoints
|
||||
|
||||
return () => {
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
}, [dispatch])
|
||||
}, [dispatch, setData, endpoints]);
|
||||
|
||||
useEffect(() => {
|
||||
let isActive = true;
|
||||
|
||||
const mapPosts = () => { // the logic for extracting post data from the promise responses
|
||||
if (data) {
|
||||
let allPosts = [];
|
||||
for (let each of data) {
|
||||
allPosts.push(each.payload);
|
||||
}
|
||||
|
||||
let extractedPosts = []; // logic for flattening arrays and reducing complex variable to a layer of post objects
|
||||
for (let each of allPosts) {
|
||||
for (let indiv of each) {
|
||||
extractedPosts.push(indiv);
|
||||
}
|
||||
};
|
||||
|
||||
extractedPosts = extractedPosts.sort((x,y) => x.created_utc > y.created_utc); // sorts posts by sort time (to do: fix this)
|
||||
|
||||
let newFeed = extractedPosts.map((post) => {
|
||||
return (
|
||||
<Post
|
||||
title={post.data.title} // each variable passed in as props
|
||||
author={post.data.author}
|
||||
subreddit={post.data.subreddit}
|
||||
ups={post.data.ups}
|
||||
comments={post.data.num_comments}
|
||||
time={post.data.created_utc}
|
||||
key={v4()}
|
||||
media={post.data.post_hint === 'image' && post.url}
|
||||
permalink={post.data.permalink}
|
||||
selftext={post.data.selftext}
|
||||
video={post.data.is_video ? post.data.media.reddit_video.fallback_url : null}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
setFeed(newFeed); // stores this array of posts in feed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mapPosts();
|
||||
|
||||
return () => {
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
}, [data, setFeed])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -27,4 +27,8 @@ img, video {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.post-text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -1,29 +1,44 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import './Post.css';
|
||||
|
||||
export default function Post({title,author,subreddit,ups,comments,time,id,media,permalink,selftext,video}) {
|
||||
export default function Post({title,author,subreddit,ups,comments,time,key,media,permalink,selftext,video}) {
|
||||
const limit = 300;
|
||||
const [body, setBody] = useState(selftext);
|
||||
|
||||
useEffect(() => {
|
||||
if (selftext.length > limit) {
|
||||
setBody(selftext.substring(0,limit) + '...');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
})
|
||||
// useEffect(() => {
|
||||
// if (selftext.length === 0) { // in the case that the post body is empty, it does not include an ellipsis on mouseout
|
||||
// return;
|
||||
// } else if (selftext.length > limit) {
|
||||
// setBody(selftext.substring(0,limit) + '...');
|
||||
// } else {
|
||||
// return;
|
||||
// }
|
||||
// }, [setBody, selftext]);
|
||||
|
||||
// const handleHover = () => {
|
||||
// setBody(selftext);
|
||||
// }
|
||||
|
||||
// const handleMouseOut = () => {
|
||||
// if (selftext.length === 0) { // ...and then doesn't add it in after a mouseover/out
|
||||
// return;
|
||||
// }
|
||||
|
||||
// setBody(selftext.substring(0,limit) + '...');
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="post-body">
|
||||
<a className="title" href={`https://reddit.com${permalink}`}>{title ? title : 'title'}</a>
|
||||
{media ? <img alt={title} src={media} /> : ''}
|
||||
{video ? <video controls type="video/mp4" src={video}></video> : ''}
|
||||
<p>{body}</p>
|
||||
{media ? <img alt={title} src={media} /> : ''}
|
||||
{video ? <video controls type="video/mp4" src={video}></video> : ''}
|
||||
<p className="post-text">{body}</p>
|
||||
<div className="post-metadata">
|
||||
<p>{subreddit ? 'r/' + subreddit : ''}</p>
|
||||
<p className="user">{author ? 'u/' + author : 'u/username'}</p>
|
||||
<p className="time-posted">{time ? time : ''}</p>
|
||||
<p className="num-comments">{comments ? comments : 'comments'}</p>
|
||||
<p className="time-posted">posted at {time ? time : '...?'}</p>
|
||||
<p className="num-comments">{comments ? comments : 'no'} comments</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -7,7 +7,7 @@ export const fetchBySub = createAsyncThunk(
|
||||
const myRequest = new Request(subreddit); // initializes request
|
||||
let response = await fetch(myRequest);
|
||||
let json = await response.json();
|
||||
let postsArray = json.data.children; // unpacks individual post objects from the subreddit JSON file, as an array
|
||||
let postsArray = json.data.children; // unpacks individual post objects from the subreddit JSON response, as an array
|
||||
return postsArray;
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
@@ -52,4 +52,5 @@ export const postsSlice = createSlice({
|
||||
export default postsSlice.reducer;
|
||||
export const selectPosts = state => state.postsSlice.posts;
|
||||
export const { filterPosts, updatePosts } = postsSlice.actions;
|
||||
// exports also includes fetchBySub (takes argument of a sub)
|
||||
// exports also includes fetchBySub (takes argument of a sub)
|
||||
// exports also includes fetchFromAll (takes argument of an array of subs)
|
||||
@@ -5,7 +5,7 @@ const urlBase = 'https://www.reddit.com/';
|
||||
export const redditSlice = createSlice({
|
||||
name: 'redditSlice',
|
||||
initialState: {
|
||||
subreddits: {
|
||||
subreddits: { // the initialized list of subreddits provided with the app
|
||||
'r/cats': {
|
||||
name: 'r/cats',
|
||||
access: `${urlBase}r/cats.json`,
|
||||
|
||||
25
src/features/sidebar/Sidebar.css
Normal file
25
src/features/sidebar/Sidebar.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.search-inactive {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 7rem;
|
||||
right: 17rem;
|
||||
min-height: 25rem;
|
||||
padding: 1.5rem;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.individual-sub {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.search-sub-input {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -1,12 +1,48 @@
|
||||
import React from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectAllSubs } from "../reddit/redditSlice";
|
||||
import './Sidebar.css';
|
||||
|
||||
export default function Sidebar({isCollapsed}) {
|
||||
|
||||
const allSubs = useSelector(selectAllSubs);
|
||||
let arrayOfSubs = Object.keys(allSubs);
|
||||
|
||||
const [subs, setSubs] = useState(arrayOfSubs);
|
||||
const [searchSubs, setSearchSubs] = useState('');
|
||||
|
||||
const searchWindowStyle = useRef('search-inactive'); // this ref allows us to access and modify the class of the search window container from another part of the render function
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
if (e.target.value) { // this logic locally stores the search term in searchSubs,
|
||||
searchWindowStyle.current = 'search-active'; // and will dispatch a search action from the reddit slice
|
||||
setSearchSubs(e.target.value); // based on the provided term
|
||||
} else if (e.target.value === '') {
|
||||
searchWindowStyle.current = 'search-inactive';
|
||||
setSearchSubs('');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={isCollapsed ? 'sidebar-hidden' : 'sidebar'}>
|
||||
<p>Hard coded subreddit</p>
|
||||
<p>Hard coded subreddit</p>
|
||||
<p>Hard coded subreddit</p>
|
||||
<p>Hard coded subreddit</p>
|
||||
<>
|
||||
<div className={isCollapsed ? 'sidebar-hidden' : 'sidebar'}> {/* Is collapsed is passed from the parent component, and is mutable within the navbar */}
|
||||
{
|
||||
subs.map((sub) => { // Maps each sub to its own line within the sidebar, along with a button that toggles its "isSelected" property
|
||||
return (
|
||||
<div className="individual-sub">
|
||||
<button className="toggle-sub-active">X</button> {/* This button will dispatch an action to change the state of this specific subreddit */}
|
||||
<p>{sub}</p>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
<input className="search-sub-input" type="text" onChange={handleChange} placeholder="Search Subs to Add"></input>
|
||||
</div>
|
||||
<div className={searchWindowStyle.current}>
|
||||
<h2>Search Results for: {searchSubs}</h2>
|
||||
<p>(results here)</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user