I need to create a Card that is dynamically created by populating data from an API. I am able to get this data but I am unable to show the view in the render method.
Kindly assist me to fix my code.
Below is my class Component where I use axios to get a form data, then I iterate through to get the key and value and assign it to the card i want to display. Now I cannot seem to see the Card at all.
class Cards extends Component {
constructor(props) {
super(props);
this.state = { users: [] }
}
componentDidMount() {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
let rows = []
let count = 0
for (var i = 0; i < response.data.length; i++) {
console.log("data: "+response.data[i].key);
rows.push(<div className="col-md-4">
<div className="card">
<p>Data {count++}</p>
<h1>{response.data[i].key}</h1>
<p>{response.data[i].value}</p>
</div>
</div>
)
this.setState({ users: rows })
}
})
.catch((error) => {
console.log(error);
});
}
render() {
return (
<div className="cards">
{this.users}
</div>
);
}
}
export default Cards;
It's not a good practice to add HTML tags into the state Instead, add your API response to the state and use the render() to render the data in proper HTML tags.
class Cards extends Component {
constructor(props) {
super(props);
this.state = {
response: {},
};
}
const apiCall = () => {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
this.setState({ response: response });
}).catch((error) => {
console.err(error);
});
}
componentDidMount() {
apiCall();
}
render() {
const { response } = this.state;
return (
<div className="cards">
{response.data.map((item, index) => {
<div key={`user-${index}`} className="col-md-4">
<div className="card">
<p>Data {index+1}</p>
<h1>{item.key}</h1>
<p>{item.value}</p>
</div>
</div>
})}
</div>
);
}
}
export default Cards;
class Cards extends Component {
constructor(props) {
super(props);
this.state = { users: [] };
}
componentDidMount() {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
this.setState({ users: response.data });
})
.catch((error) => {
console.log(error);
});
}
render() {
return (
<div className="cards">
{this.state.users?.map((user, id) => (
<div className="col-md-4" key={user.key}>
<div className="card">
<p>Data {id}</p>
<h1>{user.key}</h1>
<p>{user.value}</p>
</div>
</div>
)}
</div>
);
}
}
export default Cards;
Related
I have two pages on my react app. One page allows you to submit a post, and the second page shows all of the posts. I need to be able to retrieve the data from the state on one page, but I am receiving an error. What am I doing wrong to display this, because I thought I could use props to gather the state from my post page.
My Display Post Page:
import React from 'react';
import './App.css';
export default class Scroll extends React.Component {
render() {
return (
<div className="flex-container">
<div className="post">
{this.props.displayPost(this.props.state.posts)}
</div>
</div>
);
}
}
My post page:
import React from 'react';
import axios from 'axios';
import './App.css';
import { post } from '../../routes/routes';
export default class PersonList extends React.Component {
state = {
title: "",
body: "",
posts: []
};
componentDidMount = () => {
this.getPost();
}
getPost = () => {
axios.get("http://localhost:5000/posts/save")
.then((response) => {
const data = response.data;
this.setState({ posts: data });
console.log("Data has been recieved")
})
.catch(() => {
alert("Error recieving data")
})
}
handleChange = (event) => {
const target = event.target;
const name = target.name;
const value = target.value;
this.setState({
[name]: value
})
};
submit = (event) => {
event.preventDefault();
const payload = {
title: this.state.title,
body: this.state.body,
}
axios({
url: 'http://localhost:5000/posts/save',
method: 'POST',
data: payload,
})
.then(() => {
console.log('Data sent to the server');
})
.catch(() => {
console.log('Internal server error');
});
};
displayPost = (posts) => {
if (!post.length) return null;
return posts.map((post, index) => {
<div key={index}>
<h3 id="post-text">{post.title}</h3>
<p id="post-text">{post.body}</p>
</div>
});
}
render() {
console.log("State ", this.state)
return (
<div className="flex-container-home">
<div className="app">
<form onSubmit={this.submit}>
<input
placeholder="title"
type="text"
name="title"
value={this.state.title}
onChange={this.handleChange}
/>
<textarea placeholder="description"
name="body"
cols="30" rows="10"
value={this.state.body}
onChange={this.handleChange}
>
</textarea>
<button>Submit</button>
</form>
</div>
</div>
)
}
}
Here is working example:
import React from "react";
export default class PersonList extends React.Component {
state = {
title: "",
body: "",
posts: [],
};
componentDidMount = () => {
this.getPost();
};
getPost = () => {
this.setState({ posts: ["post1", "post2", "post3"] });
};
displayPost = (posts) => {
if (!posts || !posts.length) return null;
return posts.map((post, index) => (
<div key={index}>
<p>{post}</p>
</div>
));
};
render() {
return (
<div className="App">
<Scroll displayPost={this.displayPost} posts={this.state.posts} />
</div>
);
}
}
class Scroll extends React.Component {
render() {
return (
<div className="post">
Posts: {this.props.displayPost(this.props.posts)}
</div>
);
}
}
I get data into the props of my component by using getStaticProps. I then want to filter that data before I use it in the component. Usually I'd do this in componentDidMount, but that's not possible as it seems like the props are populated after componentDidMount is called.
What's the best practice for working around this?
Here's my current code:
class Definition extends Component {
constructor({ router }, ...props) {
super(props);
this.state = {
songsArray: [],
};
}
filterSpotifyResults = () => {
const filteredArray = [];
this.props.songsData.tracks.items.forEach((obj) => {
if (obj.explicit === true) {
return;
} else {
filteredArray.push(obj);
}
});
this.setState({ songsArray: filteredArray });
};
componentDidMount = () => {
this.filterSpotifyResults();
};
render() {
if (this.props.router.isFallback) {
return <h4>Loading...</h4>;
}
return (
<div>
<h3>this is where the definition will go</h3>
<ul>
{this.props.wordsData.definitions.map((obj, i) => (
<li key={i}>{obj.definition}</li>
))}
</ul>
<iframe
src={`https://open.spotify.com/embed/track/${this.props.songsData.tracks.items[0].id}`}
width="300"
height="380"
allowtransparency="true"
allow="encrypted-media"
></iframe>
</div>
);
}
}
export default withRouter(Definition);
export async function getStaticProps(context) {
const wordsRes = await fetch(
`https://wordsapiv1.p.rapidapi.com/words/${context.params.word}/definitions`,
{
method: "GET",
headers: {
"x-rapidapi-key": process.env.NEXT_PUBLIC_DB_KEY,
"x-rapidapi-host": "wordsapiv1.p.rapidapi.com",
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const songsRes = await fetch(
`https://api.spotify.com/v1/search?q=${context.params.word}&type=track`,
{
method: "GET",
headers: {
authorization:
"Bearer " + process.env.NEXT_PUBLIC_ENV_SPOTIFY_ACCESS_TOKEN,
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const wordsData = await wordsRes.json();
const songsData = await songsRes.json();
return {
props: {
wordsData,
songsData,
searchTerm: context.params.word,
},
};
}
Best practice would definitely be filtering the data on the server, already in your getStaticProps.
So move the filtering there, and only return the data you actually want to use/render.
sorry i'm new to React. I'm trying to make a basic social network to learn react.
Context:
When i click on the "like" button, the setState should call the function to update the state of my component, but it is updated only when i refresh the page. I think the ComponentDidUpdate function isn't called like it should. What did i do wrong? Thanks for your help!
Here are the parts of the code :
Like button component:
class Like_Button extends React.Component {
constructor(props) {
super(props);
this.state = {liked : "Like"};
}
isliked(){
fetch("likes_of_user/")
.then(res => res.json())
.then((result) => {
result.map(x => {if(this.props.pk == x.liked_post){this.setState({liked: "Unlike"});}});
})
}
componentDidMount() {
this.isliked();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.liked !== this.state.liked) {
this.isliked();
}
}
render() {
return (
<button className = "buttons" onClick={() => {
var csrftoken = getCookie('csrftoken');
fetch(`like_post/${this.props.pk}`, {method: "POST", headers: {'Accept': 'application/json', 'Content-Type': 'application/json','X-CSRFToken': csrftoken}})
}}>{this.state.liked}</button>
)
}
}
Newsfeed component:
class Newsfeed_comp extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("get_newsfeed/")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map((item ,index) => (
<li className="postbox" key={`${item}${index}`}>
{item.author}
{item.date}
{item.content}
<Like_Button pk={item.id} />
</li>
))}
</ul>
);
}
}
}
ReactDom render:
ReactDOM.render(<Newsfeed_comp />, document.getElementById("newsfeed_view"))
Try something like this:
LikeButton.js
import React, { useEffect, useState } from 'react';
export default function LikeButton({ pk }) {
const [like, setLike] = useState(false);
useEffect(() => {
const fetchLike = async () => {
const res = await fetch("likes_of_user/");
const result = await res.json();
if (result.length > 0) {
setLike(result.find(item => item.liked_post === pk));
}
};
try {
fetchLike();
} catch (error) {
// handle error
}
});
const handleClick = async () => {
const csrftoken = getCookie('csrftoken');
return fetch(`like_post/${pk}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
method: 'POST',
});
};
return (
<button className='buttons' onClick={handleClick}>
{like}
</button>
);
};
NewsFeed.js
import React, { useEffect, useState } from 'react';
export function NewsFeed() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
const getNewsFeed = async () => {
const res = await fetch('get_newsfeed/');
const result = await res.json();
setIsLoaded(true);
setItems(result);
};
try {
getNewsFeed();
} catch (error) {
setIsLoaded(true);
setError(error);
}
});
if (error) return <div>Error: {error.message}</div>;
if (isLoaded) return <div>Loading...</div>;
const list = items.map((item) => (
<li className='postbox' key={item.content}>
{item.author}
{item.date}
{item.content}
<LikeButton pk={item.id} />
</li>
));
return <ul>{list}</ul>;
};
App.js
ReactDOM.render(<NewsFeed />, document.getElementById('newsfeed_view'));
Looks like you've reversed your logic, i.e. your button directly updates the data in the backend but does nothing to update component state, so the componentDidUpdate isn't called as you've seen. The refresh is required so the component is remounted and the componentDidMount can fetch the likes data.
Try instead to update local state first, then use componentDidUpdate to issue the side-effect of updating the backend.
constructor(props) {
super(props);
this.state = { liked: true };
}
isliked() {
fetch("likes_of_user/")
.then(res => res.json())
.then((result) => {
result.map(x => {
if (this.props.pk === x.liked_post) {
this.setState({ liked: false });
}
});
})
}
componentDidUpdate(prevProps, prevState) {
if (prevState.liked !== this.state.liked) {
const csrftoken = getCookie('csrftoken');
fetch(
`like_post/${this.props.pk}`,
{
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
}
);
}
}
<button
className="buttons"
onClick={() => this.setState(
prevState => ({ liked: !prevState.liked })
)}
>
{this.state.liked ? "Liked" : "Unliked"}
</button>
I'm trying to build a todo page where I can input todos in my input field. All todos will be rendered below. I managed to build a form where I can type in a todo title and send it to my database. A small problem I'm having here is that I need to refresh the page after pushing the add button to see the new list. I assume this is because I use componentDidMount and this updates only at page refresh. Any idea how I can do this at page refresh (componentDidUpdate) AND at state change ?
FRONT-END
import React from 'react'
import './Todo.css'
import Todoitem from '../components/Todoitem'
import axios from 'axios'
import qs from "qs"
import DefaultLayout from "../layout/Default"
class Todo extends React.Component {
constructor() {
super()
this.state = {
title:"",
todos:[]
}
this.handleChange=this.handleChange.bind(this)
this.handleSubmit=this.handleSubmit.bind(this)
}
componentDidMount(){
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/todo`,
withCredentials: true
})
.then(response => {
console.log(response)
let todolist = response.data;
this.setState({todos:todolist})
})
.catch(error => {
console.log("You've made an error when getting the todos charles: ",error)
})
}
handleChange(event){
event.preventDefault()
let name = event.target.name
let value = event.target.value
this.setState({
[name]:value
})
console.log(this.state.title)
}
handleSubmit(event){
event.preventDefault()
if (!this.state.title) {
debugger
}
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify({title: this.state.title}),
headers: {"content-type": "application/x-www-form-urlencoded"},
withCredentials: true
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error.response)
})
}
handleDelete(todoId){
axios
.delete(`${process.env.REACT_APP_API_BASE}/todo/${todoId}`)
.then(response => {
const remainingTodos = this.state.todos.filter(element => element._id !== todoId)
this.setState({
todos: remainingTodos
})
})
.catch(err => console.log(err))
}
render() {
return (
<div>
<DefaultLayout>
<h1>To-do things for this app</h1>
<h2 className="todotitle">Add your to-do here, Charles!</h2>
<form className="todocontainer" onClick={this.handleSubmit}>
<div className="inputbuttonandfield">
<div className="inputcontainer">
<div className="captionpart">
<label className="captionlabel" htmlFor="title">Add to-do:</label><br></br>
<input className="captionform" type="text" name="title" value={this.state.title} placeholder="Type your to-do here!" onChange={(e) => this.handleChange(e)}></input>
<button className="shootbutton">Add!</button>
</div>
</div>
</div>
</form>
{
this.state.todos.map(element=> (
<div className="todosoverviewlister" key={element._id}>
<Todoitem id={element._id} title={element.title} />
<button className="tododelete" onClick={()=> this.handleDelete(element._id)}>Delete</button>
</div>
))
}
</DefaultLayout>
</div>
)
}
}
export default Todo
Todomodel
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const todoSchema = new Schema({
title: String
})
const Todo = mongoose.model("todos",todoSchema)
module.exports = Todo
BACKEND
//request todos
router.get("/todo", (req,res) => {
Todo
.find()
.then(response => {
res.json(response)
})
.catch(error => {
res.json(error)
})
})
//delete todo
router.delete("/todo/:id", (req,res)=>{
Todo
.findByIdAndDelete(req.params.id)
.then(response => {
res.json(response)
})
.catch(error => {
res.json(error)
})
})
You can either update the state or sync up with database by sending another GET. Let me break it down into 2 solutions:
Just update the state
Make a GET request after the POST request and update the state
Just update the state
// you code ...
handleSubmit(event){
event.preventDefault()
const newTodo = { title: this.state.title }; // extract your todo into const
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify(newTodo), // send todo in the POST
headers: {"content-type": "application/x-www-form-urlencoded"},
withCredentials: true
})
.then((response) => {
console.log(response)
this.setState(prevState => ({ // immutably update the state
todos: [...prevState.todos, newTodo]
}));
})
.catch((error) => {
console.log(error.response)
})
}
// your code ...
Send GET after POST:
// your Todo component
class Todo extends React.Component {
constructor() {
super();
this.state = {
title: "",
todos: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// extract method for loading TODOs (your previous componentDidMount)
loadTodos = () => {
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/todo`,
withCredentials: true,
})
.then((response) => {
console.log(response);
let todolist = response.data;
this.setState({ todos: todolist });
})
.catch((error) => {
console.log(
"You've made an error when getting the todos charles: ",
error
);
});
}
componentDidMount() {
this.loadTodos(); // use the extracted method
}
handleChange(event) {
event.preventDefault();
let name = event.target.name;
let value = event.target.value;
this.setState({
[name]: value,
});
console.log(this.state.title);
}
handleSubmit(event) {
event.preventDefault();
if (!this.state.title) {
debugger;
}
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify({ title: this.state.title }),
headers: { "content-type": "application/x-www-form-urlencoded" },
withCredentials: true,
})
.then((response) => {
console.log(response);
this.loadTodos(); // use the extracted method
})
.catch((error) => {
console.log(error.response);
});
}
handleDelete(todoId) {
axios
.delete(`${process.env.REACT_APP_API_BASE}/todo/${todoId}`)
.then((response) => {
const remainingTodos = this.state.todos.filter(
(element) => element._id !== todoId
);
this.setState({
todos: remainingTodos,
});
})
.catch((err) => console.log(err));
}
render() {
return (
<div>
<DefaultLayout>
<h1>To-do things for this app</h1>
<h2 className="todotitle">Add your to-do here, Charles!</h2>
<form className="todocontainer" onClick={this.handleSubmit}>
<div className="inputbuttonandfield">
<div className="inputcontainer">
<div className="captionpart">
<label className="captionlabel" htmlFor="title">
Add to-do:
</label>
<br></br>
<input
className="captionform"
type="text"
name="title"
value={this.state.title}
placeholder="Type your to-do here!"
onChange={(e) => this.handleChange(e)}
></input>
<button className="shootbutton">Add!</button>
</div>
</div>
</div>
</form>
{this.state.todos.map((element) => (
<div className="todosoverviewlister" key={element._id}>
<Todoitem id={element._id} title={element.title} />
<button
className="tododelete"
onClick={() => this.handleDelete(element._id)}
>
Delete
</button>
</div>
))}
</DefaultLayout>
</div>
);
}
}
export default Todo;
I believe the issue is that you're not updating the state when you submit (during the add operation). In your delete, you correctly keep the list in state synced with the list on the server by removing the element locally as well. In the add, you should to something similar by adding the new element to the list in state (or more precisely, make a deep copy and overwrite the one in state). That should do it.
There is no need to refetch the entire list from the server unless there are multiple users operating on the same list. If that's the case, you can add a get() call in the response of your submit. As long as the response of that operation writes to state, it will update correctly. But again, avoid that unless you need it as it will make your app slower and less responsive.
The startUpload method inside <Items /> will call the callback function to update the state of the parent component each time it receives a response, This causes <Items /> to be rendered unnecessarily multiple times.
My expected effect is that after the state is updated, only the <Results /> component needs to be re-rendered
class Parent extends React.Component {
constructor(props) {
super(props);
this.getResponseData = this.getResponseData.bind(this);
this.state = {
responseData: [],
}
}
getResponseData(data) {
this.setState({
responseData: this.state.responseData.concat(data),
})
}
render() {
return (
<div>
<Items files={this.props.files} updateData={this.getResponseData}/>
<Results data={this.state.responseData}/>
</div>
)
}
}
class Items extends React.Component {
componentDidMount() {
this.startUpload(this.props.files)
}
startUpload(files) {
const URL = 'http://localhost:3000/upload';
for (let i = 0, len = files.length; i < len; i++) {
const data = new FormData();
data.append('img', files[i]);
fetch(URL, {
method: 'post',
body: data,
})
.then(checkStatus)
.then(parseJSON)
.then(data => {
this.props.updateData(data);
})
}
}
render() {
const filesData = this.getFilesData(this.props.files);
let imageItems = filesData.map((current) => {
return (
<div>
<img src={current.objectURL} alt="preview"/>
</div>
)
});
return <div>{imageItems}</div>;
}
}
function Results(props) {
const responseData = props.data;
let result = [];
if (responseData.length) {
result = responseData.map(current => {
return <p>{current}</p>
});
return <div>{result}</div>
}
}
https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate You can use shouldComponentUpdate to inform your component whether or not should re-render or not based on a change in state/props. Using this knowledge, you can implement the logic you need in order to render the Items/Results component only when needed.
Hope that helps!