How to create an approval post in Reactjs? - reactjs

I need to approve "posts" before going online,
the first question is do I need to code on the backend, or I can do it only in the front-end in order to add the functionality?
so I have some code that creates a post, and I need to send data to the confirmation page before going to the main page.
here is the code for post creation:
const onSubmit = (data) => {
if (data.postText) {
const formData = postToFormData(data, file, createTags);
axios
.post(`/posts`, formData, {
headers: {
"Content-Type": "multipart/form-data",
accessToken: localStorage.getItem("accessToken"),
},
})
.then((res) => {
setTimeout(() => {
history.push("/approving");
}, 2000);
});
setMessage("posct created successfully!");
}
};
so as you see in the code above, we have a post method to post the data, and I need to send the data to the approving page where I need to see all the data and press "approve" or "reject"
i have 3 states in the main page to display the data:
#{value.fullName}
{value.postTitle}
{value.postText}
I would appreciate your help.

I think that you don't need to modify the backend. Only send a PUT request to update the post (if the backend accept this).
Regarding the other question, you don't need to redirect to another url to show new content in react. You can show different content depending in the state.
const Form = ({submitting, handleSubmit, formData, setFormData}) => {
return <form onSubmit={handleSubmit}>
<input
type="text"
required
value={formData.text}
onChange={(e) => setFormData({text: e.target.value})}
/>
<br />
<button type="submit">{submitting ? 'Sending...' : 'Save'}</button>
</form>
}
const Revision = ({formData}) => {
return <div>
<p>Your sucess message...</p>
<p>{formData.text }</p>
<button>Approve</button>
<button>Reject</button>
</div>
}
const App = () => {
const [submitting, setSubmitting] = React.useState(false)
const [submitted, setSubmitted] = React.useState(false)
const [formData, setFormData] = React.useState({text: ''})
const handleSubmit = (e) => {
e.preventDefault()
setSubmitting(true)
// Fake send post data
setTimeout(() => {
setSubmitted(true)
}, 1000)
}
const handleApprove = () => {
// Send PUT request to update your post.
}
const handleReject = () => {
// Send PUT request to update your post.
}
if (submitted) {
return <Revision
formData={formData}
handleApprove={handleApprove}
handleReject={handleReject}
/>
} else {
return <Form
submitting={submitting}
handleSubmit={handleSubmit}
formData={formData}
setFormData={setFormData}
/>
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

I don't quite understand what you means by "approve post before going online". But here the scenarios which I think I understood:-
a) CONFIRMATION FEATURE before submitting new post to backend (basically create a new set of data in the database) javascript
b) APPROVE or REJECT FEATURES after new post successfully submitted & created in the database
For SCENARIO A:-
you may create a component (like a popup or something) that shows user to choose whether they want to Confirm or Cancel creation of new post data.
Confirm - will triggered the onSubmit function and send the data to the backend to create a new post
Cancel - will reset every states to it's default value/state (basically emptying out every input placeholders)
For SCENARIO B:-
you must have some kind of variables in the database that can distinguish which post is approved & which is not. (like status or publish_status - can be set to either Yes or No or 1 or 0)
create a new function to handle just changing/update of status/publish_status var/value in the database of said/selected post. This can be done right after creation of new post (like what you want) or just at anytime you want (basically you have a working UI that handles a list of posts with just the id, title, status/publish_status & action buttons like edit, delete and/or publish, typically we display this in table - so if you hit action button publish, then it will triggered the update/put req to change/update the status/publish_status in the database. Just make sure to change your publish action button text to unpublish if the put req successful)

Related

React Firebase - retrieving photoURL from users collection to render in comments section

I have a React app with Firebase backend. I have a users collection in the database which stores the photoURL of the user.
When a user comments on a post, instead of saving that photoURL each time into the comments collection I just want to render the photoURL from the users collection.
That way if the user updates their photoURL it is updated on all the comments plus there's no duplication of data.
On each comment I have saved the uid in the comment collection. I just can't quite figure out how to map through all the comments and take the uid and use that to get the photoURL from the users collection. Below is what I have so far.
I'm new to React so apologies if this is a simple fix.
const [comments, setComments] = useState([])
// This is used to get all the comments
useEffect(() => {
let unsubscribe
if (postId) {
unsubscribe = db.collection('posts').doc(postId).collection('comments').orderBy('timestamp', 'desc').onSnapshot((snapshot) => {
setComments(snapshot.docs.map((doc) => doc.data()))
})
}
return () => {
unsubscribe()
}
}, [postId])
// This is for adding the comment to the comments collection
const postComment = (event) => {
event.preventDefault()
db.collection('posts').doc(postId).collection('comments').add({
text: comment,
username: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
photoURL: user?.photoURL, // This does not save anything because it's taking form auth user and not user collection
uid: user?.uid
})
setComment('')
setCommentActive(true)
setCommentsArrow(!commentsArrow)
}
// This is a function to get the photoURL of any user based on the uid
async function getImageURL(uid) {
return (await db.collection("users").doc(uid).get()).data().photoURL
}
// Rendering the comments
return (
<div style={{ transition: "height 0.3s linear" }} className={commentActive ? null : "hidden"}>
<div className="post__comments">
{comments.map((comment) => (
<div className="post__comment">
<Avatar
className="post__avatar"
alt=""
src={getImageURL(comment.uid)} // Calls the getImageURL function and passes in the uid from the comment
/>
<p><strong>{comment.username}</strong> {comment.text}</p>
</div>
))}
</div>
</div>
)
Here are the 2 options that I see are viable:
I see you are storing the users' UID in the comment doc, then make a separate request to Firestore (users collection) and then fetch their photoURL from that document.
This is what I prefer and seems to be the most viable way but to do so you would have to store the user images in Firebase Storage or any storage service. There you can structure your directory like /images/<user_uid>. So if my UID in the comment is 'uid123' then you would look for the image in Firebase Storage.
A download URL from Firebase Storage looks like:
https://firebasestorage.googleapis.com/v0/b/<project_id>.appspot.com/o/users%2F<userId>.png?alt=media&token=token
Now it does has that token at the end so you may have to use the getDownloadURL method or make your bucket public from the Google Cloud Console. But the format of URL will remains consistent i.e. https://domain.tld/path/image.png
This means users can't just simply paste an image URL to update their photo but they'll have to upload the image and you'll have to store it somewhere.
If you don't want to use storage or the 2nd method above, then you would have to make additional calls to the database to get users' profile picture.
Edit:
Function to get user's image URL from UID:
async function getImageURL(uid) {
return (await db.collection("users").doc(uid).get()).data().photoURL
}
Then in the component below:
{comments.map((comment) => (
<div className="post__comment">
<Avatar
className="post__avatar"
alt=""
src={getImageURL(comment.uid)}
/>
<p><strong>{comment.username}</strong> {comment.text}</p>
</div>
))}

React onSubmit e.preventDefault() not working sometimes + Axios

Right now I'm facing this strange problem in React where the onSubmit function with e.preventDefault() does avoid refreshing the page sometimes, and sometimes it doesn't.
I've created two hooks to keep track of the uploaded files and their progress bars.
const [ uploadedFiles, setUploadedFiles ] = useState([]);
const [ uploadPercentages, setUploadPercentages ] = useState([]);
The onSubmit function uses Axios to make a request to the backend.
const onSubmit = async e => {
e.preventDefault();
if(!!file) {
// Show file box on screen
let index = addUpload(file);
const formData = new FormData();
formData.append("file", file);
try {
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
})
} catch(err) {
// Handlers
}
}
return false // trying something different to avoid refresh
}
Just in case, the addUpload function has shown little to no relation with the source of the problem. I took it away to test it and things behave the same way.
If anyone can help I would appreciate it.
Edit:
Here is the form.
<form className="choose-file" onSubmit={onSubmit}>
<div className="file-container">
{ file ?
<p> { file.name } </p>
:
<label className="file-label" htmlFor="customFile">
<input type="file" className="file-input" id="customFile" onChange={ onChange }/>
<p><i className="fas fa-cloud-upload-alt"></i></p>
<p>Click here to select file</p>
</label>
}
</div>
<div className="file-options">
<input type="submit" value="Upload" className="file-input" id="customFile"/>
<button type="button" onClick={ removeFile }>Delete</button>
</div>
</form>
"file" is a third hook just to show the user the name of the file they just selected.
Edit 2:
The problems seems to appear only when file sizes are greater than 100MB. Besides, once the problem shows up, it starts to happen to every single file no matter its size.
For instance if I upload a 7MB file, page does not refresh, if I then try a 100MB file it starts refreshing for every upcoming file and all console logs after the axios post are never seen again.
Edit 3:
Since I'm running a local backend on Flask, I tried disconnecting it from the React app to see what happens. For small files the request to the backend is only asked once and the alarm inside the catch(err) triggers. For big files the request is asked around four times and it never reaches the catch part.
Send help
I guess the problem in not with addUpload but with the async. You can try this way
const onSubmit = (e) => {
e.preventDefault();
// After collecting formData ...
// in the *try* block do this
const post = axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
post.then(() => {alert('Done posting')}) // inside this you can do whatever you want to do after axios.post
}
**EDIT : **
Since you mentioned in the comment that the solution does not work, here's an alternative.
Declare a new function
const async post = () => {
// Get the formData here
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
}
// Now in the onSubmit function
const onSubmit = (e) => {
e.preventDefalut();
post()
}
This is the final solution I have. Hope this works...
I've been facing problems with using arrow functions as HANDLERS,
try turning it into normal function
async function HandelOnSubmit (e) {
e.preventDefault();
if(!!file) {
// Show file box on screen
let index = addUpload(file);
const formData = new FormData();
formData.append("file", file);
try {
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
})
} catch(err) {
// Handlers
}
}
return false // trying something different to avoid refresh }
At the end, the problem turned out to be something as simple as saving the files in the React's public folder.
My guess is that this caused the whole project to refresh every time a change is detected inside this folder. For small objects the time it takes almost zero, but for big files it takes a while, causing the components to refresh as well.

Updating useState and AsyncStorage and Rerendering the screen based on given result

I am new to React Native and I am currently trying to fetch credential data from a website and save it into device memory. However I am encountering few issues regarding it!
My flow:
When app is launched the app will check asyncStorage if there are credentials.
If so, we go straight into the app. Otherwise we enter the RootStackScreen(aka login Screen)*** (issue #1)
(LOGIN) A user will enter his/her login into text input which would be saved in a useState.
Using a function 'loginHandle' a fetch request is called using the info from useState and we setCredential with the data returned.** (issue #2)
The app should rerender and see that there is a credential and load into the main screen. *** (issue #3)
However, I'm encountering 2 issues.
*** Even when asyncStorage does contain the credentials when I check appLoad.credentials in the return(...) it returns a null. However, it returns the correct value in my useEffect(). I thought by using useEffect() I am calling the functions during ComponentsDidMount which is before the rendering of the screen. So shouldn't appLoad.credentials contain a string?
On my console.log(credential) It is always one step behind. I.e. I press the login button once. The console log will return null. However, I thought that since the console.log() is after the fetch command that the credentials should be set before the console.log call!
How do I get the app to rerender. I've seen online regarding force rerendering but I heard that it was bad!
App.js
import AsyncStorage from '#react-native-community/async-storage';
...
export default function App() {
const STORAGE_KEY = '#deviceCredentials';
const [data, setData] = useState('');
const appLoad = {
isLoading: true,
credentials: null
};
//Runs during ComponentsDidMount
useEffect(() => {
setTimeout(async () => {
//In 1000ms try to get credentials
try {
appLoad.credentials = await AsyncStorage.getItem(STORAGE_KEY);
console.log(appLoad.credentials);
} catch (e) {
console.log(e);
}
}, 1000);
}, []);
//Render
return (
<NavigationContainer>
{/* If credentials is not null then we can go straight into Main screen,
otherwise we go to Login Screen */}
{appLoad.credentials !== null ? (
<Drawer.Navigator drawerContent={props => <DrawerContent {...props} />}>
<Drawer.Screen name="MusteringDrawer" component={MainTabScreen} />
</Drawer.Navigator>
)
:
<RootStackScreen />
}
</NavigationContainer>
);
};
SignIn.js
...
...
const [data, setData] = useState({
username: '',
password: '',
...
})
const [credential, setCredential] = useState({ data: null });
const STORAGE_KEY = '#deviceCredentials';
...
...
//Handles user data
const handleValidUser = (val) => {
setData({
...data,
username: val,
});
}
const loginHandle = () => {
fetch(url + data.password + data.username)
.then(x => x.text())
.then(y => {
setCredential({ data: y });
});
console.log(credential);
AsyncStorage.setItem(STORAGE_KEY, credential);
}
return(
<Text>Username</Text>
<TextInput
placeholder="Your Username"
onChangeText={(val) => handleValidUser(val)}
onEndEditing={(e) => handleValidUser(e.nativeEvent.text)}
/>
<Text>Password</Text>
<TextInput
placeholder="Your Password"
onChangeText={(val) => handlePasswordChange(val)}
/>
<View style={styles.button}>
<TouchableOpacity onPress={() => { loginHandle(); }}>
<Text>Sign In</Text>
</TouchableOpacity>
</View>
Are you aware of the fact that:
const appLoad = {
isLoading: true,
credentials: null
};
is re-defined every render? Even more so, if the value gets set in the effect hook, react has no way of knowing that it was changed, thus will not re-render. And if it does re-render (for some entierly different reason, this will not re-render)... well, new variable :D.
What you want is remembering data like this across all renders. You do this, as you surely know, by using the useState hook.
Disclaimer: It is very important that you read the comments I have put in the code, they are crucial for this to work/be "best practice". This code was also not tested, but you should be able to get it to work using some of your sweet developer skills :)
export default function App() {
// Remove this, seems to be a leftover from testing
// const [data, setData] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [credentials, setCredentials] = useState(null);
// Check for credentials in the first render
useEffect(() => {
// 1. I'm not sure why you add a delay, It will probably only make your app appear sluggish
// 2. Use effect callbacks should not be made async, see this post
// https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret
async function fetchCredentials() {
setCredentials(await AsyncStorage.getItem(STORAGE_KEY));
setIsLoading(false);
}
fetchCredentials();
}, []);
// Whatever your render code may be
return null;
}
It may very well be that I have not solved your problem (I may miss understood you, and most of the time I'm generally a bit stupid). In this case, don't hesitate to get back to me via comments.
Cheers and happy coding!

How to correctly pass parameters to change the page on the API with React

I have this button inside a react component, and I call the fetchImages function with it
<button onClick={() => this.fetchImages()}
type="submit" className="btn btn-primary mt-4">
Mais Imagens
</button>
FetchImages is a function where I use axios to make a get request on a FLASK API, to get all the itens in one page
fetchImages(page) {
const { filters } = this.state;
this.setState({ loadingFetchImages: true });
const { headers } = getAxiosConfig();
axios.get(`${API_URL}/api/imagens_selecionadas`, { params: { page, q:
filters }, headers })
.then((response) => {
const images = response.data.objects.map(image => ({ ...image, check:
false }));
this.setState({ images, loadingFetchImages: false });
})
.catch(handleRequestError);
}
I need to found a way to, everytime the user click the button, the page advance with the parameter ?page=1, ?page=2, etc. So i need to call http://localhost:5000/api/imagens_selecionadas?page=1, ?page=2 and etc...
I cant found a way to do that, can someone help me?
you can define a store for your component and save your variable and constant inside it. if you pass your store via your component, you always have every critical and necessary variable you want

Prevent Redirect on File Upload through form Submittal (React, Express, Multer)

I'm setting up a file uploading functionality for the first time. I have a react front-end and an express server that will store the files. I have it set up so a user can submit the file and it is saved the way I'd like it to be saved on the express server. However, whenever a user submits the form they are redirected from the react frontend (on port 3000) to the POST route on the server (port 3050). I didn't know that this was default behavior for a post request, which I expected would keep the users on the page.
I'd like to avoid this behavior but am unsure of the best way to go about it. I've tried building an action that functions as an AXIOS request, but am having trouble accessing the form data (particularly the file data) in the AXIOS request. My understanding is that there isn't native support for multipart form-data in AXIOS.
Event.preventdefault is the obvious choice, but any implementation of that stops the form submittal from grabbing the appropriate form data and sending it through.
The code for the form is included below (this version is using an on-click event that prevents the event default and dispatches the action - the action fires but as noted doesn't pass any of the relevant information through):
<div className="modal-wrapper">
<h3 className="title__h3">Upload Data</h3>
<p>Drag and drop anywhere or choose a file to upload.</p>
<p>.csv or .xlsx</p>
<form className="upload-form" ref="uploadForm" id="uploadForm" action="http://localhost:3050/upload" method="POST" enctype="multipart/form-data">
<input name="fileName" placeholder="Name the file you're uploading here"/>
<input type="file" name="upl" id="file" className="inputfile" />
<label htmlFor="file" className="btn btn__white-outline btn__full-width">Select a File</label>
<input onClick={this.submitForm} type="submit" value="submit" />
</form>
</div>;
My simple Multer route:
app.post('/upload', upload.single('upl'), function(req,res,next) {
return false
})
My understanding is that there is no native support for sending a multipart-form item through Axios, I'd like to avoid pulling in a FormData module in order to make it work since the form (other than the redirect) works flawlessly. Is there a simple solution here that will prevent the form from trying to load the server-side page while still submitting the form data?
In your submitForm handler, pass a reference to the event and use event.preventDefault().
submitForm(event) {
event.preventDefault();
...other code here...
}
You can also try
<form className="upload-form" ref="uploadForm" id="uploadForm" action="http://localhost:3050/upload" method="POST" enctype="multipart/form-data" onSubmit={ (event) => { event.preventDefault(); } }>
Here is how I handle submitting files with Axios
handleChange = ( event ) => {
const fileReader = new FileReader();
const fileToUpload = event.target.files[0];
fileReader.onload = ( upload ) => {
const allFiles = [ ...this.state.allFiles ].concat( upload.target.result );
this.setState({
allFiles
});
};
fileReader.readAsDataURL(fileToUpload);
};
The handleChange method is tied to the onChange event of the <input type="file" onChange={this.handleChange} />
Then in the form submit handler
handleSubmit = ( event ) => {
event.preventDefault(); //So the page does not refresh
const { allFiles } = this.state;
const Promises = [];
allFiles.forEach( file => {
Promises.push( Axios({
url: "a url goes here",
method: "POST",
data: {
file: file, //This is a data url version of the file
}
}));
});
Axios.all(Promises).then( result => {
alert(result.message); //Display if it uploaded correctly
})
};
create a method in your Component that handles the submit, in the onSubmit attribute of your form call it like: onSubmit="{this.handleSubmit}" and post your data in an async way.

Resources