I am making a small blog application using React JS. I am using the context api to store the user's responses globally (in InputContext.js), so that it can be used across different components.
What I want to achieve is, when the user inputs a new blog entry on a separate input page (WriteBlogPost.js) display all the blog entries on a separate page (AllBlogs.js). The page changes are being handled with react router. I have a problem where I am unable to add the new blog objects into the array defined in the context api component (allBlogPosts). I am unsure what is causing this, any explanations and guidance towards the right direction would greatly be appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
export const WriteBlogPost = () => {
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setAllBlogPosts(setBlogPost(blog))
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<div>
<label>
Title:
<input type="text" onChange={handleChange} value={blog.title} name="title" />
</label>
<label>
Author:
<input type="text" onChange={handleChange} value={blog.author} name="author" />
</label>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
<div>
<button onClick={handleBlogPost}>Submit</button>
</div>
</div>
)
}
AllBlogs.js(currently unable to map through the array as the array is empty)
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
export const AllBlogs = () => {
const { allBlogPosts } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<h1>All blogs</h1>
{allBlogPosts.map((post) =>
<div>
<p>{post.title}</p>
<p>{post.author}</p>
<p>{post.text}</p>
</div>
)}
</div>
)
}
Just update handleBlogPost
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
};
Related
I have an app that allows user take and delete notes and I'm trying to implement the edit function. The problem is that I don't want to take the user to a different page, I was the note they click on to change into a form or some kind of editable space (prepopulated with the current content of the note) that they can then save so that it renders again but with the updated values (Google Keep style).
The things that I'm struggling with is how to change just one note since the notes are mapped to components through an array - I've tried using the filter() method and playing with ternary operators, but I either copy the entire array or nothing happens. I came across this question: Google Keep edit functionality, but I'm struggling to understand what's happening and how to adapt it in my code.
So the main question is: how can I change one component without disrupting the other elements in the array or their positions on the page?
Here's my Note component so far:
import React, { useState, useEffect} from "react";
import axios from "axios";
function Note(props) {
const [noteToEdit, setNoteToEdit] = useState({
title: "",
content: "",
category: ''
})
const [isEditNote, setEditNote] = useState(false)
const [idToEdit, setIdToEdit] = useState('')
function deleteNote(id) {
axios.delete(`http://localhost:5000/notes/${id}`)
.then(() => { console.log("Note successfully deleted")
props.setFetch(true)
});
}
function editNote(id, title, content, category){
setEditNote(true)
setNoteToEdit(prevNote => {
return {
title : title,
content : content,
category : category
};
});
console.log("Current note to edit after useState:")
setIdToEdit(id)
console.log(noteToEdit)
}
return (
<div>
{isEditNote && <h1>want to edit: {idToEdit}</h1>}
{!isEditNote &&
<div>
{props.notes.map((noteItem) => {
return (
<div className="note">
<h1>{noteItem.title}</h1>
<p>{noteItem.content}</p>
<button onClick={() => {editNote(noteItem._id, noteItem.title, noteItem.category, noteItem.content)}}>
Edit
</button>
<button onClick={() => {deleteNote(noteItem._id)}}>
Delete
</button>
<p>{noteItem.category}</p>
</div>
);
})}
</div>
}
</div>
)
}
export default Note
and my CreateArea component:
import React, { useState, useEffect } from "react";
import Header from "./Header";
import Footer from "./Footer";
import ListCategories from "./ListCategories";
import CreateCategory from "./CreateCategory";
import Note from "./Note";
import axios from "axios"
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
function CreateArea() {
const [isExpanded, setExpanded] = useState(false);
const [categories, setCategories] = useState([])
const [notes, setNotes] = useState([])
const [fetchB, setFetch] = useState(true)
const [fetchCategories, setFetchCategories] = useState(true)
const [noteToEdit, setNoteToEdit] = useState({
title: "",
content: "",
category: ''
})
const [ieditNote, setEditNote] = useState(false)
const [note, setNote] = useState({
title: "",
content: "",
category: ''
});
useEffect(() => {
if(fetchCategories){
fetch('http://localhost:5000/categories')
.then(res => res.json())
.then(json => {
setCategories(json)
setFetchCategories(false)
})
}
}, [fetchCategories])
useEffect(() => {
if(fetchB) {
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {
console.log(json)
setNotes(json)
setFetch(false)
})
}
}, [fetchB])
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(e){
e.preventDefault();
axios.post("http://localhost:5000/notes/add-note", note)
.then((res) => {
setNote({
category: '',
title: "",
content: ""
})
setFetch(true)
console.log("Note added successfully");
console.log(note)
})
.catch((err) => {
console.log("Error couldn't create Note");
console.log(err.message);
});
}
function expand() {
setExpanded(true);
}
function filterNotes(category){
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {
const filtered = json.filter((noteItem) => (noteItem.category === category));
setNotes(filtered);
})
}
function editNote(id, title, content, category){
setNoteToEdit(prevNote => {
return { ...prevNote };
});
console.log("Current note to edit after useState:")
console.log(noteToEdit)
}
return (
<div>
<Header/>
<div className="categories">
<CreateCategory setFetchCategories={setFetchCategories}/>
<button className="all-button" onClick={()=>{setFetch(true)}}>All</button>
<ListCategories categories={categories} notes={notes} filterNotes={filterNotes} setFetch={setFetch}/>
</div>
<div className="notes-container">
<form className="create-note">
{isExpanded && (
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
)}
<textarea
name="content"
onClick={expand}
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows={isExpanded ? 3 : 1}
/>
<select
name="category"
onChange={handleChange}
value={note.category}>
{
categories.map(function(cat) {
return <option
key={cat.category} value={cat.value} > {cat.category} </option>;
})
}
</select>
<button onClick={submitNote}>Add</button>
</form>
<div className="notes-group">
<Note notes={notes} setFetch={setFetch} editNote={editNote} setEditNote={setEditNote}/>
</div>
</div>
<Footer/>
</div>
);
}
export default CreateArea;
Would appreciate any guidance on this, thanks!
This is the BookForm.js it is a component that use react context api , this component return a form that contain of 3 input tag
import React, { useContext, useState } from 'react';
import { BookContext } from '../contexts/BookContext';
/* ________________________________________________________________ */
const BookForm = () => {
const { dispatch } = useContext(BookContext);
const [title, setTitle] = useState(BookContext);
const [author, setAuthor] = useState(BookContext);
const handleSubmit = (e) => {
e.preventDefault();
// console.log(title, author);
dispatch({ type: 'ADD_BOOK', book: { title, author } });
// addBook(title, author);
setTitle('');
setAuthor('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="book title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
<input
type="text"
placeholder="author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
required
/>
<input type="submit" value="add book" />
</form>
);
};
export default BookForm;
The output is
the below codebox is BookContext.js that is a stateless function for providing book data and dispatch function and there is a useEffect hook to storing data of new books :
import React, { useReducer, createContext, useEffect } from 'react';
import { bookReducer } from '../reducers/bookReducer';
/* ________________________________________________________________ */
export const BookContext = createContext();
/* ________________________________________________________________ */
const BookContextProvider = (props) => {
const [books, dispatch] = useReducer(bookReducer, [], () => {
const localData = localStorage.getItem('books');
return localData ? JSON.parse(localData) : [];
});
useEffect(() => {
localStorage.setItem('books', JSON.stringify(books));
localStorage.getItem('books');
}, [books]);
/* ________________________________________________________________ */
return (
<BookContext.Provider value={{ books, dispatch }}>
{props.children}
</BookContext.Provider>
);
};
export default BookContextProvider;
If I am using const currentUserId = props.id then the error will be props.match is undefined, and the variable "userId" even is undefined when i am trying to console log.
Then, I using props.match.params.id the code can't even being read, it's only blank
Im also trying to modify the code become const currentUserId = props.computedMatch.params.id; but nothing works
My code:
import React, {useContext, useState, useEffect} from 'react';
import { GlobalContext } from '../context/GlobalState';
import { Link, useNavigate } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import {
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap';
export const EditUser = (props) => {
const [selectedUser, setSelectedUser] = useState({
id: '',
name: ''
});
const { users, editUser } = useContext(GlobalContext);
const history = useNavigate();
const currentUserId = props.match.params.id;
useEffect(() => {
const userId = currentUserId;
console.log(typeof userId);
const selectedUser = users.find(user => user.id === Number(userId))
setSelectedUser(selectedUser)
console.log(selectedUser);
}, [currentUserId, users])
const onSubmit = () => {
history('/');
}
const onChange = (e) => {
}
return (
<Form onSubmit={onSubmit}>
<FormGroup>
<Label>Nama</Label>
<Input type='text' onChange={onChange} placeholder='Ganti Nama Disini!'></Input>
</FormGroup>
<Button type='submit'>Edit Data</Button>
<Link to="/" className="btn btn-danger ml-2" style={{ marginLeft: "10px"}}>Cancel</Link>
</Form>
)
}
i hope this will fix
import { useParams } from 'react-router-dom';
const { id: currentUserId } = useParams();
use withRouter
import React, {useContext, useState, useEffect} from 'react';
import { GlobalContext } from '../context/GlobalState';
import { Link, useNavigate } from 'react-router-dom';
import { withRouter } from 'react-router'; // here added
import { v4 as uuid } from 'uuid';
import {
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap';
const EditUser = (props) => { // here changed
const [selectedUser, setSelectedUser] = useState({
id: '',
name: ''
});
const { users, editUser } = useContext(GlobalContext);
const history = useNavigate();
const currentUserId = this.props.match.params.id;
useEffect(() => {
const userId = currentUserId;
console.log(typeof userId);
const selectedUser = users.find(user => user.id === Number(userId))
setSelectedUser(selectedUser)
console.log(selectedUser);
}, [currentUserId, users])
const onSubmit = () => {
history('/');
}
const onChange = (e) => {
}
return (
<Form onSubmit={onSubmit}>
<FormGroup>
<Label>Nama</Label>
<Input type='text' onChange={onChange} placeholder='Ganti Nama Disini!'></Input>
</FormGroup>
<Button type='submit'>Edit Data</Button>
<Link to="/" className="btn btn-danger ml-2" style={{ marginLeft: "10px"}}>Cancel</Link>
</Form>
)
}
export default withRouter(EditUser); // here added
I'm following a tutorial to make a React todo app.
I have components and contexts files.
I have addItem function but when I clicked 'Add todo' button,
the item and date is not rendering into todo list.
Also, it shows an error as Warning: Each child in a list should have a unique "key" prop. even though
I have given an id.
Since I am following the tutorial, I don't know where I did wrong.
Would be appreciated if anyone could tell what is wrong.
App.js
import React from 'react';
import Navbar from './components/Navbar';
import Form from './components/Form';
import TodoList from './components/TodoList';
import TodoContextProvider from './contexts/TodoContexts';
function App() {
return (
<div className="App">
<TodoContextProvider>
<Navbar />
<TodoList />
<Form />
</TodoContextProvider>
</div>
);
}
export default App;
TodoContexts.js
import React, { createContext, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
export const TodoContext = createContext();
const TodoContextProvider = (props) => {
const [items, setItems] = useState([
{items: 'laundry', date: '2020-11-18', id: 1},
{items: 'lunch', date: '2020-11-20', id: 2}
]);
const addItems = (items, date) => {
setItems([...items, {items, date, id: uuidv4()}]);
};
const removeItems = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<TodoContext.Provider value={{ items, addItems, removeItems }}>
{props.children}
</TodoContext.Provider>
)
}
export default TodoContextProvider
TodoList.js
import React, { useContext } from 'react';
import TodoDetails from './TodoDetails';
import { TodoContext } from '../contexts/TodoContexts';
const TodoList = () => {
const { items } = useContext(TodoContext);
return items.length ? (
<div className="todo-list">
<ul>
{items.map(item => {
return ( <TodoDetails item={item} key={item.id} /> )
})}
</ul>
</div>
) : (
<div className="empty">You have no todos at the moment.</div>
)
}
export default TodoList
TodoDetails.js
import React, { useContext } from 'react';
import { TodoContext } from '../contexts/TodoContexts';
const TodoDetails = ({ item }) => { //TodoList item is props
const { removeItems } = useContext(TodoContext);
return (
<li onClick={() => removeItems(item.id)}>
<div className="items">{item.items}</div>
<div className="date">{item.date}</div>
</li>
)
}
export default TodoDetails
Form.js
import React, { useState, useContext } from 'react';
import './Form.css';
import { TodoContext } from '../contexts/TodoContexts';
const Form = () => {
const {addItems} = useContext(TodoContext);
const [items, setItems] = useState('');
const [date, setDate] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(items, date);
addItems(items, date);
setItems('');
setDate('');
}
return (
<form className="form" onSubmit={handleSubmit}>
<input
type="text"
value={items}
placeholder="Enter todo"
onChange={(e) => setItems(e.target.value)}
/>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<input type="submit" value="Add todo"/>
</form>
)
}
export default Form
Navbar.js
import React, { useContext } from 'react';
import { TodoContext } from '../contexts/TodoContexts';
const Navbar = () => {
const { items } = useContext(TodoContext);
return (
<div>
<h1>Todo List</h1>
<p>Currently you have {items.length} todos to get through...</p>
</div>
)
}
export default Navbar
Your error may be attributable to using same variable name of 'items' in addItems function:
Try changing the name of first argument to 'item' instead.
const addItems = (item, date) => {
setItems([...items, {item, date, id: uuidv4()}]);
};
I am practicing the new React hooks and I came with another question once again, cause I simply cannot find this on the internet.
I am trying to pass down a function as a prop from a function component to another function component. I am managing the states with the useState hook. In a class-based component you'd use this.props.addTodo(this.state) where I now have addTodo.addTodo(content). This feels kinda hacky. Isn't there a better way to do this? See the full code below.
Main app component
import React, { useState } from 'react';
import Todos from './Todos';
import AddTodo from './AddTodo';
function App() {
const [todos, setTodos ] = useState([
{id: 1, content: 'buy some milk'},
{id: 2, content: 'play mario kart'}
]);
const deleteTodo = (id) => {
const Todos = todos.filter(todo => {
return todo.id !== id
});
setTodos(Todos)
}
const addTodo = (todo) => {
console.log(todo) //Here I want to do something with the new todo value, which I got from the add todo component
}
return (
<div className="todo-app container">
<h1 className="center blue-text">Todo's</h1>
<Todos todos={todos} deleteTodo={deleteTodo} />
<AddTodo addTodo={addTodo} />
</div>
);
}
export default App;
The add todo's component
import React, { useState } from 'react';
function AddTodo(addTodo) {
const [content, setContent] = useState('');
const handleChange = (e) => {
setContent(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
addTodo.addTodo(content);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>Add new todo</label>
<input type="text" onChange={handleChange}/>
</form>
</div>
)
}
export default AddTodo;
I am pretty new to actively sharing my code and asking help in this way. So please let me know if there is more info needed on this subject!
Best regards
destruct a prop obj
import React, { useState } from 'react';
function AddTodo({addTodo}) {
const [content, setContent] = useState('');
const handleChange = (e) => {
setContent(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
addTodo(content);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>Add new todo</label>
<input type="text" onChange={handleChange}/>
</form>
</div>
)
}