useState not updating state object after fetching data from API endpoint - node.js

Just starting to learn React. I wrote a simple web server that serves data from a /users endpoint. I am fetching that data in useState hook of a component, but the state object does not seem to be updating. Anyone that can point me in the right direction to get the response object data to render in the ordered list would be greatly appreciated.
import { useState, useEffect } from 'react';
const UserList = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("http://localhost:8080/users",
{
method: "GET"
}
)
.then(res => res.json())
.then(res => {
setUsers(res.data);
})
.catch(err => {
console.log(err);
});
}, []);
return (
<div>
<h1>User List</h1>
<ol>
{
users.forEach(u => {
<li>{u}</li>
})
}
</ol>
</div>
)
}
export default UserList;

Issue
You are using Array.prototype.forEach to try and render your state. .forEach is a void return, however, so nothing is returned to be rendered.
Solution
Use Array.prototype.map to map the users state to JSX.
{
users.map((u, index) => (
<li key={index}>{u}</li>
))
}

Related

Why data fetched from backend shows in console but not in the webpage?

Here I've fetched workouts from backend through api. It shows output in the console but unable to map through workouts state in the webpage.
import React, { useEffect, useState } from "react";
const Home = () => {
const [workouts, setWorkouts] = useState([]);
useEffect(() => {
const fetchWorkouts = async () => {
const response = await fetch("http://localhost:4000/api/workouts");
const json = await response.json();
if (response.ok) {
console.log('success');
console.log(json);
setWorkouts(json);
}
};
fetchWorkouts();
}, []);
return (
<div className="home">
<div className="workouts">
{workouts &&
workouts.map((workout) => {
<p key={workout._id}>{workout.title}</p>;
})}
</div>
</div>
);
};
export default Home;
You forgot to return it. Do this:
return <p key={workout._id}>{workout.title}</p>;
or you can also do this:
{workouts?.map((workout) => (
<p key={workout._id}>{workout.title}</p>
))}
You can remove the bracket on the map:
workouts.map((workout) =>
<p key={workout._id}>{workout.title}</p>;
)}
You're not returning anything. Either explicitly use the return keyword to return the element or You can do this in a more appropriate way like this.
{
workouts &&
workouts.map((workout) => (
<p key={workout._id}>{workout.title}</p>
))
}

How to update value in client site using mongodb?

I am using React in front-end and Node and MongoDB in Back-end. I have created a custom hook from where I am loading the data. The following is the custom hook
import { useEffect, useState } from "react";
const useItems = (id) => {
const [item, setItem] = useState([]);
useEffect(() => {
fetch(`http://localhost:5000/inventory/${id}`)
.then((res) => res.json())
.then((data) => setItem(data));
}, [id]);
return [item];
};
export default useItems;
And this is the component where I am calling the custom hook to load the data.
import React, { useEffect, useState } from "react";
import "./Inventory.css";
import { useParams } from "react-router-dom";
import useItems from "../../hooks/useItems";
const Inventory = () => {
const { id } = useParams();
const [item] = useItems(id);
const quantityDecrease = (newQuantity) => {
let quantity = parseInt(newQuantity) - 1;
const updateQuantity = { quantity };
const url = `http://localhost:5000/inventory/${id}`;
fetch(url, {
method: "PUT",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(updateQuantity),
})
.then((res) => res.json())
.then((data) => {
console.log("success", data);
alert("saved");
});
};
return (
<div>
<div className="col-lg-6">
<p className="inventory-textbox">
<strong>Quantity :</strong> {item.quantity}
</p>
</div>
<button onClick={() => quantityDecrease(item.quantity)}>
Delivered
</button>
</div>
);
};
export default Inventory;
Whenever the Delivered button is clicked the quantityDecrease function is executed and the quantity of the item is decreased by one. Now, my database is working fine. I am being able to update both client and server site but I have to reload the page in order to see the change in the ui. Is there a way I do not have to reload to see the change?
try using the item data as useEffect dependency. it may solve your problem.

how to get a final snapshot for a react component after fetching datas?

I'm testing a react component UI. Within this component, a request is sent and fetch data to rerender UI. Now a snapshot before the request fetch data is produced. How to get a snapshot after the request?
// component.js
class Text extends Component {
componentDidMount() {
this.load()
}
load = () => {
const {id} = this.props
fetch('/abc').then(data => {
this.setState({data})
})
}
render() {
if(!this.state.data) return null
const {data} = this.state
return (
<div>
{data}
</div>
)
}
}
//jest
describe('test', () => {
beforeEach(() => {
fetch.mockImplementation(()=> new Promise(resolve=>resolve(4)))
});
test('base render', async () => {
const wrapper = await render(<Text/>)
expect(toJson(wrapper)).toMatchSnapshot()
})
})
//received snapshot
null
//expected snapshot
<div>
4
</div>

axios does not work with while fetch does

i want to get data (array) from /{url} and i tried this code
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
fetch('/main')
.then(res => res.json())
.then(list => this.setState({ list }))
}
this is working fine but then i decided to switch to axios and tried literally same code
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
axios.get('/main')
.then(res=> res.json())
.then(list => this.setState({list}))
}
but it did not worked and gave me error in this line: .then(res=> res.json())
so i do not know what is problem if anyone knows the clue i will be glad if you tell me
// Fetch the list on first mount
componentDidMount() {
this.getList();
}
// Retrieves the list of items from the Express app
getList = () => {
axios.get('/main')
.then(res=> res.data)
.then(list => this.setState({list}))
.catch(error => this.setState({error: error.message}))
}
It is because axios has different response, instead of res.json() return data already like : return res.data or pass it to state directly something like
getList = () => {
axios.get('/main')
.then(res=> this.setState({list: res.data}))
i would recommend some changes in your design, as im using axios successfully in many projects, its not a requirement but it helps and is working very reliable:
Create a service like api.js
import axios from 'axios';
export default axios.create({
baseURL: `http://my.api.url/`
});
Then you can use it like this
import API from '../Services/api'; // this is your service you created before
LoadStats = async event => {
try {
var response = await API.get('api/targetroute');
if (response.data != null) {
this.setState({
stats: {
mydata: response.data
}
});
console.log('Receiving result: '+JSON.stringify(response.data));
}
} catch (error) {
console.log('ERROR: '+error)
}
}

ReactJS - item doesn't append instantly only refreshing page

I'm using ReactJS, NodeJS, MongoDB.
In my project I have a Task List and I'm adding new tasks (this works!) but only appends/show that new task when I refresh the page but I'm using ReactJS so I can have a more responsive/interactive website but I'm new at this and I'm still learning and I don't know what to do...Maybe I have to make something with the state?!
Hope you can help me! Thanks!
Here's my NewTask Component:
import React, { Component } from 'react';
import './NewTask.css';
class NewTask extends Component {
constructor(props) {
super(props);
this.state = {
projectId: null,
tasktitle: '',
taskcomment: ''
};
}
postDataHandler = () => {
let data = {
tasktitle: this.state.tasktitle,
taskcomment: this.state.taskcomment
};
fetch(`/dashboard/project/${this.props.projectId}/tasks/newtask`, {
method: 'POST',
data: data,
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then(response => { return response.json() })
.catch(error => console.error('Error:', error));
}
render() {
return (
<div>
<input type='text' className='form-control input--task' required placeholder='Task Title' value={this.state.tasktitle} name='tasktitle' ref='tasktitle' onChange={(event) => this.setState({ tasktitle: event.target.value })} />
<button type='submit' className='btn btn-default button--newtask' value='Submit' onClick={this.postDataHandler}>Add Task</button>
</div>
);
}
}
export default NewTask;
Here's server side to create new task
//Create New Task
exports.create_new_task = (req, res) => {
let projectid = req.params.id;
Task.create({
tasktitle: req.body.tasktitle,
taskcomment: req.body.taskcomment,
project: req.params.id
}, (err, tasks) => {
if (err) {
console.log(err);
}
Project.findById(projectid, (err, project) => {
if(err) {
console.log(err);
}
project.tasks.push(tasks._id);
project.save();
console.log('NEW Task added to project: ' + projectid)
res.json(tasks)
});
});
};
Here's my Tasks Component
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrashAlt, faEdit } from '#fortawesome/free-solid-svg-icons'
import './Tasks.css';
class Tasks extends Component {
constructor(props) {
super(props);
this.state = {
projectId: props._id,
tasks: []
};
}
componentDidMount() {
fetch(`/dashboard/project/${this.props.projectId}/tasks`)
.then(response => {
return response.json()
}).then(task => {
this.setState({
tasks: task.tasks
})
}).catch(error => console.error('Error:', error));
}
render() {
const fontawesomeiconStyle = {
fontSize: '1em',
color: '#8e8359',
textAlign: 'center'
}
const listStyle = {
display:'grid',
gridTemplateColumns:'2fr 1fr',
alignItems: 'center',
justifyItems: 'center'
}
const { tasks } = this.state;
return (
<div>
<ul className="task-list">
{tasks.map(task =>
<li key={task._id} style={listStyle}>{task.tasktitle}
<div>
<form method='POST' action={`/dashboard/project/${this.props.projectId}/tasks/delete?_method=DELETE&taskId=${task._id}`}>
<button className="button--tasks" >
<FontAwesomeIcon style={fontawesomeiconStyle} icon={faTrashAlt} />
</button>
</form>
</div>
</li>
)}
</ul>
</div>
);
}
}
export default Tasks;
Here's a gif so you can see what's really happening, only appends the
new task when I refresh the page..
You can return a task object from your POST method and then append to the existing task list. Something like this:
postDataHandler = () => {
/* removed for brevity */
.then(response => response.json())
.then(response => {
// append to existing list of tasks
this.props.appendTask(response);
})
.catch(error => console.error('Error:', error));
}
// method in parent component
// passed down through props
appendTask = task => {
let tasks = [...this.state.tasks];
tasks.push(task);
this.setState({tasks});
}
Your list will only re-render when a change in state affects what's being rendered. You either need to re-fetch the full list of tasks or manually append your new task, which is what's being done in the above example.
Here is a more complete example:
class TaskList extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [
{/* task 1 */},
{/* task 2 */}
]
}
}
appendTask = task => {
let tasks = [...this.state.tasks];
tasks.push(task);
this.setState({tasks});
}
render() {
const { tasks } = this.state;
return (
<div className="tasks">
<ul>
{tasks.map(task => <TaskItem task={task}/>)}
</ul>
<NewTask appendTask={this.appendTask}/>
</div>
);
}
}
class NewTask extends Component {
/* ... */
postDataHandler = () => {
/* ... */
.then(response => response.json())
.then(response => {
// append to existing list of tasks
this.props.appendTask(response);
})
.catch(error => console.error('Error:', error));
}
/* ... */
}
After you POST the new item your have to GET the new item as well in your item list component.
You could put both the NewTask and TaskList components in one class component that could perform a GET after the POST promise resolves and update the state with the new item.
Or you could use Redux or another state handler that would use actions that trigger things in the right order.
Look, you're making a POST request to the backend, right?
As it seems, it gets stored correctly, but you're NOT doing anything with it. One way is to do it in a similar fashion as #wdm suggested, or just append the 'task' to your current state using setState, but only if it was posted in the first place, right? :)
Make sure that the response from the backend is the data you posted, use that response and append it to the already existing state using the ... spread operator. The setState will trigger a re-render and you'll have all your tasks listed.

Resources