Creating a notes app on MERN stack https://lutif.github.io/Notes-Keeper/ ,
I have a mongodb , I want to fetch notes on database and then render those on frontend. However reactjs doesn't allow me to initialize state (call useState) in a call back. how do it get my fetch data and use it in useState. any pointer will be helpful.
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
import addNotedb from '../APICalls/addNote';
import getNotesListdb from '../APICalls/getNotesList';
function App() {
getNotesListdb().then(nojson=>nojson.json()).then( notesListdb=>
{const [notes, setNotes] = useState([...notesListdb]);}
)
function addNote(newNote) {
// const noteId =
addNotedb(newNote);
console.log('calling add note with ', newNote);
// newNote.Id = newNote;
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
{notes.map((noteItem, index) => {
return (
<Note
key={index}
id={index}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
<Footer />
</div>
);
}
export default App;
const endpoint = '/api/notes';
export default function getNotes() {
let promise =
fetch(endpoint, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}
)
return promise
}
Related
I made a social media app and this is my homepage
import { Box, useMediaQuery } from "#mui/material";
import { useSelector } from "react-redux";
import Navbar from "scenes/navbar";
import UserWidget from "scenes/widgets/UserWidget";
import MyPostWidget from "scenes/widgets/MyPostWidget";
import PostsWidget from "scenes/widgets/PostsWidget";
import AdvertWidget from "scenes/widgets/AdvertWidget";
import FriendListWidget from "scenes/widgets/FriendListWidget";
const HomePage = () => {
const isNonMobileScreens = useMediaQuery("(min-width:1000px)");
const { _id, picturePath } = useSelector((state) => state.user);
return (
<Box>
<Navbar />
<Box
width="100%"
padding="2rem 6%"
display={isNonMobileScreens ? "flex" : "block"}
gap="0.5rem"
justifyContent="space-between"
>
<Box flexBasis={isNonMobileScreens ? "26%" : undefined}>
<UserWidget userId={_id} picturePath={picturePath} />
</Box>
<Box
flexBasis={isNonMobileScreens ? "42%" : undefined}
mt={isNonMobileScreens ? undefined : "2rem"}
>
<MyPostWidget picturePath={picturePath} />
<PostsWidget userId={_id} />
</Box>
{isNonMobileScreens && (
<Box flexBasis="26%">
<AdvertWidget />
<Box m="2rem 0" />
<FriendListWidget userId={_id} />
</Box>
)}
</Box>
</Box>
);
};
export default HomePage;
and this is my post widget
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setPosts } from "state";
import PostWidget from "./PostWidget";
const PostsWidget = ({ userId, isProfile = false }) => {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts);
const token = useSelector((state) => state.token);
const getPosts = async () => {
const response = await fetch("http://localhost:3001/posts", {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
dispatch(setPosts({ posts: data }));
};
const getUserPosts = async () => {
const response = await fetch(
`http://localhost:3001/posts/${userId}/posts`,
{
method: "GET",
headers: { Authorization: `Bearer ${token}` },
}
);
const data = await response.json();
dispatch(setPosts({ posts: data }));
};
useEffect(() => {
if (isProfile) {
getUserPosts();
} else {
getPosts();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<>
{posts.map(
({
_id,
userId,
firstName,
lastName,
description,
location,
picturePath,
userPicturePath,
likes,
comments,
createdAt,
}) => (
<PostWidget
key={_id}
postId={_id}
postUserId={userId}
name={`${firstName} ${lastName}`}
description={description}
location={location}
picturePath={picturePath}
userPicturePath={userPicturePath}
likes={likes}
comments={comments}
createdAt={createdAt} // Pass the createdAt field as a prop to the PostWidget component
/>
)
)}
</>
);
};
export default PostsWidget;
when I post new post in my page old posts are staying on top and new posts are going below of the page how can I fix this. its my oldest old post post but it is top . I use mongodb and my posts hase timestamps I want to order with timestamps
I'm having some issues getting a component to re-render after submitting a form. I created separate files to store these custom hooks to make them as reusable as possible. Everything is functioning correctly, except I haven't figured out a way to re render a list component after posting a new submit to that list. I am using axios for fetch requests and react-final-form for my actual form. Am I not able to re-render the component because I am using useContext to "share" my data across child components? My comments are set up as nested attributes to each post, which is being handled through Rails. My comment list is rendered in it's own component, where I call on the usePost() function in the PostContext.js file. I can provide more info/context if needed.
**
Also, on a slightly different note. I am having difficulty clearing the form inputs after a successful submit. I'm using react-final-form and most the suggestions I've seen online are for class components. Is there a solution for functional components?
react/contexts/PostContext.js
import React, { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useAsync } from "../hooks/useAsync";
import { getPost } from "../services/post";
const Context = React.createContext();
export const usePost = () => {
return useContext(Context);
};
export const PostProvider = ({ children }) => {
const id = useParams();
const { loading, error, value: post } = useAsync(() => getPost(id.id), [
id.id,
]);
const [comments, setComments] = useState([]);
useEffect(() => {
if (post?.comments == null) return;
setComments(post.comments);
}, [post?.comments]);
return (
<Context.Provider
value={{
post: { id, ...post },
comments: comments,
}}
>
{loading ? <h1>Loading</h1> : error ? <h1>{error}</h1> : children}
</Context.Provider>
);
};
react/services/comment.js
import { makeRequest } from "./makeRequest";
export const createComment = ({ message, postId }) => {
message["post_id"] = postId;
return makeRequest("/comments", {
method: "POST",
data: message,
}).then((res) => {
if (res.error) return alert(res.error);
});
};
react/services/makeRequest.js
import axios from "axios";
const api = axios.create({
baseURL: "/api/v1",
withCredentials: true,
});
export const makeRequest = (url, options) => {
return api(url, options)
.then((res) => res.data)
.catch((err) => Promise.reject(err?.response?.data?.message ?? "Error"));
};
react/components/Comment/CommentForm.js
import React from "react";
import { Form, Field } from "react-final-form";
import { usePost } from "../../contexts/PostContext";
import { createComment } from "../../services/comment";
import { useAsyncFn } from "../../hooks/useAsync";
const CommentForm = () => {
const { post, createLocalComment } = usePost();
const { loading, error, execute: createCommentFn } = useAsyncFn(
createComment
);
const onCommentCreate = (message) => {
return createCommentFn({ message, postId: post.id });
};
const handleSubmit = (values) => {
onCommentCreate(values);
};
return (
<Form onSubmit={handleSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="comment-form-row">
<Field name="body">
{({ input }) => (
<textarea
className="comment-input"
placeholder="Your comment..."
type="text"
{...input}
/>
)}
</Field>
<button className="comment-submit-btn" type="submit">
Submit
</button>
</div>
</form>
)}
</Form>
);
};
export default CommentForm;
I have a button that, on click, has to perform a thing that makes a list displayed on my page to change, and in theory the page should reload. However, that never happens, no matter how many times I click my button.
Full code of my button:
import React from 'react';
import { useSaveOrderItemsForList } from '../../hooks/Lists/useSaveOrderItemsForList';
import ErrorIndicator from '../shared/ErrorIndicator';
import LoadingButton from '../shared/LoadingButton';
import { valueState as valueStateAtom } from '../../atoms/orderItemsAtom';
import { useSetRecoilState } from 'recoil';
export default function SaveOrderItemsButton({ orderItems, listID }) {
const { isError, error, isLoading, mutate } = useSaveOrderItemsForList(orderItems, listID);
const setValue = useSetRecoilState(valueStateAtom);
const handleClick = () => {
mutate(orderItems, listID);
setValue([]);
}
return (
<div className={'w-100'}>
<br />
<ErrorIndicator isError={isError} error={error} />
<LoadingButton
className={'w-100'}
variant={'success'}
loading={isLoading}
onClick={handleClick}
>
Save
</LoadingButton>
</div>
);
}
As for the code of my custom hook:
import { getToken } from '../../tokens/getToken';
import { basePath } from '../../config/basePath';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { useMutation, useQueryClient } from 'react-query';
async function saveOrderItemsForList(orderItems, listID) {
const token = await getToken();
const response = await fetch(`${basePath}/lists/save_order_items/${listID}`, {
method: 'PUT',
body: JSON.stringify({ orderItems }),
headers: getTokenAuthHeaders(token)
});
return response.json();
}
export function useSaveOrderItemsForList() {
const queryClient = useQueryClient();
return useMutation(saveOrderItemsForList,
{
onSuccess: () => {
return queryClient.invalidateQueries('lists');
}
}
);
}
My theory is that, since I'm managing the organizing of my list client-side, the page doesn't get updated with the information I passed (this is the code of the page that shows the list):
import Col from 'react-bootstrap/Col';
import CardsList from './CardsList';
import { useList } from '../../hooks/Cards/useList';
import useOrderItemsForCardsInList from '../../hooks/Lists/useOrderItemsForCardsInList';
import usePaginateCardsInList from '../../hooks/Cards/usePaginateCardsInList';
import LoadingAndErrorCentered from '../shared/LoadingAndErrorCentered';
export default function List({ listID }) {
const { isLoading, isError, error, data } = useList(listID);
const { data: orderItems } = useOrderItemsForCardsInList(listID);
const pagesArray = usePaginateCardsInList(orderItems, data);
return (
<Col xs={12}>
<br />
<LoadingAndErrorCentered isLoading={isLoading} isError={isError} error={error} />
{data && <CardsList cards={pagesArray} listID={listID} />}
</Col>
);
}
What do you guys think?
Edit: This is the code of my useList hook.
import { useQuery } from 'react-query';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { basePath } from '../../config/basePath';
import { getToken } from '../../tokens/getToken';
async function getList(listID) {
const token = await getToken();
const response = await fetch(`${basePath}/cards/list/${listID}`, {
method: 'GET',
headers: getTokenAuthHeaders(token)
});
return response.json();
}
export function useList(listID) {
return useQuery(['cards', 'list', listID], () => {
return getList(listID);
});
}
and on my server, I have this function declared on my endpoint:
static async getList(id) {
const query = await List.findById(id).exec();
return query;
}
queryClient.invalidateQueries('lists');
vs
useQuery(['cards', 'list', listID], () => {});
You are not invalidating the right query keys, so naturally the query doesn't refetch. You need to use the correct key for invalidation, in your case:
queryClient.invalidateQueries(['cards', 'list']);
I am trying to get the name of a user based on the user id, using a .map() method. The code that I wrote seems good to me, however, I get the error mentioned in the question title and the page won't render.
See the image below:
Here's an image of the error stack:
And here's the code I have:
import { SearchBox } from "../SearchBox"
import { StyledNavBar, NavSection, StyledLink, StyledLogo } from "./styles"
import logo from './Logo2.png'
import { useDispatch } from "react-redux"
import { useHistory } from "react-router"
import axios from "axios"
import { useEffect, useState } from "react"
function useApi() {
const userId = localStorage.getItem('userId')
const dispatch = useDispatch()
const [UserNames, setUserNames] = useState()
useEffect(() => {
async function getData() {
try {
const { data } = await axios({
method: 'GET',
baseURL: process.env.REACT_APP_SERVER_URL,
url: '/users'
})
console.log(data)
dispatch(setUserNames(data))
}catch(error){
dispatch(console.log(error))
}
}
getData()
}, [])
return { userId, UserNames }
}
export const NavBar = function({}) {
const { userId, UserNames } = useApi()
console.log(UserNames)
const token = localStorage.getItem('token')
const dispatch = useDispatch()
const history = useHistory()
function handleClick(){
dispatch({type: 'USER_LOGOUT'})
localStorage.clear()
history.push('/')
}
return(
<StyledNavBar>
<NavSection>
{!token && <StyledLink to='/login'>Ingresar</StyledLink>}
{!token && <StyledLink to='/signup'>Registrarse</StyledLink>}
<StyledLink to='/'>CategorÃas</StyledLink>
<StyledLink to='/'><StyledLogo src={logo} /></StyledLink>
{token && <StyledLink to='/'>Mis Transacciones</StyledLink>}
{token && <StyledLink to='/sellproduct'>Vender</StyledLink>}
{!!UserNames && UserNames.length > 0 && UserNames.map((usr, i) => {
return usr[i]._id === userId ?
<p>{usr[i].name}</p>
:
''
})}
<SearchBox />
{token && <button type='button' onClick={handleClick}>Cerrar Sesión</button>}
</NavSection>
</StyledNavBar>
)
}
So it's pretty much telling me that the usr[i]._id line is not correct, but as far as I can see nothing is wrong with that code.
I think you might just want usr and not usr[i]? The map() gives you the individual item from the iterable.
how do I get the values from the component that has the input to the component that is actually going to make the post? is that posible? or should I put all in the same component?
this is my Item component:
import React, {useState} from 'react';
import {Col} from 'reactstrap';
export function CustomFieldsItem({item}) {
const [value, setValue] = useState(false);
function handleChange(e) {
setValue(e.target.value);
}
return (
<>
<li className={'list-group-item d-flex border-0'} key={item.id}>
<Col md={2}>
<label>{item.label}</label>
</Col>
<Col md={10}>
<input className="form-control" type="text" value={value || item.value} onChange={handleChange} />
// <-- this is the value I want to pass to my update component when I type on it
// but the save button is in the "update component" (the one under)
</Col>
</li>
</>
);
}
This is my update Component:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import {post} from '../../common/fetch/fetchOptions';
export function CustomFieldsUpdate({item}) {
const [value, setValue] = useState(false);
const updateCustomField = async ({data}) => {
try {
await axios(
post({
url:'http://localhost:9000/projectcustomfields.json/update/1741?id=party&value=newvalue',
// <-- I want to set the value here, instead of "newvalue" but right now Im happy with just a log of the value from the component above in the console when I click on "Save" button.
// When I save right now I just post hardcoded values (which is what I want to change)
data: data,
method: 'POST',
mode: 'cors',
withCredentials: true,
credentials: 'include',
}),
);
console.log(value);
} catch (e) {
console.log('Error when updating values: ', e);
}
};
return (
<div className={'d-flex justify-content-end mr-4'}>
<button
type={'button'}
className={'btn btn-primary mr-2'}
onClick={updateCustomField}
>
Save
</button>
</div>
);
}
I have another component that list the objects the I want to update, maybe I need to pass the values from this component, maybe can use that?
import React, {useState, useEffect} from 'react';
import {CustomFieldsList} from './customFieldsList';
import {toast} from 'react-toastify';
import {ToastInnerDisplay} from '#learnifier/jslib-utils';
import {CustomFieldsItem} from './customFieldsItem';
export function CustomFieldsContainer({match}) {
const [value, setValue] = useState({
data: null,
loading: true,
error: null,
});
/**
* Initial loading of data.
*/
async function fetchData() {
setValue({...value, error: null, loading: true});
try {
let url = `http://localhost:9000/projectcustomfields.json/list/1741`;
const res = await fetch(url, {
method: 'POST',
mode: 'cors',
withCredentials: true,
credentials: 'include',
});
let data = await res.json();
setValue(prevValue => ({...prevValue, data: data.customFields, loading: false}));
} catch (error) {
toast.error(<ToastInnerDisplay message={error.message} />);
setValue({...value, error, loading: false});
}
}
useEffect(() => {
fetchData();
}, []);
if (value.loading) {
return <div>loading...</div>;
} else if (value.error) {
return <div>ERROR</div>;
} else {
return (
<div className={'section-component'}>
<div className={'col-md-6 col-sm-12'}>
<h2>Custom Fields</h2>
<CustomFieldsList setValue={setValue} list={value.data} />
</div>
</div>
);
// return <ChildComponent data={value.data} />
}
}
I render the components with a component list:
import React from 'react';
import {CustomFieldsItem} from './customFieldsItem';
import {CustomFieldsUpdate} from './customFieldsUpdate';
export function CustomFieldsList({list, setValue, update,
updateCustomField}) {
console.log(list);
console.log(update);
return (
<>
<form>
<ul className={'list-group border-0'}>
{list.map(item => (
<CustomFieldsItem item={item} key={item.id} />
))}
</ul>
<CustomFieldsUpdate updateCustomField={updateCustomField} setValue={setValue}/>
</form>
</>
);
}