today i have a problem with my searchbar.
const [posts, setPosts] = useState(null)
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div>
<input type={"text"} placeholder="Search..." onChange={event => {
setSearchTerm(event.target.value)
}}/>
</div>
)
}
This is my Searchbar Component. In the Index file, did i gave a props with.
const [posts, setPosts] = useState([])
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div className={styles.posts}>
<h1>Market-place Valando</h1>
<SearchList title={posts.filter(post => {
if (post.title.toLowerCase().includes(searchTerm.trim().toLowerCase()) && searchTerm.trim() !== "") {
return post.title
}
}).map(titles => {
{
{titles.title}
}
}
)}/>
{
posts.map(post => {
return (
<div key={post.id} className={styles.key}>
<h1>{post.title}</h1>
<Image width={1000} height={1000} src={post.image}/>
<p>Price: {post.price}.-</p>
<p>Description: {post.description}</p>
<Link href={`/posts/${post.id}`} passHref>
<a>Read more</a>
</Link>
</div>
)
})
}
</div>
)
}
I have a db.json file that i connected with an API File. In this Api File i made all the fetch stuff. This shouldnt be the problem. I think the problem is, that the filter doesnt work properly, with the titels.
You are correct, JavaScript filter does not return specific property values, but it returns the top entries of the array, a.k.a posts. So return post.title or return true will yield the same result. However, the problem in your code appears to be that you are not returning anything from the map function. All you need to do is to change it to the following:
.map(post => post.title)
Related
import React, { useEffect, useState } from "react";
import Loading from "./Loading";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
async function setCompany(companyName) {
await getData();
const newData = data.filter((info) => info.company === companyName);
setData(newData);
}
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
useEffect(() => {
getData();
}, []);
if (loading) {
return <Loading></Loading>; // simple loading screen
}
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{data.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => {
setCompany("TOMMY");
}}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => {
setCompany("BIGDROP");
}}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => {
setCompany("CUKER");
}}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
Sooo... basically I'm trying to filter the array returned by Fetch and have it display only the category I want (I called it "company instead of category in my code") depending on which button I click as shown in the "nav-buttons" div down in the code.
The first time I click on a button it works fine, but the second time it doesn't show anything as if it's filtering from an already filtered array which return no results obviously.
update these two methods with these two lines:
async function setCompany(companyName) {
const response=await getData(); //THIS ONE
const newData = response.filter((info) => info.company === companyName);
setData(newData);
}
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
return data;// And THIS ONE
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
// Get the oportunity to learn about promises, and you will save so much time. ;)
import React, { useEffect, useState } from "react";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [companyName, setCompanyName] = useState("");
async function setCompany(companyName) {
getData();
const newData = setData(newData);
}
function getData(companyName) {
setCompanyName(companyName);
fetch(url)
.then((res) => res.json())
.then((info) => {
console.log(info);
return companyName
? info.filter((info) => info.company == companyName)
: info;
})
.then((res) => {
console.log(res);
return setData(res);
})
.catch((err) => {
setLoading(false);
console.error(`ERROR ==> ${err}`);
});
}
useEffect(() => {
getData();
}, []);
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{data.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => getData("TOMMY")}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => getData("BIGDROP")}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => getData("CUKER")}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
you don't need to call the same API on each filter as it returns same data if I'm not wrong.
you can filter the data with the derived state, by storing the selected company in state i.e., on each render it calculates based on the selected company.
use the filtered data to render finally.
Here is the full e.g.
import React, { useEffect, useState } from "react";
import Loading from "./Loading";
function App() {
const url = "https://course-api.com/react-tabs-project";
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [selectedCompany, setSelectedCompany] = useState(""); // store the company on click
const filteredData = selectedCompany ? data.filter(info=> info.company === selectedCompany) : data; // filter data based on selected company
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setLoading(false);
console.error(`ERROR ==> ${err}`);
}
}
useEffect(() => {
getData();
}, []);
if (loading) {
return <Loading></Loading>; // simple loading screen
}
return (
<main>
<div className="top-wrapper">
<h2>Experience</h2>
<div className="underline"></div>
</div>
{filteredData.map((item) => {
const { id, order, title, dates, duties, company } = item;
return (
<article key={id}>
<h3>{title}</h3>
<span className="company">{company}</span>
<p>{dates}</p>
<ul>
{duties.map((duty, index) => {
return <li key={index}>{duty}</li>;
})}
</ul>
<button>MORE INFO</button>
</article>
);
})}
<div className="nav-buttons">
<button
onClick={() => {
setSelectedCompany("TOMMY");
}}
className="nav-btn"
>
TOMMY
</button>
<button
onClick={() => {
setSelectedCompany("BIGDROP");
}}
className="nav-btn"
>
BIGDROP
</button>
<button
onClick={() => {
setSelectedCompany("CUKER");
}}
className="nav-btn"
>
CUKER
</button>
</div>
</main>
);
}
export default App;
try putting a check case before filter to insure that your array isn't empty.
async function setCompany(companyName) {
await getData();
{data ?
const newData = data.filter((info) => info.company === companyName);
:
null}
setData(newData);
}
I think part of your issue is when your calling get data on button click your state isn't set before running the filter logic. I would look over your functional logic and ask yourself is this the best way to do this and am i trying to filter before or after my response.
I am trying to render a skeleton component on loading of the data or 404 error component if data not found so far i have tried using if else statements and logical operators so far none work properly.
const [post, setPost] = useState(null);
const [postExternal, setPostExternal] = useState([]);
const fetchPost = () => {
axios.get(`${API_ONE}/posts?id=${id}`).then((response) => {
setPost(response.data);
});
axios.get(`${API_TWO}/posts?id=${id}`).then((response) => {
setPostExternal(response.data);
});
return;
};
const location = useLocation();
const id = location.pathname.split('/')[2];
useEffect(() => {
fetchPost();
}, [id]);
{
post && postExternal && (
<div>
<h1>{post.title}</h1>
<img src={postExternal} />
</div>
);
}
{
!post &&
(post && post.id === postExternal.id ? (
<NotFound message='not found' />
) : (
<SkeletonItemPage />
));
}
Note: The data is fetched from two different apis
Define a loading state variable as the follow:
const [isLoading, setIsLoading] = useState(false);
Then your fetchPosts function would be like:
const fetchPost = () => {
const apiOnePromise = axios.get(`${API_ONE}/posts?id=${id}`);
const apiTwoPromise = axios.get(`${API_TWO}/posts?id=${id}`);
//toggle loader
setLoading(true);
Promise.all([apiOnePromise, apiTwoPromise])
.then(values => {
//handle your responses here
})
.finally(() => {
//toggle loader again
setLoading(false);
})
};
Using this loading variable you can differentiate between the statues, so with your jsx you can do:
{ loading && <SkeletonItemPage /> }
{ !loading && posts.length === 0 && <NotFound message='not found' /> }
I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
I want to create a simple layout where the user can choose a dynamic dropdown that retrieves data based on an API call using React.js and Django as backend, but however, I want to pre-render category from the database and let the user choose which Industry, and change the layout data accordingly
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxxx/list/?domain=${domain}`);
}
async function fetchDomain() {
return api.get('http://localhost:8002/api/v1/xxxx/domain/'); # return all domains
}
export default function Board () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([])
const [domains, setDomains] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response)=>{
setResponseData(response.data.results)
})
.catch((error) => {
console.log(error)
})
}
const handleOnClick = async (data) => {
try {
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
useEffect(() => {
fetchData();
}, []);
return (
<div className="container content">
<select>options</select>
{responseData.map((data) => (
<div className="col" key={data.t_id}>
<div className="row">{data.tactic_name}</div>
{data.data.map((item) => (
<div className="row" key={item._id} onClick={() => handleOnClick(item)}><span>{item.title}</span></div>
))}
</div>
))}
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/> }
</div>
);
}
Error:
Warning: Encountered two children with the same key, 5e0611d77833da1668feade1. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
Here on this picture https://prnt.sc/qgfymk I have created 2 blogs. Delete button is working fine. I'm sending via axios to HTTP delete request using mongoose and MongoDB as my database.
But when I start to click on like button check what happens. https://prnt.sc/qgg32o
It removes my other blog post and copies one with the same name and id. The issue here is that I have different IDs but somehow when I press LIKE button it gives me another ID.
I'll give you code for both PUT request in backend and frontend for incrementLikes, I really don't know what is going on.
controllers/blogs.js (backend)
blogsRouter.put('/:id', async (request, response, next) => {
const body = request.body
const blogs = {
title:body.title,
author: body.author,
url:body.url,
likes: body.likes
}
try {
const updatedBlog = await Blog.findOneAndUpdate(request.params.id, blogs, {
new: true
})
response.json(updatedBlog.toJSON())
} catch (exception) {
next(exception)
}
})
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import Blog from './components/Blog';
import LoginForm from './components/LoginForm'
import BlogForm from './components/BlogForm'
import Notification from './components/Notification'
import loginService from './services/login';
import blogService from './services/blogs';
const App = () => {
const [blogs, setBlogs] = useState([])
const [user, setUser] = useState(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [errorMessage, setErrorMessage] = useState(null)
// states for blog creation
const [title, setTitle] = useState('')
const [author, setAuthor] = useState('')
const [url, setUrl] = useState('')
useEffect(() => {
console.log('effect')
blogService
.getAll()
.then(response => {
console.log('promise fulfiled')
setBlogs(response.data)
})
.catch(error => {
console.log('response', error.response)
console.log('error')
})
}, [])
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem('loggedBlogUser')
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON)
setUser(user)
blogService.setToken(user.token)
}
}, [])
//put request
***const incrementLike = id => {
const blog = blogs.find(b => b.id === id)
console.log('blog id', blog)
const voteLike = {...blog, likes: blog.likes + 1}
blogService
.update(id, voteLike)
.then(returnedBlog => {
setBlogs(blogs.map(blog => blog.id !== id ? blog : returnedBlog))
})
.catch(error => {
setErrorMessage(
`Blog was already removed from server`
)
setTimeout(() => {
setErrorMessage(null)
}, 5000)
})
}***
//login
const handleLogin = async (e) => {
e.preventDefault()
try {
const user = await loginService.login({username, password})
window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
setUser(user)
setUsername('')
setPassword('')
console.log('success')
} catch (exception) {
setErrorMessage('wrong credentials')
setTimeout(() => {
setErrorMessage(null)
}, 5000)
console.log('baaad')
}
}
const deleteBlogId = (id) => {
console.log('deleted blog')
blogService
.del(id)
.then(response => {
setBlogs(blogs.filter(blog => blog.id !== id))
})
.catch(error => {
console.log(error.response);
})
}
const handleCreateBlog = async (e) => {
e.preventDefault()
const newBlogs = {
title: title,
author: author,
url: url,
date: new Date()
}
blogService
.create(newBlogs)
.then(returnedBlog => {
setBlogs(blogs.concat(returnedBlog))
setTitle('')
setAuthor('')
setUrl('')
setErrorMessage(`${author} created new blog with name ${title}`)
setTimeout(() => {
setErrorMessage(null)
}, 5000)
})
}
const loginForm = () => {
return (
<div>
<Notification message={errorMessage}/>
<div>
<LoginForm
username={username}
password={password}
handleUsernameChange={({target}) => setUsername(target.value)}
handlePasswordChange={({target}) => setPassword(target.value)}
handleSubmit={handleLogin}
/>
</div>
</div>
)
}
const handleTitleChange = (event) => {
console.log(event.target.value)
setTitle(event.target.value)
}
const blogForm = () => {
return (
<div>
<BlogForm
title={title}
author={author}
url={url}
handleTitleChange={handleTitleChange}
handleAuthorChange={({target}) => setAuthor(target.value)}
handleUrlChange={({target}) => setUrl(target.value)}
onSubmit={handleCreateBlog}
/>
</div>
)
}
const handleLogout = async () => {
window.localStorage.clear()
setUser(null)
}
const logout = () => {
return (
<div><button type="reset" onClick={handleLogout}>Logout</button></div>
)}
const blogList = () => {
return (
<div>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
{blogs.map(blog =>
<Blog
key={blog.id}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</div>
)
}
return (
<div className="App">
{user === null ?
loginForm() :
<div>
<Notification message={errorMessage}/>
{blogForm()}
{blogList()}
</div>
}
</div>
);
}
export default App;
Check the incrementLikes function. I think there is some kind of issue. Button for likies are in component called Blog.js
Blog.js
import React from 'react';
const Blog = ({blog, increment, deleteBlog}) => (
<div>
<button onClick={deleteBlog}>Delete</button>
{blog.title}
{blog.author}
{blog.likes}
<button onClick={increment}>Like</button>
</div>
)
export default Blog
While there shouldn't be 2 blogs with the same ID you can fix the issue at hand by replacing the key from blog.id to the index of the post like this.
<div>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
//change
{blogs.map((blog,index) =>
<Blog
//change
key={index}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</div>
I added //change to the lines I changed.
You can just use something like uuid for this which will generate a unique ID.
import uuid from "uuid";
<>
<h2>Blogs</h2>
<p>{user.name} logged in</p>
{logout()}
{blogs.map((blog,index) =>
<Blog
key={uuid.v4()}
deleteBlog={() => deleteBlogId(blog.id)}
blog={blog}
increment={() => incrementLike(blog.id)} />
)}
</>