Multiple subreddits #3

Merged
innocuous-symmetry merged 10 commits from multiple-subreddits into master 2022-01-27 23:19:57 +00:00
8 changed files with 189 additions and 55 deletions

View File

@@ -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>

View File

@@ -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 (
<>

View File

@@ -27,4 +27,8 @@ img, video {
display: inline-flex;
flex-direction: row;
justify-content: space-between;
}
.post-text {
font-size: 1.2rem;
}

View File

@@ -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>
</>

View File

@@ -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)

View File

@@ -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`,

View 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;
}

View File

@@ -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>
</>
);
}