My state looks like:
const [profileList, setProfileList] = useState([]);
function looks like:
const getFriends = async () => {
await axios
.get(APICallString + id)
.then((response) => {
(async () => {
let p = [];
for await (const e of response.data) {
axios
.get(
"http://localhost:3002/persons/getById/" +
e.friendId
)
.then((response) => {
p.push(response.data);
})
.catch((error) => {
console.log(error);
});
}
console.log(p);
setProfileList(p);
})();
})
.catch((error) => {
errMsg = error.message;
console.log(error);
});
setLoaded(true);
};
Button:
<Button className="p-2 border" onClick={() => getFriends()}>
Search
</Button>
The function works as expected, when I change state by typing in my textbox... but it doesnt change state its self.
Why?
Related
I have data
const data = {
animal: 'cat',
isBlack: true
}
What I wish my component to render some text if the cat is black and not to render if the cat is not black. I have several tests
describe('About', () => {
const data = {
animal: 'cat',
isBlack: true
}
beforeEach(() => {
fetchMock.getOnce('/api/animals', {data});
});
afterEach(() => {
fetchMock.reset();
expect(fetchMock.done()).toBe(true);
});
it('should render with first text', () => {
expect(instance.getByText('Black Cats are awesome')).toBeVisible();
})
it('should render with second text', () => {
expect(instance.getByText('Black Cats are my favourite')).toBeVisible();
})
it('should render with second text', () => {
expect(instance.getByText('Black Cats rule')).toBeVisible();
})
it('should render no text', () => {
expect(instance.queryByText('Black Cats rule')).toBeNull();
})
)
So basically as soon as isBlack is false no text should be rendered. So, what I have now, I have fetchMock in every test, my desire is that I do run fetchMock before each and change the response for the false cases in the tests accordingly. Is there a way?
I cannot see yout code but assuming it is more less like this:
the component CatComponent
import React from "react";
import { fetchData } from "./fetchCatData";
const CatComponent = () => {
const [data, setData] = React.useState({});
React.useEffect(() => {
const fetchCatData = async () => {
const data = await fetchData();
setData(data);
};
fetchCatData();
}, []);
return (
<div>
{data && data.isBlack && (
<>
<p>Black Cats are awesome</p>
<p>Black Cats are my favourite</p>
<p>Black Cats rule</p>
<p>Black Cats rule</p>
</>
)}
</div>
);
};
export default CatComponent;
the fetch api: fetchCatData
export const fetchData = async () => {
return Promise.resolve({ animal: 'dog', isBlack: false })
}
We can have tests like this:
import { render, screen, waitFor } from "#testing-library/react";
import CatComponent from "../CatComponent";
const mockFetchData = jest.fn();
jest.mock("../fetchCatData", () => ({
fetchData: () => mockFetchData(),
}));
describe("should work tests", () => {
it("no text", async () => {
render(<CatComponent />);
await waitFor(() => {
expect(
screen.queryByText("Black Cats are awesome")
).not.toBeInTheDocument();
});
});
it('texts present', async () => {
mockFetchData.mockResolvedValue({ isBlack: true })
render(<CatComponent />);
await waitFor(() => {
expect(
screen.getByText("Black Cats are awesome")
).toBeInTheDocument();
});
expect(screen.getByText('Black Cats are my favourite')).toBeInTheDocument();
})
});
im working through fullstackopen course along TOP, every excercise went well so I drifted off the course to build simple todo app to solidify the knowledge i gained so far. So i developed front end with react, then the back end with node express connected to mongoDB. All seemed fine but then the delete request stopped working - every other request works fine, only the delete causes errors. After requesting a delete the page crashes, BUT in the database the request is fulfilled and the note is removed. So when I reconnect to the node server and refresh the page, the content is up to date and everything seems to work again.
RESTclient is saying that delete request works fine. But in the browser, when i click delete button, after like a second the app crashes and this is shown in the console:
Notes.js:20 Uncaught TypeError: Cannot read properties of null (reading 'id')
at Notes.js:20:27
at Array.map (<anonymous>)
at b (Notes.js:19:16)
at xo (react-dom.production.min.js:167:137)
at Pi (react-dom.production.min.js:197:258)
at Eu (react-dom.production.min.js:292:88)
at bs (react-dom.production.min.js:280:389)
at gs (react-dom.production.min.js:280:320)
at vs (react-dom.production.min.js:280:180)
at ls (react-dom.production.min.js:271:88)
server.js:
require("dotenv").config();
const express = require("express");
const morgan = require("morgan");
const cors = require("cors");
const mongoose = require("mongoose");
const Note = require("./models/note");
const app = express();
app.use(express.static("build"));
app.use(express.json());
app.set("json spaces", 2);
app.use(cors());
app.use(morgan("tiny"));
/// DEFINE DEFAULT PORT //
const PORT = process.env.PORT;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
/// DEFINE BASIC ROUTES
app.get("/notes", (request, response, next) => {
Note.find({}).then((notes) => response.json(notes));
});
app.get("/notes/:id", (request, response, next) => {
Note.findById(request.params.id)
.then((note) => {
if (note) {
response.json(note);
} else {
response.status(404).end();
}
})
.catch((error) => next(error));
});
/// DELETE ///
app.delete("/notes/:id", (request, response, next) => {
Note.findByIdAndRemove(request.params.id)
.then((response) => response.status(204).end())
.catch((error) => next(error));
});
/// UPDATE ///
app.put("/notes/:id", (request, response, next) => {
const {content, done} = request.body
Note.findByIdAndUpdate(
request.params.id,
{content, done},
{new: true, runValidators: true, context: "query"},
)
.then(updatedNote => response.json(updatedNote))
.catch(error => next(error))
})
/// ADD ///
app.post("/notes", (request, response, next) => {
const body = request.body;
if (!body.content) {
return response.status(400).json({
error: "content missing",
});
}
const note = new Note({
content: body.content,
done: false,
});
note
.save()
.then((saved) => response.json(saved))
.catch((error) => next(error));
});
/// HANDLE UNDEFINED ROUTES ///
const unknownEndpoint = (request, response) => {
response.status(404).send({ error: "unknown endpoint" });
};
app.use(unknownEndpoint);
/// HANDLE ERRORS ///
const errorHandler = (error, request, response, next) => {
console.error(error.message);
if (error.name === "CastError") {
return response.status(400).send({ error: "malformatted id" });
} else if (error.name === "ValidationError") {
return response.status(400).json({ error: error.message });
}
next(error);
};
app.use(errorHandler);
front-end,
app.js:
import { useState, useEffect } from "react";
import css from "./App.css"
import Button from "./Button";
import Input from "./Input";
import noteService from "./services/NoteService";
import Notes from "./Notes";
function App() {
const [notes, setNotes] = useState([]);
const [newNote, setNewNote] = useState("");
useEffect(() => {
noteService.getAll().then((response) => {
setNotes(response);
});
}, []);
const handleInput = (event) => {
const content = event.target.value;
setNewNote(content);
};
const handleSubmit = (event) => {
event.preventDefault();
const note = { content: newNote, done: false };
noteService
.create(note)
.then((response) => setNotes(notes.concat(response)));
setNewNote("");
};
const handleDelete = (id) => {
noteService
.trash(id)
.then(setNotes(notes.filter((note) => note.id !== id)));
};
const toggleStatus = (id) => {
const note = notes.find((item) => item.id === id);
const updated = { ...note, done: !note.done };
noteService.update(id, updated).then((response) => {
setNotes(notes.map((note) => (note.id !== id ? note : response)));
});
};
const showDone = () => {
noteService.getAll().then((response) => {
setNotes(response.filter((note) => note.done));
});
};
const showUndone = () => {
noteService.getAll().then((response) => {
setNotes(response.filter((note) => !note.done));
});
};
const showAll = () => {
noteService.getAll().then((response) => {
setNotes(response);
});
};
return (
<div className="container">
<h1>TO_DO NOTES</h1>
<div className="header">
<Input action={handleInput} value={newNote} />
<Button text={"Add"} action={handleSubmit} />
</div>
<div>
<Button text="Show All" action={showAll} />
<Button text="Show Done" action={showDone} />
<Button text="Show Undone" action={showUndone} />
</div>
<Notes notes={notes} action={handleDelete} toggle={toggleStatus}/>
</div>
);
}
export default App;
Notes.js:
import Button from "./Button";
import css from "./Notes.css";
const Note = ({ item, action, toggle }) => {
return (
<li
onClick={() => toggle(item.id)}
className={item.done ? "done" : "undone"}
>
{item.content} <Button text="x" action={() => action(item.id)} />
</li>
);
};
const Notes = ({ notes, action, toggle }) => {
return (
<>
<ul>
{notes.map((item) => (
<Note key={item.id} item={item} action={action} toggle={toggle} />
))}
</ul>
</>
);
};
export default Notes;
NoteService.js:
import axios from "axios";
const baseUrl = "/notes";
const getAll = () => {
const request = axios.get(baseUrl);
return request.then((response) => response.data);
};
const create = (newObject) => {
const request = axios.post(baseUrl, newObject);
return request.then((response) => response.data);
};
const update = (id, newObject) => {
const request = axios.put(`${baseUrl}/${id}`, newObject);
return request.then((response) => response.data);
};
const trash = id => {
const request = axios.delete(`${baseUrl}/${id}`)
return request.then(result => result.data)
}
export default {
getAll,
create,
update,
trash,
};
I would really appreciate some help. I compared this project with the other one i have thats structured the same, the other one is working but here cannot figure out what is wrong.
In the Notes.js file within the Notes component, where you are iterating using notes.map, change it to notes?.map and see if that works.
I'm trying to send a PATCH request to my NodeJS API from my react frontend. I want a situation whereby if you click the edit button, the initial name price appears on the input for necessary editing. Then after editing, you can update it. Displaying the initial data works fine , but saving it doesn't work. I get the error: "Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function."
I've looked up the cleanup function, but couldn't make a headway.
Below is my code.
const EditUserForm = () => {
const history = useHistory();
const match = useRouteMatch();
let routeId = match.params.id;
console.log(routeId);
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [item, setItem] = useState({});
const [name, setName] = useState();
const [price, setPrice] = useState();
const handleInputChange = (e) => {
console.log(e.target.value)
const { name, value } = e.target;
setItem({ ...item, [name]: value});
};
const handleSubmit = (e) => {
e.preventDefault();
updateProduct();
history.push('/');
}
const updateProduct = () => {
fetch(`/addproducts/${routeId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: item.name,
price: item.price
}),
})
.then((res) => res.json())
.then((result) => setItem(result))
.catch((err) => console.log('error: ', err))
}
useEffect(() => {
fetch(`/products/${routeId}`, requestOptions)
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setName(result.product.name);
setPrice(result.product.price);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
return (
<form onSubmit={handleSubmit} >
<label>Name</label>
<input
type="text"
name="name"
defaultValue={name}
onChange={handleInputChange}
/>
<label>Price</label>
<input
type="text"
name="price"
defaultValue={price}
onChange={handleInputChange}
/>
<button type="submit">Update</button>
<button>
Cancel
</button>
</form>
)
}
export default EditUserForm
inside "handleSubmit" you are calling "history.push('/')" which produces the error, if you want to change the route then call it in .then of updateProduct
Your useEffect function is applied to each and every rendering and is not cancellable, try rewriting it like this
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(`/products/${routeId}`, {signal, ...requestOptions})
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setName(result.product.name);
setPrice(result.product.price);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
return controller.abort //so the fetch request can be canceled if the effect is re-executed
}, [routeId]); //only needs to rerun on route change
I am not too sure if the handles submit might cause similar problems, in which case you'd want to do something similar to this.
I am a beginner.I am trying to implement a POST request from React.js in a simple form, but I cannot figure out how to send POST request to database. I guess I need <form action="URL"> as well.
Any help will be appreciated.
Below is the code from React.js(frontend)
import GameTestResult from './GameTestResult';
export default function App() {
const[data, setData] = useState([]);
const [formData, setFormData] = useState("");
useEffect (() => {
fetch('http://localhost:3000/game')
.then(res => res.json())
.then(result => setData(result.rows))
.catch(err => console.log("error"))
},[]);
const handleChange = event => {
setFormData(event.target.value)
}
const eventHandler = event => {
event.preventDefault();
setFormData("");
}
return (
<div className="App">
<form method="post" onSubmit = {eventHandler}>
<input value = {formData} onChange = {handleChange} />
<button type="submit">click</button>
</form>
{data && data.map((element, index)=>(
<GameTestResult
name = {element.name}
key={element.index}
/>
))}
</div>
);
}
here is the code from express.js(backend)
var router = express.Router();
const pool = require("../config.js");
var cors = require('cors');
router.get("/game", cors(), (req, res) => {
pool
.query("SELECT * FROM game")
.then((data) => res.json(data))
.catch((e) => {
res.sendStatus(404), console.log(e);
});
});
router.post("/game", (req, res) => {
const { name } = req.body;
pool
.query('INSERT INTO game(name) values($1);', [name])
.then(data => res.status(201).json(data))
.catch(e => res.sendStatus(404));
});
module.exports = router;
Here is what you can do:
Fetch games when component is mounted. And Submit new game when form is submitted.
export default function App() {
const [data, setData] = useState([])
const [formData, setFormData] = useState('')
useEffect(() => {
fetchGames() // Fetch games when component is mounted
}, [])
const fetchGames = () => {
fetch('http://localhost:3000/game', {
method: 'GET',
})
.then((res) => res.json())
.then((result) => setData(result.rows))
.catch((err) => console.log('error'))
}
const saveGames = () => {
fetch('http://localhost:3000/game', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: formData, // Use your own property name / key
}),
})
.then((res) => res.json())
.then((result) => setData(result.rows))
.catch((err) => console.log('error'))
}
const handleSubmit = (event) => {
event.preventDefault()
saveGames() // Save games when form is submitted
}
const handleChange = (event) => {
setFormData(event.target.value)
}
return (
<div className="App">
{/* method="post" not needed here because `fetch` is doing the POST not the `form` */}
{/* Also, note I changed the function name, handleSubmit */}
<form onSubmit={handleSubmit}>
<input type="text" name="name" value={formData} onChange={handleChange} />
<button type="submit">click</button>
</form>
{data &&
data.map((element, index) => (
<GameTestResult name={element.name} key={element.index} />
))}
</div>
)
}
You can read this about how to use fetch and this about how forms work in RecatJS.
Add name as "name" to input
Listen onChange and set data setFormData({[event.target.key]: event.target.value}) the data will be for example {name: "Tony"}
Call POST request on onClick action of button like code below
JSON.stringify(data) is important to convert js object to JSON when sending it to server
import GameTestResult from './GameTestResult'
export default function App() {
const [data, setData] = useState([])
const [formData, setFormData] = useState({})
useEffect(() => {
fetch('http://localhost:3000/game')
.then((res) => res.json())
.then((result) => setData(result.rows))
.catch((err) => console.log('error'))
}, [])
const handleChange = (event) => {
setFormData({ [event.target.name]: event.target.value })
}
const eventHandler = (event) => {
fetch('http://localhost:3000/game', {
method: 'POST',
body: JSON.stringify(formData),
})
.then((res) => res.json())
.then((result) => {
console.log(result)
setFormData('')
})
.catch((err) => console.log('error'))
}
return (
<div className="App">
<form>
<input name="name" value={formData.name || ''} onChange={handleChange} />
<button onClick={eventHandler}>click</button>
</form>
{data &&
data.map((element, index) => (
<GameTestResult name={element.name} key={element.index} />
))}
</div>
)
}
When i am dispatching startAddLike , I am getting undefined value of uid in the server side ie. req.body.uid = undefined.how to solve it?
Action generator:
export const startAddLike = (id) => {
return (dispatch, getState) => {
const uid = getState().auth.uid;
axios
.post(`http://localhost:5000/api/posts/${id}/like`, uid)
.then(res => dispatch(likePost(id, uid)))
}
}
Like Post Route
router.post('/:id/like', (req, res) => {
console.log(req.body.uid);
Post.findOneAndUpdate({_id:req.params.id}, {$push: {likes: req.body.uid}},{ "new": true})
.then(post => {
console.log('l'+post);
res.json(post);
})
});
In here uid should be passed as an object.
So this should be
export const startAddLike = (id) => {
return (dispatch, getState) => {
const uid = getState().auth.uid;
axios
.post(`http://localhost:5000/api/posts/${id}/like`, { uid: uid })
.then(res => dispatch(likePost(id, uid)))
}
}
Hope that fixes your issue.