I'm referring to this post (Write to a text or JSON file in react with node).
I got the exact problem so I tried out the solutions. I don't understand the solutions very well. I spent a lot of time doing by trial and errors. For example, in this solution:
const handleSaveToPC = jsonData => {
const fileData = JSON.stringify(jsonData);
const blob = new Blob([fileData], {type: "text/plain"});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = 'filename.json';
link.href = url;
link.click();
}
Where should I put this code? The variable "handleSaveToPC" is declared but not used. How should I use it? I show my App.js and index.js files below. My goal is to write the contactsData array to a local json or text file. I do appreciate if anyone guide me exactly what I have to do. Thank you so much.
App.js
import React, { useState, Fragment } from "react";
import AddContactForm from "./forms/AddContactForm";
import EditContactForm from "./forms/EditContactForm";
import ContactTable from "./tables/ContactTable";
const App = () => {
const contactsData = [
{ id: 1, organization: "A Ltd.", name: "Carol", email: "carol#gmail.com" },
{ id: 2, organization: "B Ltd.", name: "Paul", email: "paul#gmail.com" },
{ id: 3, organization: "C Ltd.", name: "Emily", email: "emily#gmail.com" }
];
const initialFormState = { id: null, organization: "", name: "", email: "" };
const [contacts, setContacts] = useState(contactsData);
const [currentContact, setCurrentContact] = useState(initialFormState);
const [editing, setEditing] = useState(false);
const addContact = (contact) => {
contact.id = contacts.length + 1;
setContacts([...contacts, contact]);
};
const deleteContact = (id) => {
setEditing(false);
setContacts(contacts.filter((contact) => contact.id !== id));
};
const editRow = (contact) => {
setEditing(true);
setCurrentContact({
id: contact.id,
organization: contact.organization,
name: contact.name,
email: contact.email
});
};
const updateContact = (id, updatedContact) => {
setEditing(false);
setContacts(
contacts.map((contact) => (contact.id === id ? updatedContact : contact))
);
};
return (
<div className="container">
<h1>Address Book</h1>
<div className="flex-row">
<div className="flex-large">
{editing ? (
<Fragment>
<h2>Edit contact</h2>
<EditContactForm
editing={editing}
setEditing={setEditing}
currentContact={currentContact}
updateContact={updateContact}
/>
</Fragment>
) : (
<Fragment>
<h2>Add contact</h2>
<AddContactForm addContact={addContact} />
</Fragment>
)}
</div>
<div className="flex-large">
<h2>View contacts</h2>
<ContactTable
contacts={contacts}
editRow={editRow}
deleteContact={deleteContact}
/>
</div>
</div>
</div>
);
};
export default App;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
EDIT: As you're trying to download a file through the frontend, you're able to use your function pretty much anywhere. Just add for example a button, like this:
<button onClick={handleSaveToPC}>download</button>
and change your function to:
const handleSaveToPC = () => {
const fileData = JSON.stringify(contactsData);
const blob = new Blob([fileData], {type: "text/plain"});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = 'filename.json';
link.href = url;
link.click();
}
Original answer:
I suppose you're using React for the frontend and node for the backend. If so, you could send the data to a backend route, and there write the data to the PC with this function.
I'd suggest using fs.
This is an example function for how you could do it.
const fs = require('fs')
const writeToJSON = (data) => {
const DATA_PATH = '/path/to/file'
fs.writeFile(
DATA_PATH,
JSON.stringify(data),
(err) => {
if (err) return console.error(err)
console.log('Wrote data to ', DATA_PATH)
},
)
}
Related
I have a NextJS application that is using the ShopifyBuy SDK. I have been successfully able to implement a solution where I am able to fetch the products from Store and display them to the User. The user is also able to go to a product page and add the product to the cart.
However, when the user refreshes the page, the cart is reset, and the data does not persist. The code is below:
context/cart.js:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
import Cookies from "js-cookie";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState = {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const savedState = Cookies.get("cartState");
const [state, dispatch] = useReducer(reducer, savedState || initalState);
useEffect(() => {
Cookies.set("cartState", state, { expires: 7 });
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
products/[handle].tsx:
import React, { useState, useEffect } from "react";
import client from "../../lib/client";
import { useCartDispatch, useCartState } from "../../context/cart";
import Link from "next/link";
import cookie from "js-cookie";
export const getStaticPaths = async () => {
const res = await client.product.fetchAll();
const paths = res.map((product: any) => {
return {
params: { handle: product.handle.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const handle = context.params.handle;
const res = await client.product.fetchByHandle(handle);
const product = JSON.stringify(res);
return {
props: {
product,
},
};
};
function Product({ product }: any) {
const { state } = useCartState();
const { setCart } = useCartDispatch();
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
setCart(res);
};
product = JSON.parse(product);
return (
<div>
<div className=" flex-col text-2xl font-bold m-8 flex items-center justify-center ">
<h1>{product.title}</h1>
<button onClick={addToCart}>Add to Cart</button>
<Link href="/cart">Checkout</Link>
</div>
</div>
);
}
export default Product;
pages/cart/index.tsx:
import React, { useEffect } from "react";
import { useCartState, useCartDispatch } from "../../context/cart";
import client from "../../lib/client";
function Cart() {
const { state } = useCartState();
return (
<div>
<h1>Cart</h1>
{state.lineItems &&
state.lineItems.map((item: any) => {
return (
<div key={item.id}>
<h2>{item.title}</h2>
<p>{item.variant.title}</p>
<p>{item.quantity}</p>
</div>
);
})}
</div>
);
}
export default Cart;
I have tried using a library called js-cookie and also localStorage. I'm not sure where the problem lies or if the solutions that I've tried are wrong.
P.S.: I'm fairly new to NextJS and Typescript so go easy on the syntax. This code is for a personal project. Thanks in advance!
Answering this because I ended up coming up with a solution that works for me, at least.
Here it is:
const getCart = async () => {
try {
const checkoutId = Cookies.get("checkoutId");
let cart;
if (checkoutId) {
cart = await client.checkout.fetch(checkoutId);
} else {
cart = await client.checkout.create();
Cookies.set("checkoutId", cart.id);
}
setCart(cart);
} catch (err) {
console.log(err);
}
};
From my understanding, what this does is the following:
Check the cookies to see if one exists called "checkoutId"
If it exists, fetch the cart using that checkoutId
Otherwise, create a new cart and create a cookie using the cart.id that is returned in the response
Then, inside my individual Product page ([handle].tsx), I'm doing the following:
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
console.log(res);
if (cookie.get("checkoutId") === undefined) {
cookie.set("checkoutId", res.id);
}
setCart(res);
};
Using cookies to store your object cart, as far as I know, is not a good idea. You could use localStorage, like so:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState =
typeof localStorage !== "undefined" && localStorage.getItem("cartState")
? JSON.parse(localStorage.getItem("cartState"))
: {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initalState);
useEffect(() => {
localStorage.set("cartState", JSON.stringify(state));
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>{children}</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
I'm new to react so pardon any mistake.
I want to update user profile picture but i dont know how can i make a clickable image(variable name ->"avatar" in code) which previews and then update(in database also), if we click save.
Can someone help me adding a clickable image that previews and updates the from and database as well.
If we cannot make clickable image, a separate button for upload and save will also be fine.
Let me know if you want more info about code.
the code i have:
import React from 'react';
import ProfileBanner from '../ProfileBanner';
import coverSrc from 'assets/img/generic/4.jpg';
//import avatar from 'assets/img/team/2.jpg';
import { Col, Row } from 'react-bootstrap';
import ProfileSettings from './ProfileSettings';
//import ExperiencesSettings from './ExperiencesSettings';
//import EducationSettings from './EducationSettings';
//import AccountSettings from './AccountSettings';
import BillingSettings from './BillingSettings';
import ChangePassword from './ChangePassword';
import DangerZone from './DangerZone';
import axios from 'axios';
import Cookies from 'universal-cookie';
import { useEffect ,useState,useRef} from 'react';
import { toast } from 'react-toastify';
const Settings = () => {
const [formData, setFormData] = useState({
avatar: '',
});
//const { avatar} = formData;
const cookies = new Cookies();
const user_id = cookies.get('xyz');
useEffect(async () => {
//e.preventDefault();
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const { data } = await axios.post(
`/api/auth/ImageRetrieve`,
{
user_id
},
config
);
setFormData({
avatar:data.link
});
}
catch (error) {
toast.success('Something went wrong', {
theme: 'colored'
});
}
}, []);
return (
<>
<ProfileBanner>
<div>
<ProfileBanner.Header
coverSrc={coverSrc}
avatar={formData.avatar}
className="mb-8"
/>
</div>
</ProfileBanner>
<Row className="g-3">
<Col lg={8}>
<ProfileSettings />
{/*<ExperiencesSettings />
<EducationSettings />}*/}
<br></br>
<ChangePassword />
</Col>
<Col lg={4}>
<div className="sticky-sidebar">
{/* <AccountSettings />*/}
<BillingSettings />
<DangerZone />
</div>
</Col>
</Row>
</>
);
};
export default Settings;
you can basically add onClick property to any native element, so
<span onClick={()=>{ /*put here some code that change the coverSrc state*/}}>
<ProfileBanner.Header
coverSrc={coverSrc}
avatar={formData.avatar}
className="mb-8"
/>
</span>
you can create a popup to enter new pic URL and than on-close update that state.
According to my understanding of the problem, I think you are looking something like this -
const [selectedAvatar, setSelectedAvatar] = useState();
const [formData, setFormData] = useState({
avatar: '',
});
useEffect(() => {
if (!selectedAvatar) {
setFormData({...formData, avatar: ''});
return;
}
const objectUrl = URL.createObjectURL(selectedAvatar);
//DB call to save objectUrl into the DB
setFormData({...formData, avatar: objectUrl});
return () => URL.revokeObjectURL(objectUrl);
}, [selectedAvatar]);
const onSelectFile = e => {
if (!e.target.files || !e.target.files.length) {
setSelectedAvatar('');
return;
}
setSelectedAvatar(e.target.files[0]);
}
return (
<div>
<input type='file' onChange={onSelectFile} />
{!!selectedAvatar && <div>
<ProfileBanner.Header
coverSrc={coverSrc}
avatar={formData.avatar}
className="mb-8"
/>
</div>
}
</div>
)
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");
};
I am using React's Context API to share data that most of my components need.
The Context is initially defined, but shortly receives data from the Firebase database (please see IdeaContext.tsx). I define the context in a functional component and the display component, which returns a small card based on the information received.
However, the component doesn't render when I start the development server with Yarn. Instead, in order to get it to render, I have to write console.log('something') inside the display component and then it suddenly re-renders. However, when I refresh the server, it again doesn't render.
How can I make my component render immediately (or at least after the context updates with the data from the database?)
Code:
Context Definition:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
function getIdeas() {
var arr: Array<Idea> = [];
ideasRef.on('value', (snapshot) => {
let items = snapshot.val()
snapshot.forEach( (idea) => {
const obj = idea.val()
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID
})
console.log(arr)
})
})
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect( ()=> {
console.log('getting info')
setIdeas(getIdeas())
}, [])
useEffect( () => {
console.log('idea change: ', ideas)
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
Displayer and Card Component
import React, { FC, ReactElement, useContext } from "react";
import IdeaCreator from "./IdeaCreator";
import { IdeaContext } from "./IdeaContext";
import { Idea } from "../t";
import { Link } from "react-router-dom";
const IdeaPost:React.FC<Idea> = ({title, keyID, description}):ReactElement => {
console.log('Received',title,description,keyID)
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img
className="w-full"
src="#"
alt="Oopsy daisy"
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2"> <Link to={"ideas/" + keyID} key= {keyID}> {title}</Link> </div>
<p className="text-gray-700 text-base">{description}</p>
</div>
</div>
);
};
const IdeaDisplay:FC<any> = (props:any):ReactElement => {
const { ideas, setIdeas } = useContext(IdeaContext)
console.log('Ideas in display: ', ideas)
console.log('test') //This is what I comment and uncommend to get it to show
return (
<div className="flex flex-wrap ">
{ideas.map((idea) => {
console.log(idea)
console.log('Sending',idea.title,idea.description,idea.keyID)
console.log(typeof idea.keyID)
return (
<IdeaPost
title={idea.title}
description={idea.description}
keyID = {idea.keyID}
key = {idea.keyID * 100}
/>
);
})}
</div>
);
};
export default IdeaDisplay;
Solution Code:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import {ideasRef} from './firebase'
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext<IdeaContextType>(IdeaContextDefaultValues)
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] = useState<Array<Idea>>(
IdeaContextDefaultValues.ideas);
useEffect(() => {
console.log("getting info");
const setup = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
setup()
}, []);
useEffect( () => {
console.log('idea change: ', ideas)
const updateDatabase = async () => {
await ideasRef.update(ideas)
console.log('updated database')
}
updateDatabase()
}, [ideas])
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
export {IdeaContext, IdeaContextProvider}
First of all you would need to use once and not on if you want to get the data only once. If you want to use a realtime listener you could send the setIdeas to your function. Also try to be carefull with async/away calls to the Firebase sdk. Your code could look like this:
import React, { createContext, useEffect, useState } from "react";
import { IdeaContextType, Idea } from "../t";
import { ideasRef } from "./firebase";
async function getIdeas() {
var arr: Array<Idea> = [];
const snapshot = await ideasRef.once("value");
let items = snapshot.val();
snapshot.forEach((idea) => {
const obj = idea.val();
arr.push({
title: obj.title,
description: obj.description,
keyID: obj.keyID,
});
console.log(arr);
});
return arr;
}
const IdeaContextDefaultValues: IdeaContextType = {
ideas: [],
setIdeas: () => {},
};
const IdeaContext = createContext < IdeaContextType > IdeaContextDefaultValues;
const IdeaContextProvider: React.FC = ({ children }) => {
const [ideas, setIdeas] =
useState < Array < Idea >> IdeaContextDefaultValues.ideas;
useEffect(() => {
console.log("getting info");
const getData = async () => {
const ideas = await getIdeas();
setIdeas(ideas);
};
}, []);
useEffect(() => {
console.log("idea change: ", ideas);
}, [ideas]);
return (
<IdeaContext.Provider value={{ ideas, setIdeas }}>
{children}
</IdeaContext.Provider>
);
};
I noticed this strange behavior of my App, that when I do anything on it (write something in the search field, create a new list, etc) my page gets rerendererd. Of course, I cannot find the source of it.
Below is the the look of my page, when it is loaded the first time, with default (blank) search results.
And now, the result in profiler, when I type something in the searchBar (or create a new list, or anything):
Here is my code of the App.js
import React, { useState, createContext, useEffect } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = (singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
};
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = (newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
};
const onDeleteList = (id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = (renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
};
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = (favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
};
const deleteFavorited = (id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
};
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I will not post my whole app here, because it is a lot of files. I just give a link to my gitHub:
GitHub LINK
I was trying to find a solution, as stated here:
Link to SO page
which is like my case, but it didn't help (maybe because I was not using memo):
import React, { useState, createContext, useEffect, useCallback } from "react";
import NavBar from "../NavBar/NavBar";
import youtube from "../../apis/youtube";
import VideoList from "../VideoList/VideoList";
import VideoDetail from "../VideoDetail/VideoDetail";
import SideBar from "../SideBar/SideBar";
import "./App.css";
export const VideoContext = createContext();
export const FavoriteContext = createContext();
const API_KEY = process.env.REACT_APP_API_KEY;
const App = () => {
const [ videos, setVideos ] = useState([]);
const [ searchedValue, setSearchedValue ] = useState({
selectedVideo: null
});
const handleSelectedVideo = useCallback((singleRenderedVideo) => {
setSearchedValue((previous) => ({
...previous,
selectedVideo: singleRenderedVideo
}));
}, []);
const handleSearch = async (inputText) => {
const response = await youtube.get("/search", {
params: {
q: inputText,
part: "snippet",
type: "video",
maxResults: 16,
key: API_KEY
}
});
setVideos(response.data.items);
setSearchedValue({
selectedVideo: response.data.items[0] //take the first search result and make it appear as a playable one
});
};
useEffect(() => {
handleSearch();
}, []);
//By the user newly created lists
const [ lists, setLists ] = useState([]);
const addList = useCallback((newList) => {
setLists((prevLists) => {
return [ ...prevLists, newList ];
});
}, []);
const onDeleteList = useCallback((id) => {
setLists((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
//Render(Play) Favorited Video
const [ favoritedItem, setFavoritedItem ] = useState({
clickedFavoritedVideo: null
});
const handleSelectedFavorite = useCallback((renderFavorite) => {
setFavoritedItem((previous) => ({
...previous,
clickedFavoritedVideo: renderFavorite
}));
}, []);
//Add a newly favorited video to a, by user created, list (BUG: for now the favorited video is added to EVERY, by the user, created list)
const [ favoritedList, setFavoritedList ] = useState([]);
const handleFavoritedVideo = useCallback((favoritedElement, selectedList) => {
setFavoritedList((previousFavorited) => {
return [ { favoritedElement, selectedList }, ...previousFavorited ];
});
}, []);
const deleteFavorited = useCallback((id) => {
setFavoritedList((prevLists) => {
return prevLists.filter((listItem, index) => {
return index !== id;
});
});
}, []);
return (
<div className="container">
<NavBar handleSearch={handleSearch} />
<div className="content">
<SideBar
addList={addList}
lists={lists}
handleSelectedFavorite={handleSelectedFavorite}
favoritedList={favoritedList}
onDeleteList={onDeleteList}
onDeleteFavorited={deleteFavorited}
/>
<main className="video">
<VideoContext.Provider value={handleSelectedVideo}>
<FavoriteContext.Provider value={handleFavoritedVideo}>
<VideoDetail
selectedVideo={searchedValue.selectedVideo}
clickedFavoritedVideo={
favoritedItem.clickedFavoritedVideo
}
/>
<VideoList listOfVideos={videos} lists={lists} />
</FavoriteContext.Provider>
</VideoContext.Provider>
</main>
</div>
</div>
);
};
export default App;
I also tried to give a type for my buttons (type="button"), which currently have no type, like as in:
CreateNewList.js
import React, { useState } from "react";
import iconSprites from "../../images/sprite.svg";
import shortid from "shortid";
const CreateNewList = ({ onAdd }) => {
const [ list, setList ] = useState({
id: shortid.generate(),
title: ""
});
const handleChange = (event) => {
const { value } = event.target;
setList((prevList) => {
return {
...prevList,
title: value
};
});
event.preventDefault();
};
const submitNewList = (event) => {
onAdd({ ...list });
setList({ id: shortid.generate(), title: "" });
event.preventDefault();
};
return (
<React.Fragment>
<li className="new-list__item">
<form>
<div className="new-list__link">
<button
onClick={submitNewList}
className="new-list__btn-plus btn"
>
<svg className="new-list__icon">
<use href={iconSprites + "#icon-circle-with-plus"} />
</svg>
</button>
<input
className="new-list__input"
name="title"
value={list.title}
onChange={handleChange}
placeholder="New List"
/>
</div>
</form>
</li>
</React.Fragment>
);
};
export default CreateNewList;
but it also didn't help. Maybe because they are not in <form>?
So that is it. Maybe someone can help me with my issue?
To have all of the questions in one place:
Why my app keeps rerendering?
Should I use memo with useCallback?
Should I put my buttons in a <form> and give them a type?