Updating a page at refresh AND change of state - javascript

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.

Related

TypeError: Cannot read property 'value' of undefined in React JS

I am fairly new to react JS and I've implemented 2 dropdown boxes whose options are displayed by hitting an API. I want to obtain the selected value but I am getting the following error:
TypeError: Cannot read property 'value' of undefined.
As of now I just tried to obtain the value from one dropdown.
This is my code,
import React from 'react';
import Select from 'react-select';
import './Search.css';
class SearchForm extends React.Component {
constructor(props){
super(props);
this.state={
filtered :[],
values1 :[],
values2 :[],
selectedCategory:''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
try{
this.setState({selectedCategory: event.target.value});
} catch (err) {
console.error('err', err);}}
componentDidMount() {
this.fetchData1()
this.fetchData2()
}
fetchData1 = async () => {
await fetch('/category/all')
.then(res => res.json())
.then(res =>
this.setState({
values1: res,
}),
)
.catch(error => console.log(error))
}
fetchData2 = async () => {
await fetch('/loc/all')
.then(res => res.json())
.then(res =>
this.setState({
values2: res,
}),
)
.catch(error => console.log(error))
}
async handleSubmit(event){
event.preventDefault();
try{
const url ='/jobs/all/'
const Response = await fetch((url),{
method: `GET`,
mode: 'cors',
headers: {
'Accept': 'application/json'
}});
const filtered = [];
const res = await Response.json();
const Location = this.menu2.value
const Category = this.menu1.value
console.log(Location)
console.log(Category)
Object.keys( res ).forEach( function( key ) {
if( res[key].location === Location && res[key].category === Category ) {
filtered[key] = res[key];}
});
this.setState({filtered})
console.log(this.state.filtered)
}
catch (err) {
console.error('err', err);}
};
render() {
let option1 = []
if (this.state.values1) {
this.state.values1.forEach(eachCategory => {
let Category = {}
Category.value = eachCategory.id
Category.label = eachCategory.category
option1.push(Category)
})
}
console.log(option1)
let option2 = []
if (this.state.values2) {
this.state.values2.forEach(eachLocation => {
let Location = {}
Location.value = eachLocation.id
Location.label = eachLocation.location
option2.push(Location)
})
}
console.log(option2)
return (
<div>
<form action="/search" onSubmit={this.handleSubmit.bind(this)}>
<Select options={option1} value={this.state.selectedCategory} placeholder='Category' onChange={this.handleChange}>
</Select>
<Select options={option2} placeholder='Location'/>
<button>Find</button>
</form>
{this.state.filtered.map((data)=>{
// return <div>{data.location}</div> // you can render here list items
return (
<div className="flex-container">
<div key={data.id}>
<div>Job Title: {data.category}</div>
<div>Location: {data.location}</div>
<div>Position: {data.position}</div>
<div>Duration: {data.duration}</div>
<div>Skills Required: {data.skills_req}</div>
<div>Apply By: {data.apply_by}</div>
<div>Starting Date: {data.starting_date}</div>
<div>Stipend: {data.stipend}</div>
<div>About Work: {data.about_work}</div>
<div>Perks: {data.perks}</div>
</div>
</div>)
})}
</div>
);
}
}
export default SearchForm;
Please point out where am I wrong.
Well, according to the react-select documentation you're handling onChange in a wrong way. It should just be like this.
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
https://www.npmjs.com/package/react-select
So in your case, you just have to change this.setState({selectedCategory: event.target.value}); to this.setState({selectedCategory: event}); :
handleChange(event) { //give it a proper name, say selectedValue instead ofevent'
try{
this.setState({selectedCategory: event}); //no need of event.target.vaue; in fact that will be undefined
} catch (err) {
console.error('err', err);}}
Please note that this Select is different from normal select where you get the value using e.target.value in the handleChange method. The Select comes in with react-select package and hence you need to follow the usage accordingly.
You didn't send event to handleChange method.
Try:
onChange={e => this.handleChange(e)}

Display Dynamic Data in Render Method

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;

React and this.props.results.map is not a function

after tried to set a simple search in the logs show this:
everything seems very ok and standardized so a couple of searches i still have any idea why this error is happening.
i've tried with fetch and the same result
please, someone can elucidate, why this error is happening?
the code:
import React, { Component } from "react";
import axios from "axios";
import Suggestions from "./suggestions";
class Search extends Component {
constructor(props) {
super(props);
this.state = {
term: "",
error: false,
results: []
};
}
onChange(e) {
this.setState(
{ term: e.target.value },
() => {
axios
.get("/search?q=" + this.state.term)
.then(res => this.setState({ results: res.data }))
.catch(() => this.setState({ error: true }));
}
);
}
render() {
return (
<div id="searchbox">
<div>
<form>
<input
ref={input => {
this.search = input;
}}
value={this.state.term}
onChange={this.onChange.bind(this)}
type="text"
placeholder="Search..."
/>
<button type="submit">
<i className="search icon" />
</button>
<Suggestions results={this.state.results} />
</form>
</div>
</div>
);
}
}
export default Search;
the suggestion
import React from "react";
const Suggestions = props => {
const resultList = props.results.map(r => <li key={r.id}>{r.title}</li>);
return <ul>{resultList}</ul>;
};
export default Suggestions;
response
res.data will give you the entire parsed JSON response, but you want the array that is the value of the posts property instead.
axios
.get("/search?q=" + this.state.term)
.then(res => this.setState({ results: res.data.posts }))
.catch(() => this.setState({ error: true }));
IMO you need use
axios
.get("/search?q=" + this.state.term)
.then(res => this.setState({ results: res.data.posts }))
.catch(() => this.setState({ error: true }));
and not
axios
.get("/search?q=" + this.state.term)
.then(res => this.setState({ results: res.data }))
.catch(() => this.setState({ error: true }));

Post request in react

Unable to send POST request by using fetch method
I am able to fetch request from the server but unable to Post the request dynamically. I am taking input value but it showing the error below:
Error: SyntaxError: "JSON.parse: unexpected character at line 1 column 1 of the JSON data"
const url = "http://some domain/api/tweets";
const input = {tweet: {body: ''}};
class App extends Component{
constructor(props){
super(props);
this.state={
error:null,
isLoaded:false,
data: [],
value: ''
}
this.onSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
this.setState({value: e.target.value});
}
componentDidMount() {
fetch("http://some domain/api/tweets")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result.data
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
handleSubmit(e){
e.preventDefault()
fetch(url, {
method: 'POST',
body: JSON.stringify(this.state.value),
headers:{
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
}
render(){
const { error, isLoaded, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="list-type5">
<form onSubmit={this.onSubmit} >
<input type="text" placeholder="Body" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" />
</form>
<ol>
{data.map(i => (
<div key={i.id}>
<li >
<a> <b> ID:</b> {i.id} | <b> Body:</b> {i.body} | <b> Views:</b> {i.views} </a>
</li>
</div>
))}
</ol>
</div>
);
}
}
}
export default App;
Help would be appreciated.
I just changed and remove const input = {tweet: {body: ''}}; from top and write it into the handleSubmit function just check it below:-
handleSubmit(e){
e.preventDefault()
const input = {tweet: {body: this.state.value}};
fetch(url, {
method: 'POST',
body: JSON.stringify(input),
headers:{
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
}
handleSubmit(e) {
fetch("http://some domain/api/tweets", { /*your object...*/ })
.then(res => res.text()) // previous .then(res => res.json())
.then(text => console.log(text))
...
}
The res.json() call seems to be wrong at this place, as your response might not be a valid JSON object. Maybe try res.text() instead and console.log your response to see what it tells you.
More infos about the response object you can find over here: MDN - Response Object

componentDidUpdate() working mostly, but not rendering new data

I am using the componentDidUpdate() method and for the most part, it is doing what it should. It runs the function to get the data from the API as well as logs it to the console. The problem is that it does not render the new data on the front end. The only time it renders the new data is if the component actually mounts (if the page is refreshed). I feel like I'm very close, but have hit a dead end. Here is my code:
import React from 'react';
import Nav from './Nav';
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
APIData: []
}
}
getAPIData() {
const url = `http://localhost:3001${this.props.location.pathname}`;
return fetch(url, {
method: 'GET',
mode: 'CORS',
headers: {
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log(data);
return data;
}).catch(err => { console.log('Error: ', err) });
};
dataList() {
return (
<div>
{this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)}
</div>
)
}
componentDidMount() {
console.log(this.props.location.pathname);
this.getAPIData()
.then(data => {
console.log('in List.js ', data);
this.setState({
APIData: data
});
});
}
componentDidUpdate(prevProps, prevState) {
console.log(this.props.location.pathname);
// only update if the data has changed
this.getAPIData()
.then(data => {
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
console.log(data);
});
}
render() {
return (
<div>
<Nav />
<br />
<br />
<br />
<br />
<div>
{/* {this.state.APIData.map((APIData) => (
<p> And the data returned is -> {APIData.firstName}
{APIData.lastName} !</p>
)
)} */}
{this.dataList()}
</div>
</div>
);
}
}
export default List;
I think it may be this block:
if (prevProps.data !== this.props.data) {
this.setState({
APIData: data
});
}
Are you actually passing a data prop to this component?
If not, then it would be checking if undefined !== undefined and never executing.
If you are, then you might check if the data reference is actually changing, or you're just mutating the inside of the object.

Categories