I am building a blogging application. This application has frontend built with ReactJS and backend built with Python (Django Framework). The frontend and backend are connected with Django REST API Framework.
This post is on ReactJS Frontend.
I am using Functional Component. There is a "BlogList" Component where all blogs fetched from API endpoints will be displayed. I created a "BlogForm" Component where I have to enter 2 fields: "Title" and "Content" to create blog post. I imported the "BlogForm" Component inside "BlogList" Component to show it on the same page.
Please see this image to get a good understanding
When I enter "Title" and "Content" inside "BlogForm" Component and click on "Submit" button, data is saved in database using API call. But the added data is not showing in the "BlogList" Component at that time. I have to refresh the page to see the new blog post in the blog list.
Can anyone in the community help me to solve the problem that how can I instantly view the added blog post when I click on "Submit" button?
For your kind reference, the code is as below.
BlogList.js
import BlogForm from './BlogForm'
const BlogList = (url) => {
const [blogs, setBlogs] = useState([])
useEffect(() => {
async function fetchBlogData() {
const url = requests.blogList
const request = await axios.get(url)
setBlogs(request.data)
return request
}
fetchBlogData()
}, [url])
return (
<Wrapper>
<BlogWrapper className="blog">
// Blog Form Component Added
<BlogForm />
<div className="blog__header">
<h1 className="blog__header--title">
Information
</h1>
</div>
<hr className="blog__divider"/>
<div className="blog__list">
<ul>
<li className="blog__item">
{ blogs.map( (blog) => (
<div className="blog__item--container" key={ blog.id }>
<h1 className="blog__item--title">
<a className="blog__item--title blog__item--title--link" href={`/blog/${ blog.id }`}>
{ blog.title }
</a>
</h1>
<small className="blog__item--date">{ blog.date_published }</small>
<p className="blog__item--content">{ blog.content }</p>
</div>
) ) }
</li>
</ul>
</div>
</BlogWrapper>
</Wrapper>
)
}
export default BlogList
BlogForm.js
const BlogForm = () => {
const [inputValues, setInputValues] = useState({
title : '',
content : ''
})
const handleSubmit = async (event) => {
event.preventDefault()
setInputValues({ ...inputValues })
const { title, content } = inputValues
const blogPost = { title, content }
const url = requests.blogCreate
const response = await axios.post(url, blogPost)
return response
}
const handleChange = (name) => (event) => {
setInputValues({ ...inputValues, [name] : event.target.value })
}
return (
<div>
<form onSubmit={ handleSubmit }>
<input type="text" name="title" placeholder="Title" required value={ inputValues.title } onChange={ handleChange('title') }/>
<input type="text" name="content" placeholder="Content" required value={ inputValues.content } onChange={ handleChange('content') }/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default BlogForm
UPDATED
After the answer from DrewReese, I updated my code and I am getting error when I try to add a new Blog Post. It is showing undefined
BlogList has the state you want to be updated from the child component BlogForm. You can pass a callback from parent to child for BlogForm to call with the updated posts.
BlogList
const BlogList = (props) => {
const [blogs, setBlogs] = useState([]);
useEffect(() => {
async function fetchBlogData() {
const url = requests.blogList;
const request = await axios.get(url);
setBlogs(request.data);
}
fetchBlogData();
}, [props]);
// (1) Define a callback to update state
const addNewPost = post => setBlogs(posts => [...posts, post]);
return (
<Wrapper>
<BlogWrapper className="blog">
// Blog Form Component Added
<BlogForm addNewPost={addNewPost} /> // <-- (2) pass callback
...
</BlogWrapper>
</Wrapper>
);
}
BlogForm
const BlogForm = ({ addNewPost }) => { // <-- (3) destructure callback
const [inputValues, setInputValues] = useState({
title : '',
content : ''
})
const handleSubmit = async (event) => {
event.preventDefault()
setInputValues({ ...inputValues });
const { title, content } = inputValues;
const blogPost = { title, content };
const url = requests.blogCreate;
const response = await axios.post(url, blogPost);
addNewPost(response.data); // <-- (4) call callback with new post data
}
...
return (
...
);
}
Related
I tried a lots of things , and this problem does not seem to go away , can someone help me with this ??
this is my app component :
function App() {
const [todo, setTodo] = useState([]);
async function getTodo() {
try {
const todo = await axios.get("http://localhost:5000/api/todos");
// console.log(todo.data)
setTodo(todo.data);
} catch (error) {
console.log("something is wrong");
}
}
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
return (
<div className="App">
<h1>My Todo List</h1>
<h2>My Todo List</h2>
<Task Todor={todo} />
<Write />
</div>
);
}
export default App;
and this is my todos component :
function Todos({ Todor }) {
return (
<div className="Todos">
{Todor.map(T => <Todo post={T} />)}
</div>
);
}
export default Todos;
and this is my todo component :
function Todo({ post }) {
return (
<div className="Todo">
<h2>{post.title}</h2>
</div>
);
}
export default Todo ;
and this my add component :
export default function Write() {
const [inputText, setInputText] = useState({
title: ""
});
function handleChange(e) {
setInputText({
...inputText,
[e.target.name]: e.target.value,
});
}
const [status, setStatus] = useState(false);
async function addItem(e) {
e.preventDefault();
const res = await axios.post("http://localhost:5000/api/todos", inputText);
setInputText(inputText)
console.log("response:", res)
setStatus(true);
setInputText("");
}
return (
<div className="container">
<div className="form">
<input onChange={handleChange} type="text" name="title" />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
</div>
);
}
the new items dont show until I refresh the page , how to do that without refreshing ?
because obviously that defeats the purpose of React !!
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
The code inside useEffect with empty dependencies array [] only runs on the first render, to run it on every render you should remove the empty array dependencies.
useEffect(() => {
// Update the document title using the browser API
getTodo();
});
Note: It is not a best practice because your component will invoke getTodo() every time rerendered. In your case, you can use a state variable to control where to re-run the getTodo funtion e.g:
const [isAddedSuccess, setIsAddedSuccess] = useState(false)
Everytime you add new item successfully, just setIsAddedSuccess(true) and your useEffect should look like below:
useEffect(() => {
// Update the document title using the browser API
if (isAddedSuccess) getTodo();
}, [isAddedSuccess]);
This is my project for business card app.
I have a problem with using state and props between components.
Component tree looks like this.
Editor <- CardEditForm <- ImageFileInput
The url state is in the Editor component. There is a function to update state and give it as props to child components. When I upload an image on ImageFileInput component, url data goes up until the editor component and then using setUrl to url be updated. And then I gave url to CardEditorForm component.
The problem is this, In cardEditorForm, when it comes to using url props, I can't get the updated url. Only gets the initial state. I really need to get an updated url. I also tried to use setTimeout() to get the updated url. But it doesn't work either. What can I do?..
It's my first time to ask a question on stack overflow. Thank you for helping the newb.
Here is the code.
editor.jsx
const Editor = ({ cards, deleteCard, createOrUpdateCard }) => {
const [url, setUrl] = useState('');
const updateUrl = (src) => {
setUrl(src);
};
return (
<section className={styles.editor}>
<h1 className={styles.title}>Card Maker</h1>
{Object.keys(cards).map((key) => (
<CardEditForm
key={key}
card={cards[key]}
onDelete={deleteCard}
onUpdate={createOrUpdateCard}
updateUrl={updateUrl}
url={url}
/>
))}
<CardAddForm onAdd={createOrUpdateCard} updateUrl={updateUrl} url={url} />
</section>
);
};
card_edit_form.jsx
const CardEditForm = ({ card, onDelete, onUpdate, updateUrl, url }) => {
// ...
const changeUrl = () => {
setTimeout(() => {
const newCard = {
...card,
fileURL: url,
};
onUpdate(newCard);
}, 4000);
};
return (
<form className={styles.form}>
// ...
<div className={styles.fileInput}>
<ImageFileInput updateCard={changeUrl} updateUrl={updateUrl} />
</div>
// ...
</form>
);
};
export default CardEditForm;
image_file_input.jsx
const ImageFileInput = ({ updateUrl, updateCard }) => {
const [image, setImage] = useState('');
const upload = new Upload();
const onUpload = (e) => {
e.preventDefault();
upload.uploadImage(image).then((data) => updateUrl(data));
updateCard(e);
};
return (
<div>
<input type="file" onChange={(e) => setImage(e.target.files[0])} />
<button name="fileURL" onClick={onUpload}>
image
</button>
</div>
);
};
I am new to React. I am stuck on this problem for days now.
I have got a parent component which wraps two sibling components, "FileUpload" and "Documents"
The "FileUpload" is for uploading a file and "Documents" is for displaying all the uploaded files.
I want the "Documents" rerender after a new file is uploaded via "FileUpload", so that it shows the new file in the UI.
What would be the best approach to achieve this ?
Below is the code I have written so far for the sibling components:
FileUpload:
import React, { useState } from "react";
import Axios from "axios";
const FileUpload = (props) => {
const [files, setFiles] = useState([]);
const onInputChange = (e) => {
setFiles(e.target.files);
};
const handleSubmit = async (e) => {
e.preventDefault();
const data = new FormData();
for (let i = 0; i < files.length; i++) {
// console.log(files);
data.append("file", files[i]);
}
data.append("parentDbId", props.parentDbId);
data.append("parentObject", props.parentObject);
//console.log(data);
try {
await Axios.post("http://localhost:5000/upload", data);
} catch (err) {
console.error(err.message);
}
};
return (
<form
// action="http://localhost:5000/upload"
// method="POST"
//encType="multipart/form-data"
onSubmit={handleSubmit}
>
<div className="row mb-3">
<div className="col-lg-4">
<label htmlFor="formFileMultiple" className="form-label mb-0">
Add files
</label>
<input
className="form-control"
type="file"
id="formFileMultiple"
name="file"
multiple
onChange={onInputChange}
/>
</div>
<div className="col-lg-4 mt-0 gx-0">
<button type="submit" className="btn btn-primary mt-4">
Upload
</button>
</div>
</div>
</form>
);
};
export default FileUpload;
====================================================================
Documents:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Documents = (props) => {
const parentDbId = props.parentDbId;
const [documents, setDocuments] = useState([]);
//mount
useEffect(() => {
console.log("first use effect");
loadDocuments();
}, []);
const loadDocuments = async () => {
const result = await axios.get(
`http://localhost:5000/documents/${parentDbId}`
);
setDocuments(result.data);
};
return (
<>
<div className="row">
{documents.map((document, index) => (
<div className="col-lg-3" key={index}>
<a href={document.filePath}>{document.fileName}</a>
</div>
))}
</div>
</>
);
};
export default Documents;
Thanks,
Jimmy
Simple, just have the parent control document state and pass the state and callback down to the children as a prop. Now the siblings are referencing the same state and will be re-rendered when props (ie document state) changes. The parent can also handle the data fetching and uploading.
it will look like this:
const Parent = () => {
const [documents, setDocuments] = useState([]);
...do data fetching here
const handleSubmit = useCallback(async () => {}, []); // You might want to reset document state here?
return (
<div>
<Docs documents={documents} />
<Upload onUpload={setDocuments} onSubmit={handleSubmit} />
</div>
);
}
I wonder if you should actually have two documents components, one for displaying the files being uploaded, and one for displaying the already uploaded files. You would embed one within the Upload component and the other would fetch documents from the api every time onUpload completes
I'd like to display data fetched from API. The API url is based on user input. Once a user inputs and clicks submit, it is supposed to display the fetched data. But before user click submit, it should display nothing.
The problem I have is, when rendering the a user's data in lists in html, the map function doesn't exist on property{}, before user input anything. What should i set as the initial state?
The transformedData works fine. console.log(transformedData) prints
I'd like to display every title as a list. Example user input can be 0x147412d494731cbb91dbb5d7019464a536de04dc
import { useState } from "react";
// example user: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState({});
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
fetchNFTHandler();
};
function fetchNFTHandler() {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
const transformedData = data.assets.map((element, index) => {
return {
title: element.name,
id: index,
};
});
setData(transformedData);
console.log(transformedData);
});
}
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
console.log(data)
{/* {data.map((element) => (<li key={element.id}>
{element.title}</li>))}
*/}.
{/* here is the problem. before user input, this map function doesn't work */}
</div>
</header>
</div>
);
}
export default App;
i am trying to make a CRUD app in DRF-Reactjs by following Tania rascia's example
i have successfully implemented add, delete, list view. but i am trying to edit a specific row which is not updating in DRF backend. but the edited row is shown in the frontend list. why it is not updating in django admin list?
in DRF side views.py:
#api_view(['POST'])
def TodoUpdate(request, pk):
todo = Todo.objects.get(id=pk)
serializer = TodoSerializer(instance=todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
i am using cors header to interface between frontend to backend. here is the frontend code for edit:
App.js:
import React,{Fragment, useState,useEffect} from 'react'
import EditList from './components/EditList';
import axios from 'axios'
export default function App() {
const initialTodoSate = { id: null, title: "", body: "" };
const [todos, setTodos] = useState([]);
const [todoList, setTodolist] = useState(initialTodoSate);
const [editing, setEditing] = useState(false);
useEffect(()=>{
axios.get("http://localhost:8000/api/todo-list",{})
.then(res=>{
setTodos(res.data)
}).catch(err=>{
console.log(err)
})
},[])
const addTodoList = (todo) => {
axios
.post("http://localhost:8000/api/todo-create/",todo)
.then((res) => {
console.log(res.data);
todo.id = todos.length + 1;
setTodos([todo, ...todos]);
})
.catch((err) => {
console.log(err);
});
};
const deleteTodo = (id) => {
setEditing(false);
axios.delete(`http://localhost:8000/api/todo-delete/${id}/`)
.then(res=>{
setTodos(todos.filter((todo) => todo.id !== id));
}).catch(err=>{
console.log(err)
})
};
const updateTodo = ( id,updatedTodo) => {
axios
.post(`http://localhost:8000/api/todo-update/${id}/`, id)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
setEditing(false);
setTodos(todos.map((todo) => (todo.id === id ? updatedTodo : todo)));
};
const editRow = (todo) => {
setEditing(true);
setTodolist({
id: todo.id,
title: todo.title,
description: todo.description,
});
};
return (
<div className="container">
<h1>Django-based Todo with React Hooks</h1>
{editing ? (
<Fragment>
<h3>Edit Task</h3>
<EditList
editing={editing}
setEditing={setEditing}
todoList={todoList}
updateTodo={updateTodo}
/>
</Fragment>
) : (
<Fragment>
<CreateTodo addTodoList={addTodoList} />
<hr />
</Fragment>
)}
<div className="flex-row">
<div className="flex-large">
<TodoList todos={todos} editRow={editRow} deleteTodo={deleteTodo} />
</div>
</div>
</div>
);
}
and EditList.js:
import React, { useState,useEffect } from "react";
export default function EditList({ todoList, setEditing, updateTodo }) {
const [todo, setTodo] = useState([todoList]);
useEffect(() => {
setTodo(todoList);
}, [todoList]);
const handleChange = (e) => {
const { name, value } = e.target;
setTodo({ ...todo, [name]: value });
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
updateTodo(todo.id, todo);
}}
>
<label>Title:</label>
<br />
<input
type="text"
name="title"
value={todo.title}
onChange={handleChange}
/>
<br />
<label>Description:</label>
<br />
<input
type="text"
name="body"
value={todo.body}
onChange={handleChange}
/>
<br />
<button>Update Task</button>
<button onClick={() => setEditing(false)} className="button muted-button">
Cancel
</button>
</form>
);
}
when i try to edit one row with title and body, it is edited and after pressing the update button, the updated row included in the list. but the problem is when i look into the django admin it has not been updated and when i check the development tools, i found an error:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at form
at EditList (http://localhost:3000/static/js/main.chunk.js:511:3)
at div
at App (http://localhost:3000/static/js/main.chunk.js:70:83)
console. # vendors~main.chunk.js:31671
where am i having the mistake?
can anyone help me please? please let me know if you need any additional codes or information.
Trying to update something should be done in a put request, not a post request. This is a REST API convention, but a discrepancy may have some consequence down the line.
In this case, the error in your development tools is telling you that one of your components has an onChange/onSubmit etc property that is changing over the course of one mount from null to a function. This is not what's causing your issue, but I suspect it can be fixed by declaring the code in a handleSubmit function and then putting that into your onSubmit.
I think the error that's actually causing your problem is that the updatedTodo is not being sent to the backend. All that is being sent is the id (second parameter of axios.post). So if you pause the backend during execution, you would see that request.data = the id only, when it should be TodoSerializer's readable fields.
PS:
You can add a "debugger;" statement in the code after the updateToDo async request error to see what the error actually is (read more on the development tools debugging - browser dependent).
Don't abuse fragments - in this case, it would make for a more accessibility-friendly experience if you use divs in most of these components. Wouldn't it make more sense if the heading of some content was grouped with the content? https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters