I'm trying to build a calendar function where you can save the data you introduce to the database so it gets added as an event. The calendar is build with the fullcalendar module for React.
Here is the error I'm getting:
POST localhost:4000/events/create-event net::ERR_FAILED
Here is my models code:
const mongoose = require("mongoose")
const EventSchema = mongoose.Schema({
start: { type : Date, required: false },
end: { type : Date, required: false },
title: { type: String, required: false }
})
const Events = mongoose.model("events", EventSchema)
Here is where the event gets added:
const express = require('express');
const router = express.Router();
const moment = require("moment")
const Events = require('../models/events.js')
router.route('/create-event').post((req, res) => {
const events = {
events:req.body.event
}
const newEvent = new Events(events)
newEvent.save()
.then (() => res.json(newEvent))
.catch(err => res.status(400).json('error' + err))
})
router.route('/get-events').get((req, res) => {
Events.find({start: {$gte: moment(req.query.start).toDate()}, end: {$lte: moment(req.query.end).toDate()}})
.then(event => res.json(event))
.catch(err => res.status(400).json('Error:' + err))
})
module.exports = router
Here is the full code for client side:
import React, {useState, useRef} from 'react';
import FullCalendar from '#fullcalendar/react' // must go before plugins
import dayGridPlugin from '#fullcalendar/daygrid' // a plugin!
import AddEvent from './AddEvent.js'
import axios from "axios"
import moment from "moment"
import "react-datetime/css/react-datetime.css";
function CalendarSection() {
const [modalOpen, setModalOpen] = useState(false)
const [events, setEvents] = useState([])
const calendarRef = useRef(null)
console.log(events)
const onEventAdded = (event) => {
let calendarApi = calendarRef.current.getApi()
calendarApi.addEvent({
start: moment(event.start).toDate(),
end: moment(event.end).toDate(),
title: event.title
})
}
const handleEventAdd = (data) => {
console.log(data.event)
axios.post("localhost:4000/events/create-event", data.event)
}
const handleDateSet = async (data) => {
const response = await axios.get("localhost:4000/events/get-events?start=" +moment(data.start).toISOString() +"&end="+moment(data.end).toISOString())
setEvents(response.data)
}
return (
<section>
<button onClick={() => setModalOpen(true)}>Add Event</button>
<div style={{position: "relative", zIndex: 0}}>
<FullCalendar
ref={calendarRef}
events={events}
plugins={[ dayGridPlugin ]}
initialView="dayGridMonth"
eventAdd={(event) => handleEventAdd(event)}
dateSet={(date)=> handleDateSet(date)}
/>
</div>
<AddEvent isOpen={modalOpen} onClose={() => setModalOpen(false)} onEventAdded={event => onEventAdded(event)} />
</section>
)
}
export default CalendarSection
Here is the Add Event component:
import React, {useState} from 'react'
import Modal from "react-modal"
import Datetime from 'react-datetime';
function AddEvent({isOpen, onClose, onEventAdded}) {
const [title, setTitle] = useState("")
const [start, setStart] = useState(new Date())
const [end, setEnd] = useState(new Date())
const onSubmit = (e) => {
e.preventDefault()
onEventAdded({
title,
start,
end
})
onClose()
}
return (
<Modal isOpen={isOpen} onRequestClose={onClose}>
<form onSubmit={onSubmit}>
<input placeholder="Title" value={title} onChange={e => setTitle(e.target.value)} />
<div>
<label>Start Date</label>
<Datetime value={start} onChange={date => setStart(date)} />
</div>
<div>
<label>End Date</label>
<Datetime value={end} onChange={date => setEnd(date)} />
</div>
<button>Add Event</button>
</form>
</Modal>
)
}
export default AddEvent
Related
I'm working on a to-do list app in React that connects to a Firestore Database and I'm able to send the data correctly to Firebase, but my {task.name} is not displaying. The list numbers and the button are loading, but not {task.name}. The tasks are added to Firebase with the handleAdd function and the tasks are supposed to be loaded with the useEffect function. Here is the code for the main to-do.js file:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};
as far as I can tell you are setting task the wrong way, instated of mapping to data stay consistent with the original uploading logic, to avoid such cases you can type your state for the sake of consistency.
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
name: doc.data().name,
id: doc.data().id,
completed: doc.data().completed,
created: doc.data().created,
})))
I figured this out. I had to change {task.name} to {task.data.name}. I also updated the useEffect function. Here is the code:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.data.id}
key={task.data.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};
I am creating a blog website in which I am embedding react-draft-wysiwyg editor. I am facing problem when the user has to update the blog. When I click the update button the content is gone. I looked into many solutions but I couldn't make it work.
This is my code
import axios from "axios";
import React, { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router";
import { Link } from "react-router-dom";
import { Context } from "../../context/Context";
import "./singlePost.css";
import { EditorState, ContentState, convertFromHTML } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { convertToHTML } from 'draft-convert';
import DOMPurify from 'dompurify';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import Parser from 'html-react-parser';
export default function SinglePost() {
const location = useLocation();
const path = location.pathname.split("/")[2];
const [post, setPost] = useState({});
const PF = "http://localhost:5000/images/";
const { user } = useContext(Context);
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [updateMode, setUpdateMode] = useState(false);
useEffect(() => {
const getPost = async () => {
const res = await axios.get("/posts/" + path);
setPost(res.data);
setTitle(res.data.title);
setDesc(res.data.desc);
};
getPost();
}, [path]);
const handleDelete = async () => {
try {
await axios.delete(`/posts/${post._id}`, {
data: { username: user.username },
});
window.location.replace("/");
} catch (err) {}
};
// updating post
const handleUpdate = async () => {
try {
await axios.put(`/posts/${post._id}`, {
username: user.username,
title,
desc,
});
setUpdateMode(false)
} catch (err) {}
};
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
const [convertedContent, setConvertedContent] = useState(null);
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToHTML();
}
const convertContentToHTML = () => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
setDesc(currentContentAsHTML);
}
const createMarkup = (html) => {
return {
__html: DOMPurify.sanitize(html)
}
}
return (
<div className="singlePost">
<div className="singlePostWrapper">
{post.photo && (
<img src={PF + post.photo} alt="" className="singlePostImg" />
)}
{updateMode ? (
<input
type="text"
value={title}
className="singlePostTitleInput"
autoFocus
onChange={(e) => setTitle(e.target.value)}
/>
) : (
<h1 className="singlePostTitle">
{title}
{post.username === user?.username && (
<div className="singlePostEdit">
<i
className="singlePostIcon far fa-edit"
onClick={() => setUpdateMode(true)}
></i>
<i
className="singlePostIcon far fa-trash-alt"
onClick={handleDelete}
></i>
</div>
)}
</h1>
)}
<div className="singlePostInfo">
<span className="singlePostAuthor">
Author:
<Link to={`/?user=${post.username}`} className="link">
<b> {post.username}</b>
</Link>
</span>
<span className="singlePostDate">
{new Date(post.createdAt).toDateString()}
</span>
</div>
{updateMode ? (
// <textarea
// className="singlePostDescInput"
// value={desc}
// onChange={(e) => setDesc(e.target.value)}
// />
<Editor
contentState={desc}
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
) : (
<p className="singlePostDesc">{Parser(desc)}</p>
)}
{updateMode && (
<button className="singlePostButton" onClick={handleUpdate}>
Update
</button>
)}
</div>
</div>
);
}
I want to display desc which is saved in MongoDB database when the user clicks on update button.
The following part is what I tried to do but didn't work.
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
I am getting warning in this:
react.development.js:220 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the r component.
Please help
I'm doing a CRUD with redux. The application has a form with a username, and then a screen to create, view, edit and delete posts and I'm doing the editing part. However, when I use array.find() to traverse the array and look for each post, I'm getting this error:
Cannot read properties of undefined (reading 'find')
I will leave the codes below.
EditScreen:
import React, {useState} from 'react';
import '../_assets/modal.css';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom'
import {editPost} from '../redux/postsslice';
function EditModal({ closeModal}) {
const { pathname } = useLocation();
const postId = pathname.replace("/edit-post/", "")
const post = useSelector((state) => state.posts.find((post) => post.id === postId))
const dispatch = useDispatch()
const navigation = useNavigate();
const [title, setTitle] = useState(post.title)
const [content, setContent] = useState(post.content)
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostClicked = (e) => {
if (title && content) {
dispatch(editPost({id: postId, title, content}))
}
}
return (
<div className="modalBackground">
<div className="modalContainer">
<div className="title"><h1>Edit item</h1></div>
<h2>Title</h2>
<form>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={onTitleChanged}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={onContentChanged}
></textarea>
<button onClick={onSavePostClicked}>SAVE</button></form>
</div>
</div>
)
}
export default EditModal
mainscreen:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import {nanoid} from 'nanoid'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: nanoid(),
title,
content
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
if (user === '') {
return <Navigate to="/" />
} else {
return (
<div className="containerMainScreen">
{openModal && <Modal closeModal={setOpenModal} />}
{openEditModal && <EditModal closeModal={setOpenEditModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(true);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(true);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<br></br>
<textarea style={{ border: "none" }}>{post.content}</textarea>
</div>
</div>
))}
</div>
);
}
}export default MainScreen;
postSlice:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload); // modifies the draft state.
},
editPost(state, action) {
const { id, title, content } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
}
}
});
export const { addPost, editPost } = postsSlice.actions
export default postsSlice
editModal.js (edit page)
import React, {useState} from 'react';
import '../_assets/modal.css';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom'
import {editPost} from '../redux/postsslice';
export function EditModal({ closeModal}) {
const { pathname } = useLocation();
const postId = parseInt(pathname.replace("edit-post/", ""))
const post = useSelector((state) => state.loadPosts.find((post) => post.id === postId))
const dispatch = useDispatch()
const navigation = useNavigate();
const [title, setTitle] = useState(post.title)
const [content, setContent] = useState(post.content)
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostClicked = (e) => {
if (title && content) {
dispatch(editPost({id: postId, title, content}))
}
}
return (
<div className="modalBackground">
<div className="modalContainer">
<div className="title"><h1>Edit item</h1></div>
<h2>Title</h2>
<form>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={onTitleChanged}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={onContentChanged}
></textarea>
<button onClick={onSavePostClicked}>SAVE</button></form>
</div>
</div>
)
}
export default EditModal
store.js:
import { configureStore } from '#reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'
const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
})
export default store
signup page:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name))
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
With the state shape:
const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
});
Then the path to the posts state is state.loadPosts, so the selector in EditModal should be:
const post = useSelector((state) => state.loadPosts.find(
(post) => post.id === postId),
);
The error TypeError: Cannot read properties of undefined (reading 'title') is closely related to this line of code. If state.loadPosts is an empty array or there are not matching posts by id, then .find returns undefined.
const [title, setTitle] = useState(post.title); // <-- throws error on undefined post
const [content, setContent] = useState(post.content); // <-- throws error on undefined post
A quick fix could be to use the Optional Chaining operator
const [title, setTitle] = useState(post?.title);
const [content, setContent] = useState(post?.content);
but this only sets the initial state. If there is no matching post to edit then there's no point in rendering the editing UI. At this point you should render some fallback UI or navigate back, etc...
enter image description hereError: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-cal
import React, { useEffect, useState } from 'react';
import "./AddKeywordsPage.css";
import './AddedKeywords';
import AddedKeywords from './AddedKeywords';
import { Link, useHistory } from 'react-router-dom';
import { db } from '../../firebase';
import firebase from 'firebase';
import FeedCreated from '../LeftMenuBar/FeedCreated';
import { useSelector } from 'react-redux';
import { selectUser } from '../features/userSlice';
function AddKeywordsPage({ toggle }) {
const [keywords, setKeywords] = useState([]);
const [input, setInput] = useState('');
const [feedInput, setFeedInput] = useState('');
const history = useHistory();
const user = useSelector(selectUser);
// useEffect(() => {
// db.collection('keywords').orderBy('timestamp', 'desc').onSnapshot((snapshot) =>
// setKeywords(
// snapshot.docs.map((doc) => (
// {
// id: doc.id,
// data: doc.data()
// }
// ))
// ))
// }, [])
useEffect(() => {
db.collection('keywords').orderBy('timestamp', 'desc').onSnapshot((snapshot) =>
setKeywords(
snapshot.docs.map((doc) => (
{
id: doc.id,
data: doc.data()
}
))
))
}, [])
const addKeywordTag = (e) => {
// e.preventDefault();
db.collection('keywords').add({
keyword: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
setInput("");
};
const addFeed = (eve) => {
eve.preventDefault();
db.collection('feeds').add({
feed: feedInput,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
console.log(setFeedInput);
setFeedInput("");
setKeywords([]);
alert("Feed added successfully");
}
return (
<div className="addkeywordspage_main">
<div className="addkeywordspage">
<div>
<p className="addkeywordspage_keywords">Create Feed</p>
<div className="addkeywordspage_feed">
<TextField
value={feedInput}
onChange={eve => setFeedInput(eve.target.value)}
style={{ width: '300px'}}
id="outlined-basic"
label="Add Feed Name"
variant="outlined"
InputLabelProps={{style: {fontSize: 15}}}
/>
</div>
<div className="addkeywordspage_flex">
<div className="addkeywordspage_textfield">
<TextField
value={input}
onChange={e => setInput(e.target.value)}
style={{ width: '400px' }}
id="outlined-basic"
label="Add phrases"
variant="outlined"
InputLabelProps={{style: {fontSize: 15}}}
/>
</div>
<div className="addkeywordspage_button1">
<button onClick={addKeywordTag}>Add</button>
</div>
</div>
<div className="over">
{keywords.map(({ id, data: { keyword } }) => (
<AddedKeywords
id={id}
keyword={keyword}
/>
))}
</div>
<div className="add_feed_button">
<button onClick={addFeed} >Add Feed</button>
</div>
</div>
</div>
</div>
)
}
export default AddKeywordsPage;
l for tips about how to debug and fix this problem.
I'm new to react.
Now trying to make form with react hooks, and I want to render Cloud component only when press submit button. But it rendered every onChange called.
I know that onChange re-rendered cause also useState hook.
But have no idea how to render only when press submit button.
My final goal is when write name and press enter, if value is not contained in api, setShake make shake True and if True, put shake-cloud class in Cloud.js.
REACT IS TOO DIFFICULT :(
Thanks for help tho :)
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
<Cloud cloudhex={cloudHex} shake={shake} />
</div>
</div>
)}
</>
);
};
export default App;
Cloud.js
import React, {useEffect} from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCloud } from "#fortawesome/free-solid-svg-icons";
import './cloud.css';
const Cloud = ({cloudhex, shake}) => {
useEffect(() => {
}, [])
console.log(shake)
return (
<div className={`cloud-wrap ${ shake ? "shake-cloud":''}`}>
<span className="cloudhexname">{cloudhex}</span>
<FontAwesomeIcon icon={faCloud} className="cloud" style={{color:`${cloudhex}`}} />
</div>
);
};
export default Cloud;
A good approach in this case is to use useRef() Hook to store our search field value, instead of using useState(). Because useRef() Hook does not force a re-render while useState() does. This approach is known as un-controlled way to use input field.
You basically need to make few modifications in your code which are as follows:
const search = useRef("");
Then remove onChange={updateSearch} and value={search} from input and use a property ref={search}. So that your input looks like below:
<input
className="search-bar"
type="text"
ref={search}
/>
Then in the submit handler, you can get the value of the input field using search.current.value. So your getSearch() would look like
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search.current.value);
isColor();
};
Assuming user has typed an input. If not then you can set a validation before using setQuery() in the getSearch() form submit handler.
if(search.current.value){
setQuery();
}
Note: If you have any other controlled inputs in your project, you can change then to un-controlled inputs using refs and this way re-renders would not happen in your code.
Do it like that
If you want to render cloud component after form submit then put one flag and toggle that, here I take clicked state
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
const [clicked, setClicked] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
{clicked && <Cloud cloudhex={cloudHex} shake={shake} />}
</div>
</div>
)}
</>
);
};
export default App;