Rendering info from the Reddit API using React - javascript

I am trying to build a basic web app where a user can search for subreddits using the Reddit API.
However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
import React, { useState, useEffect } from "react";
import Amplify, { API, graphqlOperation } from "aws-amplify";
import aws_exports from "./aws-exports";
import { withAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
import awsconfig from "./aws-exports";
import './App.css'
import axios from 'axios'
import CharacterGrid from './components/CharacterGrid'
import SearchBar from './components/SearchBar'
Amplify.configure(awsconfig);
const App = () => {
const [items, setItems] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [query, setQuery] = useState('')
useEffect(() => {
const fetchItems = async () => {
setIsLoading(true)
const result = await axios(
`https://www.reddit.com/subreddits/search.json?q=${query}`
)
setItems(result.data)
setIsLoading(false)
}
fetchItems()
}, [query])
return (
<div className='container'>
<AmplifySignOut/>
<SearchBar style={{ marginTop: "6%" }} getQuery={(q) => setQuery(q)} />
<CharacterGrid isLoading={isLoading} items={items} />
</div>
)
}
export default withAuthenticator(App)
The child component CharacterGrid looks like this:
import React from 'react'
import CharacterItem from './CharacterItem'
const CharacterGrid = ({items, isLoading}) => {
return
isLoading
? (<h1>Loading ...</h1>)
: (
<section className='cards'>
<p>{items.data}</p> //this does not work
<p>{items.children}</p> //this does not work either
</section>
)
}
export default CharacterGrid
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters. Where am I going wrong? I have also tried converting to JSON, and mapping through the responses using .map() but I keep getting errors no matter what I try. Where am I going wrong?

However, while I can console.log the data I need from the API I cannot seem to figure out how to display it.
Because Reddit API returns an array of subreddits you should use map() function to iterate over the array and convert each item into React element.
items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name}
</li>
))
All I want to do is show the names of the subreddits that are returned from the API for the query string the user enters.
You need to inspect the data schema yourself and scrape the response properly.
Here is working example:
const { useState, useEffect } = React;
const App = () => {
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [query, setQuery] = useState("");
useEffect(() => {
setIsLoading(true);
if (query.length >= 3) {
axios(`https://www.reddit.com/subreddits/search.json?q=${query}`)
.then(response => setItems(response.data.data.children))
.catch(error => console.log("Error", error));
}
setIsLoading(false);
}, [query]);
return (
<div>
<input type="text" value={query} onChange={e => setQuery(e.target.value)} />
<CharacterGrid items={items} isLoading={isLoading} />
</div>
);
}
const CharacterGrid = ({ items, isLoading }) => {
return isLoading ? (
<h1>Loading ...</h1>
) : (
<ul>
{items.map(i => (
<li key={i.data.display_name_prefixed}>
{i.data.display_name} ({i.data.display_name_prefixed})
</li>
))}
</ul>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<div id="root"></div>

In order to loop through the elements and e.g: print the title, you could do the following.
items.data.children.map(child => <p> child.data.title </p>)
According to the response provided by reddit,
reddit response

Related

React, problem with my Todo list app, my search engine only finds notes on the page I'm on

Hello I am making an application to practice React, my notes app has a pagination which works perfectly, the problem is in the search engine, which only looks for notes from the page I'm on, for example, if I'm on page 2 and I look for a note on page 2, it shows it, however if the note is on a different page, it doesn't show it, it doesn't find it.
I know where the problem is but I'm not sure how to solve it, I'm a bit new to React and I was asking for your help.
I was able to do my pagination with the package react-paginate here is the documentation https://www.npmjs.com/package/react-paginate
My code:
Component App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination
data={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
searchText={searchText}
/>
</div>
</div>
)
}
export default App;
Component Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
import NoteList from './NoteList';
import { MdSkipPrevious, MdSkipNext } from 'react-icons/md';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 9;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<div className="pagination-wrapper">
<ReactPaginate
breakLabel="..."
nextLabel={<MdSkipNext
className='icons'
/>}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<MdSkipPrevious
className='icons'
/>}
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</div>
</>
);
}
export default Pagination;
Component NoteList.js
import React from 'react'
import Note from './Note'
import '../styles/NoteList.css'
import AddNote from './AddNote'
const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => {
return (
<>
<div className="add-notes-wrapper">
<AddNote
handleAddNote={handleAddNote}
/>
</div>
<div className='notes-list'>
{notes.map((note =>
<Note
key={note.id}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</>
)
}
export default NoteList;
Component Search.js
//import React, { useState } from 'react'
import {MdSearch, MdAdd} from 'react-icons/md'
import '../styles/Search.css'
const Search = ({ handleSearchNote, setShowNote }) => {
const handleShowAddNote = () => {
if (setShowNote){
let addNote = document.querySelector('.new');
addNote.classList.add('wobble-horizontal-top')
addNote.style.display='flex';
document.querySelector('.notes-list').style.display='none';
document.querySelector('.pagination').style.display='none';
}
}
return (
<div className='search'>
<div className="input-wrapper">
<MdSearch
className='icon search-icon'
/>
<input
type="text"
placeholder='What note are you looking for?'
onChange={(event) => handleSearchNote(event.target.value) }
/>
</div>
<div className="btn-wrapper-search">
<button
className='btn-addNote'
onClick={handleShowAddNote}>
Nueva Nota
</button>
<MdAdd
className='icon add-icon'
/>
</div>
</div>
)
}
export default Search
The problem is in the component Pagination.js because I'm filtering the notes on each page with the currentItems variable, if I did it with the data variable it would work, but then it would show all the notes, and I don't want that, I currently want to show 9 notes per page.
greetings and thanks in advance.
Edit:
#Newbie I'm doing what you said, but I don't know if you mean this, in my Pagination.js component I did:
useEffect(() => {
const filterNotes=data.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)
setItemOffset(0);
}, [data, searchText])
It doesn't work, do I have to pass a prop to my components additionally?
greetings.
As I suggested to you, search all the notes with searchText in your App.js and pass the results into the Pagination component and it will solve your problem.
Codesandbox: https://codesandbox.io/s/youthful-thompson-xugs0c
Edit
All changes are as per what we talked about in the email.
Codesandbox: https://codesandbox.io/s/green-fast-3k76wx
Search and pagination do not play well together, one of the common solutions is to jump to page 1 each time the filter term changes.
So use an useEffect on searchText to filter data and reset itemOffset to 0, then redo pagination as if the data changed.
The user will jump to page 1 at each keystroke of the search, then he can navigate pages (if there are more than one). This will lead to a less confusing UX.

Error on fetch data, function instead of value

Hi guys I am trying to fetch data using get, and I want the data to be displayed after I click on the button, as a normal crud
I am new in programming if there is someone that can help me. I APPRECIATE THANKS
everything in my backend is ok, I try in postman and console.log is everything good. My problem is only in this part thanks
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
useEffect(()=> {
const todosUsers = async () => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}
todosUsers()
},[])
return (
<>
<button onClick=
{users.map((users) => (
<h1>{users.username}</h1>
))}></button>
</>
)
}
export default Usuarios;
one solution would be to keep a seperate variable to see if button is clicked as S.Singh mentioned
const [users, setUsers] = useState([]);
const [clicked, setClicked] = useState(false);
and in your component you can set the clicked variable to true on click
return (
<>
<button onClick={() => setClicked(true)}></button> //setting clicked true onclick
{clicked && users.map((users) => ( //only renders when click is true
<h1>{users.username}</h1>
))}
</>
)
if you want to hide and show alternatively on click just change the line to onClick={() => setClicked(!clicked)}
codesandbox demo
Move
{users.map((users) => (
<h1>{users.username}</h1>
))
}
from onClick definition and place it in an a markup where you want it to render. Define onClick hendler, which will be setting data from an API to a state
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchOnClick = async () => {
await axios.get("/users");
console.log(res)
setUsers(res.data);
}
return (
<>
<button onClick={fetchOnClick}>
Fetch
</button>
<div>
{users.map((users) => (
<h1>{users.username}</h1>
))}
</div>
</>
)
}
OR
If you want to fetch the data inside useEffect hook, like you did in your example
import React, { useState, useEffect } from "react";
function Usuarios() {
const [users, setUsers] = useState([]);
const [isContainerShowed, setIsContainerShowed] = useState(false)
useEffect(() => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}, [])
const displayContainer = () => setIsContainerShowed(true);
return (
<>
<button onClick={displayContainer}>
Fetch
</button>
<div style={{display: isContainerShowed ? "block" : "none"}}>
{users.map(users => <h1>{users.username}</h1>)}
</div>
</>
)
}
use Effect hook runs every time the component is rendered but as I understand, you want to display users when the button is clicked, so you can fetch users once the button is clicked and display them using the code below
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchUsers= async () => {
const res= await axios.get("/users");
setUsers(res.data);
}
return (
<>
<button onClick={() => fetchUsers()}></button>
{
users.length && users.map(user => <h2>{user.username}</h2>)
}
</>
)
}

useFetch custom hook not working properly

I am working on react website.
I have created one custom data fetching hook 'usePostFetch' as follows:
import React, { useState, useEffect } from "react";
//axios
import axios from "axios";
const usePostFetch = () => {
const [postData, setPostData] = useState([]);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getData = async () => {
setIsLoading(true);
try {
const res = await axios.get("http://localhost:8000/Sell");
const data = await res.data;
setPostData(data);
setIsLoading(false);
} catch (error) {
console.log("Error from fetch: " + error);
setError(error.message);
setIsLoading(false);
}
};
getData();
}, []);
const values = [
...new Set(
postData.map((post) => {
return post.productType;
})
),
];
return { postData, values, error, isLoading };
};
export default usePostFetch;
I have a product page that renders when I click any of the links on the home page with a link "/product/:productId".productId is the id of clicked link product.
Product Page:
import React, { useEffect, useState } from "react";
//react router dom
import { useParams } from "react-router";
//Hooks
import usePostFetch from "../../Hooks/usePostFetch";
//styles
import { Wrapper, Info, Discription } from "./Product.styles";
//Server
const Server = "http://localhost:8000";
const Product = () => {
const { productId } = useParams();
const { postData, isLoading, error } = usePostFetch();
const [data, setData] = useState({});
console.log(postData, isLoading, error);
useEffect( () => {
const fetchData = async () => {
var value = await postData.filter((post) => {
return post._id === productId;
});
console.log(value);
setData(value);
};
fetchData();
}, [postData]);
return (
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
</Info>
</Wrapper>
);
};
export default Product;
But when I go to that link I got data in console like this:
Because of these empty arrays, I got errors like this:
What can I do or what is wrong with my code?
It appears you are reading state that doesn't exist yet. The initial data state is an empty object:
const [data, setData] = useState({});
And on the initial render you are attempting to read from a 0 property, which OFC is undefined still.
data[0] --> OK, undefined
data[0].productName --> NOT OK, throws error trying to access from undefined
You can conditionally render the data content when you know it's populated:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
{data[0] && (
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
)
</Info>
</Wrapper>
Or you can just use the Optional Chaining operator to defend against null/undefined property accesses:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0]?.productImage}`}
alt={`${data[0]?.productName}`}
/>
<div className="data">
<h1>{data[0]?.productName}</h1>
<h3>{data[0]?.productPrice}</h3>
</div>
</Info>
</Wrapper>
It also seems that you are really expecting data to be an array, so you will want your initial state to maintain a state/type invariant, so it should also be declared as an array.
const [data, setData] = useState([]);

Trouble displaying the data from an api

So, this a weird problem. Yesterday everything was working fine, I could see the category object displayed, I could switch between categories, but today I wanted to continue the project, but the data is not being displayed, I can not even see the object in the console. What could be the problem, is it in the Fetch I would really really appreciate the help
import React, {useState, useEffect} from 'react'
import URL from '../utilis/URL'
const BookContext = React.createContext();
export default function BooksProvider({ children }) {
const [data, setData] = useState([])
const [currentSelectedCategory, setCurrentSelectedCategory] = useState([]);
const handleSelectCategory = (category) => {
setCurrentSelectedCategory(data[category]);
};
const fetchData = async () => {
const response = await fetch(URL);
const result = await response.json();
console.log(data.result)
setCurrentSelectedCategory(result[Object.keys(result)[0]]);
setData(data);
};
useEffect(()=>{
fetchData();
},[])
return (
<BookContext.Provider value={{ data, handleSelectCategory, setCurrentSelectedCategory, currentSelectedCategory }}>
{children}
</BookContext.Provider>
);
}
export {BookContext, BooksProvider}
import React,{useState, useEffect} from 'react'
import './Home.css'
import Books from './Books'
import { BookContext } from "../../context/books";
const Home = () => {
const {data, handleSelectCategory, currentSelectedCategory } =React.useContext(BookContext)
return (
<div className='books__container' >
<h1 className='categories'>Categories</h1>
{Object.keys(data).map((key, index)=>{
let books = data[key];
return (
<>
<span key={key} onClick={() => handleSelectCategory(key)} className='books__list' >
{books[0].category}
</span>
</>
);})}
<Books category={currentSelectedCategory} />
</div>
)
}
export default Home
This doesn't do anything:
console.log(data.result)
Because data is an empty array (and arrays have no result property anyway):
const [data, setData] = useState([])
And this doesn't do anything:
setData(data);
Because you're just updating the state to itself, which changes nothing and probably doesn't trigger any re-render. (Though even if it does, the render is mapping the elements of data which is still an empty array.)
You probably meant to set the data state object to the result from the API?:
setData(result);

How to make work search bar passing props through components?

I want to filter data and implement in search bar. In Hook/index.js component I am fetching and filtering data inside useEffects. Then I am passing props in App.js. Afterwards I have a Searchbar component, where I am listening to the input and here it must work. I get undefined.
Hook/index.js component
import React, { useState, useEffect } from "react";
import "./hook.scss";
export default () => {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const fetchData = () => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => setData(result))
.catch((err) => console.log("error"));
};
useEffect(() => {
const searchResult =
data && data.filter((item) => item.name.toLowerCase().includes(search));
setSearch(searchResult);
}, []);
useEffect(() => {
fetchData();
}, []);
return [data, error];
};
App.js
import React, { useState }from "react";
import Header from "./components/Header";
import SearchBar from "./components/SearchBar";
import Flag from "./components/Flag";
import useCountries from "./Hooks";
import CountryList from "./components/CountryList";
import "./App.scss";
export default function App() {
const [data, error] = useCountries();
return (
<div className="App">
<SearchBar /> // {/*this throws an error <SearchBar data={data}/> */}
<Header />
{data &&
data.map((country) => (
<div className="CountryList" key={country.name}>
<Flag flag={country.flag} />
<CountryList
population={country.population}
name={country.name}
region={country.region}
/>
{country.languages.map((language, languageIndex) => (
<CountryList key={languageIndex} language={language.name} />
))}
</div>
))}
<useCountries />
</div>
);
return [data, error]
}
Searchbar component
import React, {useState} from "react";
import "./SearchBar.scss";
export default function SearchBar({data}) {
const [search, setSearch] = useState("");
function handleChange(e) {
setSearch(e.target.value);
}
return (
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={data}
onChange={handleChange}
/>
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
</div>
);
};
You are sending data variable to input instead of search variable.
In JS filter return array and DOM cannot display array since it is not html or jsx so you need to convert array to jsx with map. with map you can return array or jsx
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={search} // change here
onChange={handleChange}
/>
<ul>{(data || []).filter((item) => item.name.toLowerCase().includes(search)).map(e=>(<li key={e.name}>{e.name}</li>))}</ul> /change here
</div>
Your new .filter() Array contains Objects inside it! You need to .map() it before return as Objects are not valid as a React child.
{ data?.filter((item) => item.name.toLowerCase().includes(search)).map((element =>
<>
/* Your code goes here! */
</>) }
Explanation:
Array.prototype.filter() returns a new Array and in your case your Array is filled with Objects, like this:
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
// The code above returns an Array just like below.
const array = [ {name: 'Brazil' /*...others properties*/}, {name: 'USA' /*...others properties*/}, {name: 'England' /*...others properties*/} ];
When you return array, React reject to mount your Objects, cus it can't understand what to do. That's why you map it, to have access to each Object inside it.

Categories

Resources