Array not resetting after changing query text - reactjs

React beginner here. I'm learning how to make a simple scrolling website. What happens is that when I type for example "hello" the first results will show up correctly, however if I type something else, the results will be added at the end of the array, and if I want to see them, I will have to scroll down. I want it to show the results as I am typing all while "resetting" the array every time.
Here's the code:
import React, { Component } from 'react';
import axios from 'axios';
import styled from 'styled-components';
const SearchStickerContainer = styled.div`
width: 100%;
height: 70vh;
overflow: scroll;
position: absolute;
top: 20%;
`;
const SearchLabel = styled.label`
font-size: 25px;
font-weight: bold;
`;
function SearchStickers() {
const [photos, setPhotos] = React.useState([]);
const [page, setPage] = React.useState(1);
const [bottom, setBottom] = React.useState(false);
const [query, setQuery] = React.useState(null);
const [search, setSearch] = React.useState('');
React.useEffect(() => {
querySearch(null, page);
}, [])
const querySearch = async (queryString, page) => {
let config = {
headers: {
apiKey: '823bb74a52fb44f8590c87b3dfd8c4e8'
}
}
if (bottom === true) {
setPage(page + 1);
}
if (queryString !== null) {
await axios
.get(`/v1/search?userId=9937&q=${queryString}&lang=en&pageNumber=${page}&limit=20`, config)
.then(res => {
if (res.data.body.stickerList !== null) {
setPhotos([...photos, ...res.data.body.stickerList]);
}
});
}
}
const searchBar = () => {
return (
<form>
<SearchLabel>
<span>search</span>
</SearchLabel>
<input
type="text"
id="search"
placeholder="search stickers"
onChange={(e) => [setQuery(e.target.value), querySearch(e.target.value)]}
value={query}
/>
</form>
)
}
const handleScroll = (e) => {
const bottom = e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
setBottom(true);
if (bottom) {
querySearch(query, page);
setBottom(false);
}
}
return (
<SearchStickerContainer onScroll={handleScroll}>
{searchBar()}
<div>
<div style={{ minHeight: "100px" }}>
{photos && photos.map(user => (
<img src={user.stickerImg} height="200px" width="200px" />
))}
</div>
{bottom === true ? <div> Loading ... </div> : null}
</div>
</SearchStickerContainer>
);
}
export default SearchStickers;
I suspect the issue comes from:
setPhotos([...photos, ...res.data.body.packageList]);
No matter how else I change it, the issue will remain the same (or get different issues, like the scrolling disappearing).

If you are not wanting to always append search results to the existing state, then instead of
setPhotos([...photos, ...res.data.body.stickerList]);
just fully replace the photos state
setPhotos(res.data.body.stickerList);

Related

How save style in the local storage

I have such a project. Here I want the button border save in the local storage.The buttons are divided into categories. For example when you refresh the page after selecting a sports button, the border of the button disappears. I want save btn border in the localstorage. I saved the categories in memory, but I can't make the border of the selected button.How can I fix it?
import React, { useEffect, useState } from "react";
import SpinnerLoad from './components/SpinnerLoad'
import NewsItem from "./components/NewsItem";
import Category from "./components/data/Category"
const App = () => {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false)
const [selected, setSelected] = useState('');
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then(res => res.json())
.then(res => {
setState(res.data)
setLoading(true)
})
.catch((error) => console.log(error))
setLoading(false);
};
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
<button onClick={() =>{ fetchValue(category,i) ; setSelected(i)} }
style={{border : selected === i ? '1px solid red' : null}} >{category}</button>
);
useEffect(() => {
let categoryValue = localStorage.getItem("category") || "all";
fetchValue(categoryValue)
const select = localStorage.getItem("selected") || "";
setSelected(select);
}, []);
return (
<>
<div className="header-bg">
<h1 className="mb-3">News</h1>
<div className="btns ">
{Category.map((value,i) => {
return <CategoryButton category={value} i={i}/>;
})}
</div>
</div>
<div className="news">
<div className="container">
<div className="row">
{
!loading
? <SpinnerLoad />
:
state.map((data, index) => {
return (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
key={data.id}
/>
);
})
}
</div>
</div>
</div>
</>
);
};
export default App;
According to the code looks like you want to display data specific to a category set when the user clicks on the category buttons. and after the click, the correct data is rendered and the current category button receives a change in its style highlighting it is the current state.
I don't understand why you need to store anything in a client's localstorage,
I would not recommend storing too much in localStorage as it is limited and is used by different sites a user visits, I only store authentication tokens in localstorage and I believe that is the norm.
I've tried to create the effect you want without the need to store in local storage
import React, { useState, useCallback, useEffect } from "react";
import ReactDOM from "react-dom";
import { cat } from "../categories.js";
import { news } from "../news.js";
function Example() {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState(null);
useEffect(() => {
function fetchFunction() {
setLoading(true);
for (let i = 0; i < news.length; i++) {
if (news[i].id === selected) {
const current = news[i].c;
setState(current);
}
}
setLoading(false);
}
fetchFunction();
}, [selected]);
return (
<>
<ol
style={{
width: "50%",
listStyle: "none",
display: "flex",
justifyContent: "space-between"
}}
>
{cat.map((item, index) => {
return (
<li key={index}>
<button
style={{ border: selected === item.id && "none" }}
onClick={() => {
setSelected(item.id);
}}
>
{item.name}
</button>
</li>
);
})}
</ol>
<section style={{ width: "100%", height: "70%" }}>
{state.map((item, index) => {
return (
<div
key={index}
style={{
width: "30%",
height: "30%",
background: "red",
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "1% 0 2% 0"
}}
>
{item.name}
</div>
);
})}
</section>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);
You can save the selectedIndex in localStorage and retrieve it in the useEffect..
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
// setting selected as string instead of index for type checking
<button onClick={() =>{ fetchValue(category,i) ; setSelected(`${i}`)} }
style={{border : selected === `${i}` ? '1px solid red' : null}} >{category}</button>
);
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
// ...
}
useEffect(() => {
const select = localStorage.getItem("selected") || "";
// passing selectedIndex to the fetchValue, otherwise it becomes
//undefined..
fetchValue(categoryValue,select)
setSelected(select);
},[])

Using API to import Chicago Art Institute using react typescript

I am having an extremely hard time trying to import a list of image details on my react application from the Chicago art institute. I struggle a lot understanding API, so a detailed answer would be helpful. As of now, I do understand the code I need for the image list I want, but I am stuck at this code below on making it do what I want with the link provided from the Chicago art documentation. I would like for the API to pop up on the website if possible.
import { Navbar } from '../Navbar';
import Real from '../../assets/images/scream.jpeg'
import { makeStyles } from '#material-ui/core';
const useStyles = makeStyles({
background: {
backgroundImage: `linear-gradient(rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0.5) 100%), url(${Real})`,
width: '100%',
height: '100%',
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
position: "absolute",
zIndex: -1,
},
main_text: {
textAlign: 'center',
position: 'relative',
top: '70%',
left: '50%',
transform: 'translate(-50%, -50%)',
color: 'white',
}
})
async function getArt()
{
let response = await fetch(`https://api.artic.edu/api/v1/artworks?ids=208131,240935,142595,120300,13454,151363,102611,191556,117266,137125,126414&fields=id,title,image_id`);
let data = await response.json()
return data;
}
getArt()
.then(data => console.log(data));
export const Art = () => {
const classes = useStyles();
return (
<>
<Navbar />
<div className={`${classes.background}`}>
<div className={classes.main_text}>
<div></div>
</div>
</div>
</>
)
}
Here is a minimalistic implementation. You retrieve data from API, then set your img's src attribute to the API's response.
import React, { useEffect, useState } from "react";
const imageUrl = "https://api.artic.edu/api/v1/artworks?ids=208131,240935,142595,120300,13454,151363,102611,191556,117266,137125,126414&fields=id,title,image_id";
export default function App() {
const [img, setImg] = useState<string | undefined>();
const fetchImage = async () => {
const res = await fetch(imageUrl);
const imageBlob = await res.blob();
const imageObjectURL = URL.createObjectURL(imageBlob);
setImg(imageObjectURL);
};
useEffect(() => {
fetchImage();
}, []);
const classes = useStyles();
return (
<>
<Navbar />
<img src={img} alt="icons" />
<div className={`${classes.background}`}>
<div className={classes.main_text}>
<div></div>
</div>
</div>
</>
);
}
Cheers
You need to use useEffect hook for that inside the component.
Not sure what type of data is returned, I assume that it is an array of objects in this case
interface IArtData {
id: string
//Put whatever type of data it is
}
export const Art = () => {
const classes = useStyles();
const [artData, setArtData] = useState<IArtData[] | undefined | null >() //If it is array, keep like this, else <IArtData | undefined | null>
//Run effect at mount time, fetch data and set state
useEffect(() =>
{
const fetchData = async () => {
let response = await fetch(`https://api.artic.edu/api/v1/artworks?ids=208131,240935,142595,120300,13454,151363,102611,191556,117266,137125,126414&fields=id,title,image_id`);
let data: IArtData[] | undefined | null = await response.json()
//Set data to state
setArtData(data)
}
fetchData()
}, []) //Run only once on mount
return (
<>
<Navbar />
<div className={`${classes.background}`}>
<div className={classes.main_text}>
<div>
{artData?.length
//Check if artData exists and render items
? artData.map((item) => {
console.log(item) //Check what data is inside of item
return (<div><p>{item.something}</p></div>); //Change "something" with a real parameter. For example, if item is {url: "https...", title: "My title"}, change it with item.title
})
: null}
</div>
</div>
</div>
</>
)
}

Post content and reply comment using react

I am new in react and for educational purposes, I am trying to make social media app that contains a discussion section. I made a recursive textbox for that. Now, I need a line number for new content as well as for the replied comment. Here submit button is used to post new content and the reply button is used to comment on existing content. How can I do this?
Here is my code
import React, { useState } from "react";
import ContentBoxWithVotes from "./content_box_with_votes";
import styled from "#emotion/styled";
// creating a bundler component
const Bundle = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
`;
const ContentBoxRepeatWtihVotesFunctional = () => {
const [buttonName, setButtonName] = useState("stop");
const [isExit, setIsExit] = useState(false);
const [count, setCount] = useState([1]);
const [lineNumber, setLineNumber] = useState(1);
const toogelButton = (text) => {
if (buttonName === "stop") {
setButtonName("Restart");
setIsExit(true);
} else {
setButtonName("stop");
setIsExit(false);
}
};
function handleReply(lineNumber){
setLineNumber(lineNumber+1);
};
const handleChange = (index, value) => {
// setValue({
// values:{
// ...values,
// [index]: value
// }}
// );
};
const handleSubmit = (e) => {
e.preventDefault();
if (!isExit) {
const lastCount = count[count.length - 1] + 1;
setCount([...count, lastCount]);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
{count.map((key) => (
<Bundle>
<label>{lineNumber[key] } </label>
<ContentBox
value={key}
onChange={(event) => handleChange(event.target.value)}
/>
<input type="submit" value="Submit" />
<input type="button" value="Reply" onClick= {handleReply}/>
</Bundle>
))}
</form>
<button onClick={toogelButton}>{buttonName}</button>
</div>
);
};
export default ContentBoxRepeatWtihVotesFunctional;
```
**code inside ContentBox is**
```
import React from 'react';
const ContentBox = () => {
return(
<input type="text" placeholder='Add your content line here ... '/>
);
};
export default ContentBox;

How to add suggestion box to text input in React

I have an input with an onChange attribute that calls a function that updates my prop to store the three suggestions that I want to display. These suggestions are stored in a string array. How can I display these values below the input like an autocomplete feature?
Currently using react typescript and bootstrap
This is how you should handle the dropdown suggestions. I am not very familiar with react bootstrap but here is the general idea:
import React, { useState, useRef, useEffect, Fragment } from "react";
const SuggestionsInput = () => {
const [ value, setValue ] = useState("");
const [ showDropdown, setShowDropdown ] = useState(false);
const wrapperRef = useRef(null);
const suggestions = "a, aa, b, bb, cc, d, dd, e, ee";
const suggestionArr = suggestions
.split(", ")
.filter(suggestion =>
suggestion.toLowerCase().includes(value.toLowerCase())
);
const changeHandler = e => {
setValue(e.target.value);
};
const handleClickOutside = e => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
setShowDropdown(false);
}
};
// Attaching the previous event with UseEffect hook
useEffect(() => {
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
});
return (
<div className="input__wrapper" ref={wrapperRef}>
<input
type="text"
onChange={changeHandler}
onFocus={() => setShowDropdown(true)}
value={value}
/>
{showDropdown && (
<div className="suggestions__dropdown">
{suggestionArr && suggestionArr.length > 0 ? (
<Fragment>
{suggestionArr.map((suggestion, index) => (
<div
key={"suggestion_" + index}
className="suggestion__item"
onClick={() => setValue(suggestion)}
/>
))}
</Fragment>
) : (
<div className="no__suggestions">No suggestions found</div>
)}
</div>
)}
</div>
);
};
And you should add this css:
.input__wrapper {
position: relative;
}
.input__wrapper .suggestions__dropdown {
position: absolute;
top: 30px;
right: 0;
left: 0;
width: 100%;
height: 100px;
overflow-y: auto;
}

How to prevent Link from messing up my radio buttons behavior?

I am trying to build a menu, that shows the current page in bold.
For that I am using radio buttons so that each item on the menu is a label for that radio button, and in my css I make an active item bold.
My problem is, that because the label is wrapped in a Link element, when an item is clicked nothing really changes. It navigates properly but the radio button sate doesn't change. Maybe everything just re renders ignoring my action?
It works just fine without the link element. Why is that? And what can I do to make it work?
This is the code for my menu component:
import "./styles.scss";
import React from "react";
import { Link } from "react-router-dom";
const Menu = () => {
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
onChange={() => console.log(smallHyphenedItem)}
/>
<Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
{item}
</label>
</Link>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
export default Menu;
EDIT: I've tried to use a state in the menu component but that doens't help either:
const Menu = () => {
const [currentPage, setCurrentPage] = useState(null);
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
checked={currentPage === smallHyphenedItem}
onChange={() => setCurrentPage(smallHyphenedItem)}
/>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
<Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
>
{item}
</Link>
</label>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
export default Menu;
This was a bit more of a headach than I've expected, but I got it working.
Like Sasha suggested, I needed to store the choice in a state using redux to have it persist between pages.
But that isn't enough. using Link didn't allow for the action to be executed before navigating (to my understanding).
What I had to do was instead of using Link, to just navigate to the page I wanted using the history.push() command.
This is my final working code:
import "./styles.scss";
import React from "react";
import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom";
import history from "../../../history";
import { setCurrentPage } from "../../../actions";
const Menu = ({ setCurrentPage, page }) => {
const myHistory = useHistory(history);
console.log(page);
const turnToLowerCaseWithHyphen = string => {
return (string[0].toLowerCase() + string.slice(1)).replace(" ", "-");
};
const handleChange = (page) => {
setCurrentPage(page);
myHistory.push(`/${page}`)
};
const renderMenuItems = array => {
return array.map(item => {
const smallHyphenedItem = turnToLowerCaseWithHyphen(item);
return (
<div className="flex-group" key={smallHyphenedItem}>
<input
className="menu-item__radio"
id={smallHyphenedItem}
type="radio"
name="menu-items"
checked={page === smallHyphenedItem}
onChange={() => handleChange(smallHyphenedItem)}
/>
<label htmlFor={smallHyphenedItem} className="menu-item__label">
{/* <Link
to={"/" + smallHyphenedItem}
className="menu-item"
key={smallHyphenedItem}
> */}
{item}
{/* </Link> */}
</label>
</div>
);
});
};
return (
<div className="menu">
{renderMenuItems(["Feed", "Search", "Contact us"])}
</div>
);
};
const mapStateToProps = state => {
return {
page: state.page
};
};
export default connect(mapStateToProps, { setCurrentPage })(Menu);
And this is my CSS:
.menu {
display: grid;
grid-template-columns: repeat(4, max-content);
grid-gap: 3rem;
&-item {
&__label {
font-family: var(--font-main);
font-size: 1.6rem;
transition: all 0.2s;
text-decoration: none;
color: var(--color-grey-medium);
width: max-content;
&:hover {
color: var(--color-main);
}
}
&__radio {
visibility: hidden;
opacity: 0;
&:checked + .menu-item__label {
color: var(--color-main);
}
}
}
}

Resources