I have a website where if I press a button it fetches a post object from a database and adds it into an array. I need to somehow display all the objects in the array as react components and also to update the list every time when a new post is added.
I've been trying to use the map() method but I can't get it to display the new posts that are added when I click the button.
Main component:
import Post from './Post'
import { useState, useEffect, createElement } from 'react'
import { useCookies } from 'react-cookie';
import Axios from 'axios'
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = ()=>{
Axios.defaults.withCredentials = true
Axios({
method: 'get',
url: 'http://localhost:3010/api/getpost/',
headers: {'Content-Type': 'multipart/formdata' }
})
.then((response) => {
addPostToPostArray(response)
})
.catch((response) => {
console.log(response);
});
}
const addPostToPostArray = (response) => {
let imgName = response.data.imgurl.slice(68, 999999)
let sendObj = {
id:response.data.id,
posterid:response.data.posterid,
imgurl:`http://localhost:3010/images/${imgName}`,
title:response.data.title,
likes:response.data.likes,
date:response.data.date
}
postArr.push(sendObj)
/*
A fetched post will look like this:
{
id:123, posterid:321, imgurl:`http://localhost:3010/images/333.png`,
title:'best title', likes:444, date:111111
}
*/
}
return (
<div>
{postArr.map((e) => {
return <Post post={e}/>
})}
<button onClick={getPosts}>load post</button>
</div>
);
}
export default Content;
Post component:
const Post = (props) => {
const post = props.post
return (
<div className='post-frame'>
<h1>{post.title}</h1>
<div className="image-frame">
<img src={post.imgurl}></img>
</div>
<p>{post.likes}</p>
<p>{post.posterid}</p>
</div>
);
}
export default Post;
To update the state of the component you need to call that setPostArr function with the updated array. Without that the state of the component never get's updated.
Here's an example
const Content = () => {
const [postArr, setPostArr] = useState([])
const getPosts = () => {
...
}
const addPostToPostArray = (response) => {
let sendObj = {
...
}
// ~ This part here
setPostArr([...postArr, sendObj])
// ~ Instead of
// postArr.push(sendObj)
}
return ...
}
Related
Currently following a slightly older tutorial, but learning using React 18 -- trying to update the text area in a notes app
It looks like when I type, a character appears and then immediately is deleted automatically
Can anyone confirm if I might be missing a detail here?
for reference if familiar with the project at time 1:37:03 : https://www.youtube.com/watch?v=6fM3ueN9nYM&t=377s
import React, {useState, useEffect} from 'react'
import notes from '../assets/data'
import { useParams } from 'react-router-dom';
import { Link } from 'react-router-dom'
import { ReactComponent as ArrowLeft } from '../assets/arrow-left.svg'
const NotePage = ( history ) => {
const {id} = useParams();
// let note = notes.find(note => note.id===Number(id))
// console.log(id)
let [note, setNote] = useState(null)
useEffect(() => {
getNote()
}, [{id}])
let getNote = async () => {
let response = await fetch(`http://localhost:8000/notes/${id}`)
let data = await response.json()
setNote(data)
}
// let updateNote = async () => {
// await fetch(`http://localhost:8000/notes/${id}`, {
// method: 'PUT',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({...note, 'updated':new Date()})
// })
// }
// let handleSubmit = () => {
// updateNote()
// history.push('/')
// }
return (
<div className="note">
<div className="note-header">
<h3>
<Link to="/">
<ArrowLeft /*onClick={handleSubmit}*/ />
</Link>
</h3>
</div>
<textarea onChange={(e) => {
setNote({...note, 'body': e.target.value}) }}
value={note?.body}>
</textarea>
</div>
)
}
export default NotePage
Your value in the useEffect dependency array is incorrect and causing getNote to be called every time you make changes in the textArea. Every time getNote is called, it's resetting the note state back to whataver is being received by getNote. Which in your case is probably a blank note
Change this :
useEffect(() => {
getNote();
}, [{ id }]);
To this:
useEffect(() => {
getNote();
}, [id]);
I'm facing difficulty displaying data in React - Here is my code:
import Axios from 'axios';
import { useNavigate } from 'react-router';
export default function ProductCatalog() {
let navigate = useNavigate();
function addProduct() {
navigate('/adding')
}
const [products, setProducts] = useState([{}])
useEffect(() => {
const axiosProd = async () => {
const response = await Axios('http://localhost:3001/getProducts');
setProducts(response.data)
};
axiosProd();
}, []);
const useProducts = products.map((product)=>{
return <div>
<h1>{product.name}</h1>
</div>
})
return(
<>
<button className = "button" onClick={addProduct}>Add New Product</button>
<br></br>
{useProducts}
</>
)
}
I know data is coming in as JSON Objects as when i follow the link of http://localhost:3001/getProducts, I see my data. What am i doing wrong?
You should make a function then outside of the function call the use effect.
To do a get request using axios use axios.get(api)
For example:
// Get All Shoes
const getShoes = () => {
axios.get('/shoes')
.then(res => setShoes(res.data))
.catch(err => console.log(err));
}
Then
useEffect(() => {
getShoes();
}, [])
I am trying to do getServerSideProps but I am getting the following error what is the error I am doing
TypeError: Cannot read properties of undefined (reading 'map')
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { FormControl, Button } from "react-bootstrap";
import Card from "react-bootstrap/Card";
export default function Answershooks(props, { posts }) {
return (
<div className="answerhook">
{posts.map((personData, index) => {
return (
<Card key={index} className="cardmobile">
<Card.Body>
<p className="answersize">{personData.Answers} </p>
</Card.Body>
</Card>
);
})}
</div>
);
}
export async function getServerSideProps(ctx) {
const res = await fetch("https://askover.wixten.com/answersapi/" + props.id);
console.log(res);
console.log("dada");
const posts = await res.json();
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
};
}
i have added added a file stucture screenshot so u undersand how my files are placed
Your main problem is you're trying to call getServerSideProps in Answerhooks but it's not a page component, so you cannot get data on the server as expected
Instead of having getServerSideProps in that, you can move your API call to getServerSideProps in [itmid].jsx (which is an actual page component) like below
export async function getServerSideProps(ctx) {
var id = ctx.query.itmid;
const queryRequest = fetch("https://ask-over.herokuapp.com/questone/" + id).then(async (res) => await res.json());
const answerRequest = fetch("https://askover.wixten.com/answersapi/" + id).then(async (res) => await res.json());
const [posts, answerPosts] = await Promise.all([queryRequest, answerRequest]);
return {
props: {
posts,
answerPosts
}
};
}
After that, you can get answerPosts from props for Query
function Query({ posts, answerPosts }) {
return <Answerhooks answerPosts={answerPosts} />
}
Finally, you can have the data on props inside Answerhooks component
function Answershooks({ answerPosts }) {
//TODO: Apply your logic with `answerPosts`
console.log(answerPosts)
return <div></div>
}
Lets start with the fetch error and work out why that is failing so make a new component.
fetchHandler.js
export async function fetchHandler(url){
try{
const res = await fetch(url);
return res
} catch(err){
console.log(err); //this will tell us why it failed.
return false //this gives us a condition we can use on the front end
}
}
Then your Answerhooks.
import {fetchHandler} from '../yourpath'
export default function Answershooks({ posts }) {
return (
<div className="answerhook">
{posts.map((personData, index) => {
return (
<Card key={index} className="cardmobile">
<Card.Body>
<p className="answersize">{personData.Answers} </p>
</Card.Body>
</Card>
);
})}
</div>
);
}
export async function getServerSideProps(ctx) {
const url = `https://askover.wixten.com/answersapi/${ctx.query.id}`
const res = await fetchHandler(url)
console.log(res);
const posts = await res.json();
return {
props: {
posts: posts === false ? [] : posts //Just to stop your app crashing
},
};
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({req}) => {
const result = await store.dispatch(fetchHome());
return {
props: {
list : result
},
};
}
);
I want to test the child component render after click a display detail button. When click the button, it will call api getPokemonDetail to get the Pokemon detail and then change the PokemonDetail state.
The app is running well. But on my unit test, I can only get the Loading text after button click. I used axios-mock-adapter to mock axios request. If I disable the axios mock, it's running correct. Looks like the axios mock is not working after button click. How should I fix it? The below is my code.
Parent Component:
const getPokemonDetail = async (pokemonName) => {
const queryName = pokemonName ?? "ditto"
try {
const result = await axios.get(`https://pokeapi.co/api/v2/pokemon/${queryName}`)
console.log(result.data)
const { name, id, weight } = result.data
return Promise.resolve({ name, id, weight })
} catch (error) {
return undefined
}
}
const ParentComponent = (props) => {
const { pokemonName } = props
const [pokemonDetail, setPokemonDetail] = useState({
detail: null,
loading: true,
})
const retrieveDisplayStatus = async () => {
const getPokemonDetailResult = await getPokemonDetail(pokemonName)
if (getPokemonDetailResult) {
setPokemonDetail({
detail: getPokemonDetailResult,
loading: false,
})
}
}
const { showDetails, toggleDetails } = useToggleDetails(retrieveDisplayStatus)
return (
<>
<h2>Display Pokemon Detail</h2>
<button onClick={toggleDetails}>Display Detail</button>
<>
{showDetails ? (
<>{
pokemonDetail.loading ?
<div>Loading...</div> :
<ChildComponent pokemonDetail={pokemonDetail.detail} />
}</>
) : null}
</>
</>
)
}
Child Component:
const ChildComponent = (props) => {
const { pokemonDetail } = props
const { id, name, weight } = pokemonDetail
return (
<>
<div>ChildComponent</div>
<div data-testid="testId">id: {id}</div>
<div>name: {name}</div>
<div>weight: {weight}</div>
</>
)
}
export default ChildComponent
On the Parent component test:
import { render, screen } from "#testing-library/react"
import ParentComponent from "../ParentComponent"
import userEvent from "#testing-library/user-event"
import axios from "axios"
import MockAdapter from "axios-mock-adapter"
describe("test", () => {
let mock
beforeAll(() => {
mock = new MockAdapter(axios)
})
afterEach(() => {
mock.reset()
})
test("Render child component after api call", async () => {
render(<ParentComponent />)
const heading = screen.getByText("Display Pokemon Detail")
expect(heading).toBeInTheDocument()
const toggleButton = screen.getByRole("button", { name: /display/i })
expect(toggleButton).toBeInTheDocument()
userEvent.click(toggleButton)
const loadingText = await screen.findByText(new RegExp("Loading", "i"))
expect(loadingText).toBeInTheDocument()
// the below is not working, looks like the axios mock not working
mock.onGet(`https://pokeapi.co/api/v2/pokemon/ditto`).reply(200, { name: "ditto", id: 132, weight: 40 })
const childHeading = await screen.findByText("ChildComponent")
expect(childHeading).toBeInTheDocument()
const id = await screen.findByTestId("testId")
expect(id).toHaveTextContent("132")
})
})
I'm trying to make a page to show the details of each video.
I fetched multiple video data from the back-end and stored them as global state.
This code works if I go to the page through the link inside the app. But If I reload or open the URL directory from the browser, It can not load the single video data.
How should I do to make this work?
Thanx
Single Video Page
import { useState, useEffect, useContext } from "react";
import { useParams } from "react-router-dom";
import { VideoContext } from "../context/videoContext";
const SingleVideo = () => {
let { slug } = useParams();
const [videos, setVideos] = useContext(VideoContext);
const [video, setVideo] = useState([]);
useEffect(() => {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}, []);
return (
<>
<div>
<h1>{video.title}</h1>
<p>{video.content}</p>
<img src={video.thumbnail} alt="" />
</div>
</>
);
};
export default SingleVideo;
Context
import React, { useState, createContext, useEffect } from "react";
import Axios from "axios";
import { AxiosResponse } from "axios";
export const VideoContext = createContext();
export const VideoProvider = (props) => {
const [videos, setVideos] = useState([]);
const config = {
headers: { "Access-Control-Allow-Origin": "*" },
};
useEffect(() => {
//Fetch Vidoes
Axios.get(`http://localhost:5000/videos`, config)
.then((res: AxiosResponse) => {
setVideos(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<VideoContext.Provider value={[videos, setVideos]}>
{props.children}
</VideoContext.Provider>
);
};
I think the reason is because when you refresh the app, you fetch the video data on context and the useEffect on your single video page component runs before you receive those data.
To fix you can simply modify slightly your useEffect in your single video component to update whenever you receive those data:
useEffect(() => {
if (videos.length) {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}
}, [videos]);