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>
</>
);
}
Related
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 am using React Slick for the slider. I have a Card component for the cards and also I have Slider section where I render the CardList component for the upper card with mapping data from data.json and SlickSlider component for the slider.
I know that I have to add onClick event on the Card component but that's it.
const NewSliderSection = () => {
return (
<div className='new-slider-section'>
<CardList />
<SlickSlider />
</div>
)
}
export default NewSliderSection
import React from 'react'
import "./CardList.css"
import { useState, useEffect } from "react";
import Card from '../Card/Card';
const CardList = () => {
const [cardData, setCardData] = useState([]);
const getData=()=>{
fetch('data.json'
,{
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
)
.then(function(response){
console.log(response)
return response.json();
})
.then(function(myJson) {
console.log(myJson);
setCardData(myJson)
});
}
useEffect(()=>{
getData()
},[])
return (
<div className='card-list-container'>
{cardData.length > 0 && cardData.map((card) => (
<Card key={card.id} title={card.title} src={card.src}/>
))}
{cardData.length === 0 && <h3>Loading...</h3>}
</div>
)
}
export default CardList
import React, { useEffect, useState } from "react";
import Slider from "react-slick";
// import CardList from "../CardList/CardList";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import Card from "../Card/Card";
import "./SlickSlider.css"
const SlickSlider = () => {
const [cardData, setCardData] = useState([]);
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
arrows: true,
};
const getData=()=>{
fetch('data.json'
,{
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
)
.then(function(response){
console.log(response)
return response.json();
})
.then(function(myJson) {
console.log(myJson);
setCardData(myJson)
});
}
useEffect(()=>{
getData()
console.log(cardData)
},[])
return (
<div className="slick-slider">
<Slider {...settings}>
{cardData.map((card) => (
<Card src={card.src} title={card.title} key={card.id} />
))}
{/* <div>HELLO</div> */}
</Slider>
</div>
)
}
export default SlickSlider
The code below may work:
store reference of the slider
const sliderRef = useRef(null);
<Slider ref={sliderRef} ...>
...
</Slider>
and add click listener on the card component with slickGoTo method (https://react-slick.neostack.com/docs/api)
onClick={()=>{sliderRef.current.slickGoTo(index)}}
You can try to add simple button to see if it works
<button onClick={()=>{sliderRef.current.slickGoTo(3)}}>test</button>
You may know how to "forwardRef" to pass the reference of "SlickSlider" to "CardList ".
I'm looking to get some help as I've been stuck on this for a while. I have a MERN stack application interacting with the Github API. Users are able to search for Github users and save them to their profile on the app and once they do that they will also start following the user on Github. In the same fashion users are able to delete the users from their profile in the app and that will unfollow the same user on Github. The problem is that when I click on the delete user button, it does not update on the browser unless I refresh the page then I see that the user is deleted from the array of saved users. In the Profile.jsx component I am rendering the list of saved users and fetching the information from the database. In the SavedGithubUsersCard.jsx component I am mapping through the saved users data to render the user's information such as name. In DeleteButton.jsx I am deleting the specific saved user when I click on the button. My question is what am I doing wrong? is the prop change not being fired or is there another issue with UseEffect?
Here is the code:
import React, { useEffect, useContext } from 'react';
import swal from 'sweetalert';
import { AppContext } from '../context/AppContext';
import SavedGithubUsersCard from './SavedGithubUsersCard';
import axios from 'axios';
Profile.jsx
const Profile = () => {
const {
githubUserData, setGithubUserData
} = useContext(AppContext);
useEffect(() => {
axios.get('/api/githubdata').then((res) => {
setGithubUserData(res.data);
})
.catch((err) => {
if (err) {
swal('Error', 'Something went wrong.', 'error');
}
});
}, [setGithubUserData]);
return (
<div className="profile-saved-users">
<div className="githubUserData-cards">
<h1 className="saved-users-header">Saved users</h1>
{!githubUserData || githubUserData.length === 0 ? (
<p className="no-saved-users-text">No saved users yet :(</p>
) : (
<SavedGithubUsersCard githubUserData={githubUserData}/>
)}
</div>
</div>
);
};
export default Profile;
import React from 'react';
import { Card, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import DeleteButton from './DeleteButton';
SavedGithubUsersCard.jsx
const SavedGithubUsersCard = ({githubUserData}) => {
return (
<>
{githubUserData?.map(githubUserData => (
<Card key={githubUserData._id} id="saved-users-card">
<Card.Img
variant="top"
src={githubUserData.avatar_url}
id="saved-users-card-image"
/>
<Card.Body id="saved-users-card-information">
<Card.Title
id="saved-users-name"
style={{ textAlign: 'center' }}
>
{githubUserData.name}
</Card.Title>
<Card.Subtitle
id="saved-users-username"
className="mb-2 text-muted"
style={{ textAlign: 'center' }}
>
{githubUserData.login}
</Card.Subtitle>
<Card.Text id="saved-users-profile-url">
Profile URL: {githubUserData.html_url}
</Card.Text>
<Link
to={{ pathname: "githubUserData.html_url" }}
target="_blank"
>
<Button
id="saved-users-profile-button"
variant="outline-primary"
>
View profile
</Button>
</Link>
<DeleteButton githubUserDataId={githubUserData._id} githubUserDataLogin= {githubUserData.login} />
</Card.Body>
</Card>
))}
</>
);
};
export default SavedGithubUsersCard
DeleteButton.jsx
import React from 'react';
import { Button } from 'react-bootstrap';
import axios from 'axios';
import swal from 'sweetalert';
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const handleRemove = async () => {
try {
fetch(`https://api.github.com/user/following/${githubUserDataLogin}`, {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`
}
});
await axios({
method: 'DELETE',
url: `/api/githubdata/${githubUserDataId}`,
withCredentials: true
});
swal(
'Removed from profile!',
`You are no longer following ${githubUserDataLogin} on Github`,
'success'
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
<Button
variant="outline-danger"
style={{ marginLeft: 20 }}
onClick={handleRemove}
id="saved-users-delete-button"
>
Remove User
</Button>
);
};
export default DeleteButton;
AppContext.jsx
import React, { createContext, useState, useEffect } from 'react';
import axios from 'axios';
const AppContext = createContext();
const AppContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(false);
const [githubUserData, setGithubUserData] = useState([]);
const user = sessionStorage.getItem('user');
useEffect(() => {
// incase user refreshes and context is cleared.
if (user && !currentUser) {
axios
.get(`/api/users/me`, {
withCredentials: true
})
.then(({ data }) => {
setCurrentUser(data);
})
.catch((error) => console.error(error));
}
}, [currentUser, user]);
return (
<AppContext.Provider
value={{ currentUser, setCurrentUser, loading, setLoading, githubUserData, setGithubUserData}}
>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppContextProvider };
Thank you!
From what I see of your code when you delete a user you successfully delete them in the back end but don't also update the local state.
Option 1 - Refetch the users list and update local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
const usersDataRes = await axios.get('/api/githubdata');
setGithubUserData(usersDataRes.data);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
Option 2 - Attempt to manually synchronize the local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
setGithubUserData(users =>
users.filter(user => user._id !== githubUserDataId)
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
I am very new to react and node, I have managed to create an API for a simple todo list. I have fetched the data from the api and presenting it on the screen.
If I leave the dependency array empty on the useEffect() hook it will only render once and doesn't loop. But If I add a new Todo it will not update the list unless I refresh. So I put the todos state into the dependency array, this will then show the new item when I add it but if I look at the network tab in the dev tools its hitting the api in an infinite loop. What am I doing wrong ?
here is the code:
App
import React, { useState, useEffect } from "react";
import Todo from "./components/Todo";
import Heading from "./components/Heading";
import NewTodoForm from "./components/NewTodoForm";
const App = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
getTodos();
}, []);
return (
<div className="container">
<Heading todos={todos} />
<section className="todos-container">
<ul className="todos">
{todos.map((todo) => (
<Todo key={todo._id} todo={todo} />
))}
</ul>
</section>
<section className="todo-form">
<NewTodoForm />
</section>
</div>
);
};
export default App;
Heading
import React from "react";
const Heading = ({ todos }) => (
<header>
<h1>Todos</h1>
<p>
{todos.length} {todos.length === 1 ? "Item" : "Items"}
</p>
</header>
);
export default Heading;
Todo
import React, { useState } from "react";
const Todo = ({ todo }) => (
<li>
{todo.name}
<input type="checkbox" />
</li>
);
export default Todo;
NewTodoForm
import React, { useState } from "react";
import { Plus } from "react-feather";
const NewTodoForm = () => {
const [formData, setFormData] = useState({
name: "",
completed: false,
});
const { name } = formData;
const handleOnChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
await fetch("http://localhost:3001/api/todos", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
setFormData({
name: "",
completed: false,
});
};
return (
<form onSubmit={handleSubmit}>
<div className="form-control">
<Plus className="plus" />
<input
name="name"
type="text"
placeholder="Add New Item"
onChange={handleOnChange}
value={name}
/>
<button>Add</button>
</div>
</form>
);
};
export default NewTodoForm;
If I comment all the components out and only have the App component it still infinite loops when I add todos to the dependency array of the useEffect() hook.
So instead of giving that as a dependency write the function outside the useEffect so that you can call that function after you add a todo
Example:
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
useEffect(() => {
getTodos();
}, []);
So getTodos will only run once initially and runs again only on the onSubmit or onClick of your Todo, So, just call getTodos function onSubmit or onClick
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
}