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...
Related
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
in my CRUD app, the user on the first screen enters their username and then can create, edit, and delete posts. I have an array of users and an array of posts (I'm using Redux). When a user creates a post, their username needs to appear in a sector within the div to identify who posted the post, but I still can't find a way to get the right user who is logged in to show in the div. How could I capture the specific user who is currently logged in?
I thought of doing something like: when the user typed their username and went to the posts screen, all posts would be linked to this user, but I don't know how to do that
postsSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload);
},
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
}
},
postDeleted(state, action) {
const { id } = action.payload;
const existingPost = state.some((post) => post.id === id);
if (existingPost) {
return state.filter((post) => post.id !== id);
}
},
},
});
export const { addPost, editPost, postDeleted } = postsSlice.actions
export default postsSlice
usersSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: [],
reducers: {
saveUser (state, action) {
state.push(action.payload)
},
}
});
export const { saveUser, replaceUsers } = userSlice.actions;
export default userSlice
mainscreen.js:
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 { useNavigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const navigate = useNavigate();
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: new Date(),
title,
content,
user
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const handleButton = (e) => {
e.preventDefault()
navigate("/")
}
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
console.log({ posts })
return (
<div className="containerMainScreen">
<button onClick={handleButton}>Back</button>
{openModal && <Modal deleteId={openModal} closeModal={setOpenModal} />}
{openEditModal && <EditModal editId={openEditModal} 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.slice().reverse().map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<br></br>
<textarea style={{ border: "none" }} value={post.content}></textarea>
</div>
</div>
))}
</div>
);
}
export default MainScreen;
Signup:
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;
You can store a current user and maintain a list of all users that ever "signed in".
user.slice.js
const initialState = {
currentUser: '',
users: [],
}
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
saveUser(state, action) {
// person that just signed in
state.currentUser = action.payload;
// list of unique user names
state.users = Array.from(new Set([...state.users, action.payload]));
},
replaceUsers: (state, action) => action.payload
}
});
export const { saveUser } = userSlice.actions;
export default userSlice;
MainScreen.js
Add an author property to each added post, and access the post.author property when mapping the posts.
function MainScreen() {
const dispatch = useDispatch();
const { currentUser } = useSelector((state) => state.user); // <-- current user
const posts = useSelector((state) => state.loadPosts);
...
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: uuidV4(),
title,
content,
author: currentUser // <-- current user to post
})
);
setTitle("");
setContent("");
};
...
if (currentUser === "") {
return <Navigate to="/" />;
} else {
return (
<div className="containerMainScreen">
...
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{
color: "white",
fontSize: "45px",
paddingLeft: "23px"
}}
/>
</div>
<div id="postowner">
<h3>#{post.author}</h3> // <-- user in post object
<br></br>
<textarea
style={{ border: "none" }}
value={post.content}
></textarea>
</div>
</div>
))}
</div>
);
}
}
export default MainScreen;
I have a two Input form and a Paragraph, when I try to change the value of input the paragraph get updated, once the paragraph is updated I am trying to edit the paragraph with the help of drafts library, but once I update the paragraph and save it, it doesn't update the paragraph.
Please anyone Help me out to solve the problem
Codesandbox Link : Code
Context API
import React, { useState, createContext } from "react";
export const Contx = createContext();
export const ConProvider = ({ children }) => {
const [customerName, setCustomerName] = useState("");
const [amount, setAmount] = useState("");
const defaultValue = `<p>Hello ${
customerName === "" ? "User" : customerName
},</p>
<p>Please Click on the link below to pay the order - <strong>${amount}</strong> . </p>
<p>Click hear to pay</p>
<br/>
<p>Thanks and Regards</p>
<p>testUser</p>`;
const [draftValue, setDraftValue] = useState(defaultValue);
return (
<Contx.Provider
value={{
defaultValue,
setCustomerName,
setAmount,
customerName,
amount,
setDraftValue,
draftValue
}}
>
{children}
</Contx.Provider>
);
};
homePage
import React, { useContext, useState } from "react";
import ReactDOM from "react-dom";
import { ConProvider, Contx } from "../ContextApi";
import Data from "./Component/Data/Data";
import NewDraft from "./Component/Data/NewDraft";
import Modal from "./Component/Data/Modal";
import "./styles.css";
function App() {
const { defaultValue, setDraftValue, draftValue } = useContext(Contx);
// console.log("defaultValue", defaultValue);
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div className="App">
<Data />
<Modal handleClose={handleClose} show={show}>
<NewDraft
prsFunc={setDraftValue}
handleClose={handleClose}
defaultValueEmpty={false}
defaultValue={defaultValue}
/>
</Modal>
<div
className="templateStyle p-2"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: draftValue && draftValue
}}
/>
<button onClick={handleShow}>Edit</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<ConProvider>
<App />
</ConProvider>,
rootElement
);
Input Form
import React, { useContext } from "react";
import { Contx } from "../../../ContextApi";
export default function Data() {
const {
setCustomerName,
setDraftValue,
defaultValue,
setAmount,
customerName,
amount
} = useContext(Contx);
React.useEffect(() => {
setDraftValue(defaultValue);
});
// console.log("fffffff", customerName, amount);
return (
<div>
<input
type="text"
value={customerName}
name="customerName"
placeholder="Enter Customer name"
onChange={(e) => {
setCustomerName(e.target.value);
}}
/>
<input
type="number"
placeholder="Enter Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
);
}
DraftJS
import React, { useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import {
EditorState,
convertToRaw,
ContentState,
convertFromHTML
} from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import draftToHtml from "draftjs-to-html";
import "./Data.css";
export default function NewDraft({
handleClose,
defaultValue,
defaultValueEmpty,
prsFunc
}) {
const initialState = defaultValueEmpty
? () => EditorState.createEmpty()
: EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(defaultValue))
);
const [editorState, setEditorState] = useState(initialState);
const onChange = (value) => {
setEditorState(value);
};
const saveData = () => {
prsFunc(draftToHtml(convertToRaw(editorState.getCurrentContent())));
handleClose();
};
// console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())));
return (
<div>
<div style={{ border: "2px solid", padding: "20px" }}>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(value) => onChange(value)}
/>
<button variant="secondary" onClick={saveData}>
Save
</button>
</div>
</div>
);
}
The problem you are facing is caused by this line in Data component. Every time the component is updated, the draftValue is set to the defaultValue.
React.useEffect(() => {
setDraftValue(defaultValue);
});
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;
I'm building app, using hooks and I got stuck.
I do not why but my Component does not download a state from my Context Component or maybe my initial state does not update correctly. Bellow I insert a few screenshot from my app.
Context Component:
import React, { createContext, useState } from 'react';
export const WeatherDataContext = createContext();
const WeatherDataContextProvider = (props) => {
const [weather, setWeather] = useState(
{
city: null,
temp: null
}
)
const addWeather = (city, temp) => {
setWeather({
city,
temp
})
}
return (
<WeatherDataContext.Provider value={{weather, addWeather}}>
{props.children}
</WeatherDataContext.Provider>
)
}
export default WeatherDataContextProvider
Form - axios - Component:
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
import axios from 'axios'
import {Link} from 'react-router-dom'
const WeatherForm = () => {
const {addWeather} = useContext(WeatherDataContext);
const [value, setValue] = useState('')
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
})
}
return (
<div class='weather-form'>
<form onSubmit={handleChange}>
<input placeholder='City' onChange={(e) => setValue(e.target.value)} value={value} required/>
<Link to='/weather'><button>Search</button></Link>
</form>
</div>
)
}
export default WeatherForm
And final component where I want to use my update state
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
const WeatherFront = () => {
const {weather} = useContext(WeatherDataContext)
console.log(weather)
return (
<div class='weather-front'>
<h1>City: {weather.city}, Temperatura: {weather.temp}</h1>
</div>
)
}
export default WeatherFront
The problem may be that you are not submitting the form.
<Link to='/weather'><button>Search</button></Link>
just navigates to WeatherFront.
You may try
import { useHistory } from "react-router-dom";
...
const WeatherForm = () => {
const history = useHistory()
const {addWeather} = useContext(WeatherDataContext)
const [value, setValue] = useState('')
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
history.push('/weather')
})
}
return (
<div class='weather-form'>
<form onSubmit={handleChange}>
<input placeholder='City' onChange={(e) => setValue(e.target.value)} value={value} required/>
<input type="submit" value="Search" />
</form>
</div>
)
}