I hope you all are good. I am trying to create an update function in Mern, when I tried it on Postman it works fine but when I 'am trying to implement same with React it does not give me any result and also not giving me any error. I 'am unable to figure out What I'am doing wrong. If anyone here can figure out by looking on my code, what is wrong with it, It will be great help. Thanks a lot in advance.
import { getANews, updateNews } from "../../actions/news";
const Edit = ({ router }) => {
const [websiteCategory, setWebsiteCategory] = useState("");
useEffect(() => {
initNews();
}, [router]);
// get a single news to make changes on it
const initNews = () => {
if (router.query._id) {
getANews(router.query._id).then((data) => {
if (data.error) {
console.log(data.error);
} else {
setWebsiteCategory(data.websiteCategory);
}
});
}
};
const onChangeWebsiteCategory = (event) => {
setWebsiteCategory( event.target.value);
};
// update the category field in this particular news
const editNews = (event) => {
event.preventDefault();
updateNews(router.query.id, websiteCategory).then((data) => {
if (data.error) {
console.log("error");
} else {
console.log(websiteCategory);
console.log(data);
}
});
};
return (
<div>
<h3>Edit News</h3>
<form onSubmit={editNews}>
<div className="form-group">
<label>Website-Category </label>
<input
type="text"
className="form-control"
defaultValue={websiteCategory}
onChange={onChangeWebsiteCategory}
/>
</div>
<div className="form-group">
<input type="submit" value="Edit News" className="btn btn-primary" />
</div>
</form>
</div>
);
};
export default withRouter(Edit);
Edit: Thanks for the reply, below is the complete code. Hope this helps you helping me :)
This is my action/news file
import fetch from "isomorphic-fetch";
import { API } from "../config";
// API = http://localhost:3000/
export const getANews = (id) => {
return fetch(`${API}/news/${id}`, {
method: "GET",
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
export const updateNews = (id) => {
return fetch(`${API}/news/${id}`, {
method: "POST",
headers: {
Accept: "application/json",
},
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
your code looks fine. I do not have your ....action/news implementation. So can't run your code.
may I suggest your line 25-26 be switched to
setWebsiteCategory(event.target.value);
you code in my IDE
I looked at your code have repeated your problem. useEffect 2nd parameter should be an empty array. so the initialization is called once in the begining. (read "Preventing Endless Callbacks using Dependencies" section of https://blog.bitsrc.io/fetching-data-in-react-using-hooks-c6fdd71cb24a
see https://codepen.io/lukelee10/pen/VweGByb
is what i have jested up. the getANews and updateNews from line 46 - 53 are my stubbed out call to the API (notice they are both async function(meaning they return promise).
SO, your take away is line 54 - 97.
Finally I found the solution, earlier I was not sending category in my update function. So I removed my update function from action file and fetch the data in my function and send the updated data back in the body.
const editNews = () => {
return fetch(`${API}/news/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(websiteCategory),
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
Related
I'm creating a comment section for my website and in my front end, I want to create a function that displays the comment that the user posted in the database.
But when I created a function that retrieves the comment and display it, the comment won't show up. I don't know what I am doing wrong, did I place it in the wrong place or did I program it correctly?
this is my backend program
app.post('/forum', function (req, res){
const queryString = "INSERT INTO comments (Comments) VALUES (?)"
console.log (req.body)
con.query(queryString, [req.body.comment], function (err, result){
if (err) {
throw err;
}
if (result.length != 1) {
return res.send("Posted Comment")
}
else {
res.send('Comment failed to post')
};
})
})
app.get('/forum', function (req, res){
const queryString = 'SELECT * FROM comments WHERE Comments = ?'
console.log (req.body)
con.query(queryString, [req.body.insert], function (err, result){
if (err) {throw err;}
var commenting = JSON.stringify(result)
res.send(commenting);
})
})
the api call that retrieves the response
static async forum(comm){
const response = await axios
.post('http://localhost:8080/forum', {
comment: comm,
})
.then ()
return response.data;
}
static async comment(comm){
const response = await axios
.get('http://localhost:8080/forum', {
comment: comm
})
.then ()
return response.data;
}
and my frontend where the comment would show up
function Forum() {
const [comment, inputComment] = useState ('')
/*user posts the comment*/
const onPost = (event) => {
event.preventDefault()
apiClient.forum(comment) .then( (response) => {
console.log(response)
})
}
/*users comment gets displayed*/
apiClient.comment() .then((response) =>{
document.getElementById("comment-section").innerHTML = response
})
return (
<section class = "index-banner" >
<div>
<label>Comment</label>
<textarea name="comment" id= "comment" rows="10" tabIndex = "4"onChange = {e => {inputComment(e.target.value)}}></textarea>
<button onClick={onPost}>Post</button>
</div>
<div>
<body id = "comment-section" ></body>
</div>
</section>
)
}
Is the request definitely coming through? You can verify in the browser DevTools on the network tab.
A few recommendations I could make about a better way to use React.
When the response returns, add it to the component using useState
The component will automatically re-render when there is a state update
(here you don't need the document.getElementById
You could also use the useRef approach
the favoured "React way" of doing document.getElementById
1. with useState:
function Forum() {
const [comment, setComment] = useState ('')
const [postedComment, setPostedComment] = useState ('')
const onPost = (event) => {
event.preventDefault()
apiClient
.forum(comment)
.then( (response) => {
console.log(response.data)
// this should trigger a rerender, in the <div>{postedComment}</div>
// assuming your response contains the posted comment:
setPostedComment(response.data)
})
.catch( error => console.log(error) /* handle Exception here */)
}
return <div>
<label>Comment</label>
<textarea
name="comment"
id="comment"
rows="10"
tabIndex ="4"
onChange={e => {inputComment(e.target.value)}}>
</textarea>
<button onClick={onPost}>Post</button>
<div>{ postedComment }</div>
</div>
}
2. with useRef:
const commentRef = useRef(null)
// ...
const onPost = (event) => {
event.preventDefault()
apiClient
.forum(comment)
.then( (response) => {
console.log(response)
// assuming your response contains the posted comment:
commentRef.current.innerHTML = response
})
.catch( error => console.log(error) /* handle Exception here */)
}
return <div>
{/* ... */}
<div ref={commentRef}>
</div>
</div>
NOTES:
I would avoid using the body element except for the entire application!
To address some of the comments:
fetch uses response.json()
axios uses response.data
hey guys i dont know why my setPlaceId works but not my setPlaceName
placeName doesnt print anything when there is some data that should be printed out could someone help? So yea i added more code, if you need more things ill post it, thanks for everyones help in advance i wish i can fix this as soon as possible.
tldr: setPlaceId pastes text but setPlaceName doesnt paste text when there are info that should be pasted.
btw i deleted some stuff from the code so it wouldnt be so long so dont worry about me not having some stuff that i called
function ConvertPlaces() {
const [placeName, setPlaceName] = useState("");
const [placeId, setPlaceId] = useState("");
useEffect(() => {
convert();
}, []);
const convert = () => {
fetch(
`https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=${userInput}`,
{
method: "GET",
headers: {
//deleted api keys
},
}
)
.then((res) => res.json())
.then((response) => {
console.log(response);
setPlaceName(response.Places.map((airport) => airport.PlaceName));
setPlaceId(response.Places.map((airport) => airport.PlaceId));
})
.catch((err) => {
console.log(err);
});
};
const handleChange = (event) => {
setInputField(event.target.value);
setUserInput(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
setSavedInput(inputField);
setInputField("");
convert();
};
return (
<div>
<SearchPlaces
run={convert}
handleChange={handleChange}
handleSubmit={handleSubmit}
inputField={inputField}
userInput={userInput}
savedInput={savedInput}
/>
<p>sfdajfp</p>
<p>{placeId}</p>
<p>{placeName}</p>
</div>
);
}
export default ConvertPlaces;
I am not sure this is going to solve your issue, as you should really show a little bit more code but, regardless of that, may I suggest you loop through the response only once?
// ...
fetch([API endpoint])
.then(res => res.json())
.then(response => {
const setPlaceData = response.reduce((outObj, airport) => {
outObj.PlaceNames.push(airport.PlaceName);
outObj.PlaceIds.push(airport.PlaceId);
return outObj;
}, { PlaceNames: [], PlaceIds: [] });
/*
* I am assuming that you are using 'useState()' hook and
* you are willingly replacing whatever content is already
* present in your state variables rather than adding to it.
* E.g.
* setPlaceName([...placeName, ...setPlaceData.PlaceNames]);
*/
setPlaceName(setPlaceData.PlaceNames);
setPlaceId(setPlaceData.PlaceIds);
});
// ...
I'm trying to add an async/await in my code to have the app wait for the execution of a function to invoke an other one, but I can't seem to be able to figure out where my code is wrong.
I have an API call in a redux action, this is the action
export const editSecondaryCategory = (text) => {
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
I want, in my component, to wait after this action is completed to call an other function to update the state. Ideally, it should be something like this (I guess?):
async updateCategory() {
// do the thing I have to wait for
const propsUpdate = await this.props.editSecondaryCategory(text);
// if you've done the thing then call the function to update the state
if (updatedCat) {
this.updateStateArray();
}
}
And I'd call this.updateCategory() inside my component after the user is done editing the information.
Clearly, this code does not work. I know it's wrong, I just don't know why. Basically I have no clue what to write inside updateCategory() to make this work.
Please help lol
You need to rewrite editSecondaryCategory function to make it async.
export async function editSecondaryCategory(text){
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
Currently, your function is not an async function do the above changes and check.
So this is probably quite simple but I can't find the solution or what is happening. I am trying to call the sendRecording from the event handler inside the handleRecorder function. But the sendRecording is never reached:
import React from 'react';
import axios from 'axios';
declare var MediaRecorder: any;
const sendRecording = () => async dispatch => {
console.log('3');
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({});
try {
const res = await axios.post('/api/recording', body, config);
} catch (err) {
const errors = err.response.data.errors;
}
};
const handleRecorder = () => {
console.log('1');
sendRecording();
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
const audioChunks: any[] = [];
mediaRecorder.addEventListener('dataavailable', event => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener('stop', () => {
console.log('2');
sendRecording();
});
setTimeout(() => {
mediaRecorder.stop();
}, 3000);
});
};
const Recorder = () => {
return (
<div>
<button className='btn' onClick={handleRecorder}>
<i className='fa fa-microphone' title='Record' />
</button>
</div>
);
};
export default Recorder;
When I click the button, the console prints:
1
2
But never the 3
It actually happens the same when I call the sendRecording function from the beginning of the handleRecorder function.
I'm still learning, it must be something simple that I have not understood, but it is taking me ages to solve this.
What you have done was a closure function, which is a function inside a function
you can find out more about that here: https://javascript.info/closure.
The way you have defined the function sendRecording is incorrect for this usage.
It should be something like this:
const sendRecording = async (dispatch) => {
...
}
not sure why you have an argument called dispatch, especially since it's not used.
you can now execute this function by simply calling
sendRecording();
why you sending in the dispatch params?
try removing it to :
const sendRecording = async () => {
console.log('3');
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({});
try {
const res = await axios.post('/api/recording', body, config);
} catch (err) {
const errors = err.response.data.errors;
}
};
Thank you very much to all of you!
I don't know what I did there ^^"
The dispatch was there because I was using it but I removed some code before posting it here but forgot to remove the dispatch.
It works perfectly anyway, thank you.
I am trying to test the loadAllProjects function.
The test fails at .then() with the error: TypeError: Cannot read property 'then' of undefined
I have also tried mocking the reponse of getHeadersWithToken() but could not get it to work.
Snookered on this one and would appreciate any help.
test:
it('should create SET_ALL_PROJECTS action when fetching projects', () => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
store.dispatch(actions.loadAllProjects.apply())
.then(() => { // FAILS HERE
expect(store.getActions()).toEqual(expectedActions)
})
});
code:
export const getHeadersWithToken = () => {
return fetch("/.auth/me", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
const header = 'Bearer ' + json[0].access_token
const applicationJsonHeaders = getJsonHeaders(header)
return applicationJsonHeaders
})
.catch( error=> {
console.error(error)
})
}
export const loadAllProjects = () => {
return (dispatch) => {
getHeadersWithToken()
.then(applicationJsonHeaders => {
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
dispatch(setAllProjects(json))})
.catch(error => {
console.error(error)
dispatch(failedToLoadProjects(error))
});
})
}
}
store used in test:
const store = mockStore(Map(
{
allProjects: Map({
}),
currentProject: Map({
authenticationData: Map({
})
})
})
);
What Redux middleware are you using for the async stuff? Make sure you set up the middleware when creating the store for testing.
Since i dont see that anywhere in your code above im gonna assume we are not using that middleware here.
Since loadAllProjects is a higher order function i would do this:
it('should create SET_ALL_PROJECTS action when fetching projects', (done) => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
// Higher order function that returns a new function.
const loadAllProjectsAsync = actions.loadAllProjects();
// The function returned expects a dispatch from Redux as an argument.
// It will do async work and when its done, it will call the provided dispatch.
loadAllProjectsAsync(store.dispatch).then(() => {
expect(store.getActions()).toEqual(expectedActions);
done();
})
});
You also need to modify your code for loadAllProjects so that the inner function returns the promise:
export const loadAllProjects = () => {
return (dispatch) => {
// You will need to return the promise in order for the test to be able to call .then() on it.
return getHeadersWithToken()
.then(applicationJsonHeaders => {
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(parseResponseAndHandleErrors)
.then(json => {
dispatch(setAllProjects(json))})
.catch(error => {
console.error(error)
dispatch(failedToLoadProjects(error))
});
})
}}
Also, as already stated, you must tell the jest when the test is done if you are testing async stuff. Do this by letting your it call take done as a param and call that as a function after you have verified the outcome i the .then()
This was just a quick fix from my side. There still might be something im missing or some bugs in the code above, but you get the point.
Let me know if you have any follow up questions?
In case this is ever useful to anyone and in acknowledgment of Septastium's answer, I eventually changed the code to:
async getHeadersWithToken(requestType) {
if (process.env.REACT_APP_RUNNING_LOCALLY==="true") {
return {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
};
}
let result = await fetch("/.auth/me", this.requestOptions)
let headers = result.json()
.then( json => {
const header = 'Bearer ' + json[0].access_token
const applicationJsonHeaders = this.getJsonHeaders(header, requestType)
return applicationJsonHeaders
})
.catch(error => {
console.error(error)
})
return headers
}
export const loadAllProjects = () => {
return async dispatch => {
const authenticator = new Authenticator()
let applicationJsonHeaders = await authenticator.getHeadersWithToken(constants.GET)
let loggedInUser = await authenticator.getLoggedInUser()
const requestOptions = {
method: 'GET',
headers: applicationJsonHeaders,
};
return await fetch(process.env.REACT_APP_PROJECTS_API_URL + "/projects", requestOptions)
.then(response => {
return parseResponseAndHandleErrors(response)
})
.then(json => dispatch(setAllProjects(json)))
.then(()=> dispatch(setAuthenticationData(loggedInUser)))
.catch(error => {
console.error(error)
return dispatch(failedToLoadProjects(error))
});
}
}
and the test to:
const checkActionsWereDispatched = async (expectedActions, actionCreator) => {
const store = mockStore(Map(
{
}),
);
store.dispatch(await actionCreator.apply()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
}
it('should create SET_ALL_PROJECTS action when fetching projects', async () => {
fetch
.once(JSON.stringify([{ access_token: "12345" }]))
.once(JSON.stringify({ name: "x" }))
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: { name: "x" } },
]
checkActionsWereDispatched(expectedActions, actions.loadAllProjects)
});
As noted above I think Spetastium's version of the test is easier to read than mine and his article here was very helpful.