working on a project for a undisclosed in which data on patients gets pulled from their api and gets loaded as modal on the page . When you click on a modal more info of the threat gets pulled up as a modal. The goal here is for them to render when someone clicks for it based on div.
how do I proprly send the data from api to modal component on each click ?
div table-alike on click
import React, {useState,useEffect} from 'react';
const keys = Object.keys(arr[0]);
// const handleOnClick = param => {
// console.log('do something: ', param);
// }
export default function Demo() {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const toggleModal = () => {
setModalIsOpen(!isModalOpen);
};
const handleOnClick = () => {
toggleModal()
};
useEffect(() => {
const fetchUsers = async () => {
try {
const { data } = await axios.get('https://gist.githubusercontent.com/SkyBulk/a75a32254d58aea2cf27cbb43117a2f4/raw/eb5f85560c0dfd74a4aab9db755ac5a06f0627c2/api.json').results;
setUsers(data);
} catch (err) {
console.error("failed", err);
}
setModalIsOpen(false);
};
fetchUsers();
}, []);
return (
<div className="container">
<>
{keys.map((key) => (
<div className="col" key={key}>
<div className="row">{key}</div>
{arr[0][key].map((item) => (
<div className="row" key={item.technique_id} onClick={() => handleOnClick(item)}>{item.technique}</div>
))}
</div>
))}
</>
{isModalOpen && <Modal onRequestClose={handleOnClick} data={users}/>}
</div>
);
}
modal
import React, { useEffect } from "react";
const Modal = ({ onRequestClose, data }) => {
// Use useEffect to add an event listener to the document
useEffect(() => {
function onKeyDown(event) {
if (event.keyCode === 27) {
// Close the modal when the Escape key is pressed
onRequestClose();
}
}
// Prevent scolling
document.body.style.overflow = "hidden";
document.addEventListener("keydown", onKeyDown);
// Clear things up when unmounting this component
return () => {
document.body.style.overflow = "visible";
document.removeEventListener("keydown", onKeyDown);
};
});
return (
<div className="modal__backdrop">
<div className="modal__container">
<div className="modal-header">
<div className="modal-close" onClick={onRequestClose}>
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</div>
<div className="job-title-wrapper">
<div className="job-card-title">{data}</div>
</div>
</div>
</div>
);
};
export default Modal;
If I understand this correctly, you want to make a particular API call and set it's data into the modal when the user clicks on a row, correct?
What you are currently doing through your useEffect is simply always fetching the user data every time something in your component updates, which is probably not what you want based off of your description. So you can probably remove that block completely.
Instead, you want that API called to be made whenever you click on the row, so it would seem that the more appropriate place to do so would be on your handleOnClick function. It should look something like this:
const handleOnClick = async () => {
try {
const { data } = await axios.get('https://gist.githubusercontent.com/SkyBulk/a75a32254d58aea2cf27cbb43117a2f4/raw/eb5f85560c0dfd74a4aab9db755ac5a06f0627c2/api.json').results;
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
Like this, the data should be all set in your user state by the time the modal is opened. With these changes, your Demo component should look something like this:
export default function Demo () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const handleOnClick = async () => {
try {
const { data } = await axios.get('https://gist.githubusercontent.com/SkyBulk/a75a32254d58aea2cf27cbb43117a2f4/raw/eb5f85560c0dfd74a4aab9db755ac5a06f0627c2/api.json').results;
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
return (
<div className="container">
<>
{keys.map((key) => (
<div className="col" key={key}>
<div className="row">{key}</div>
{arr[0][key].map((item) => (
<div className="row" key={item.technique_id} onClick={() => handleOnClick(item)}>{item.technique}</div>
))}
</div>
))}
</>
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/>}
</div>
);
}
Related
I have a react-beautiful-dnd app where the user can drag cards from one column to another. When I drag a card to a column with existing cards, the existing cards jump down. This makes sense if I'm trying to drag the card to the top of the column. However, the first card slides down even if I drag the new card to the middle or the list. And if I drag the new card to the bottom of the list everything above it slides down. Once I release the mouse button and the dragging is complete, the cards snap back to where they should be. Therefore, it's something to do with isDraggingOver. I checked my styled components, but the only styles that change on isDraggingOver are background colors and border colors. Has anyone else encountered this? If so, how did yo fix it. Thanks in advance!
Update 9/6:
After unsuccessfully trying #Jordan solution I looked at the GitHub issues for the project and found this. Sure enough, if I do not render provided.placeholder the problem goes away. Of course, now I get a warning in the console about not rendering that placeholder. I then tried rendering it again but wrapping it in a call to React.cloneElement() and passing {style: {height: '0'}} as props. This does not work. So for now, I am simply not rendering the placeholder. Does anyone know another way around this? Here's a screenshot of the latest diff:
Update 8/25
Trying the solution of #jordan but I"m still seeing the issue. Here is my updated component file for the draggable component called Task
Draggable file:
import React, { memo, useState } from 'react'
import { Draggable } from 'react-beautiful-dnd'
import TaskForm from '../TaskForm/TaskForm'
import Task from '../Task/Task'
import { useStyletron } from 'baseui'
import useDraggableInPortal from '../../Hooks/useDraggableInPortal'
const TaskCard = ({ description, id, index, column }) => {
const renderDraggable = useDraggableInPortal()
const [isEditing, setIsEditing] = useState(false)
const [css, theme] = useStyletron()
const handleEdit = id => {
setIsEditing(true)
}
return (
<Draggable draggableId={id} index={index}>
{renderDraggable((provided, snapshot) => (
<div
className={css({
boxSizing: 'border-box',
':focus': {
outline: `${theme.colors.hotPink} 3px solid`,
borderRadius: '6px',
margin: '3px',
}
})}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
{!isEditing &&
<Task
column={column}
description={description}
handleEdit={handleEdit}
id={id}
snapshot={snapshot}
/>
}
{isEditing &&
<TaskForm
column={column}
handleOnCancel={() => setIsEditing(false)}
initialValues={{ description, id }} isEditing={true}
/>
}
</div>
))}
</Draggable>
)
}
export default memo(TaskCard)
Hook from #jordan :
import { createPortal } from 'react-dom'
import { useEffect, useRef } from 'react'
const useDraggableInPortal = () => {
const self = useRef({}).current
useEffect(() => {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.pointerEvents = 'none'
div.style.top = '0'
div.style.width = '100%'
div.style.height = '100%'
self.elt = div
document.body.appendChild(div)
return () => {
document.body.removeChild(div)
}
}, [self])
return (render) => (provided, ...args) => {
const element = render(provided, ...args)
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt)
}
return element
}
}
export default useDraggableInPortal
I had this issue, and I found a solution to it here.
Basically when the library is using position: fixed, there are are some unintended consequences - and in those cases you need to use portal to get around them:
I had the same issue. Following the great solution of #kasperpihl, I created a simple hook do do the trick:
const useDraggableInPortal = () => {
const self = useRef({}).current;
useEffect(() => {
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.pointerEvents = 'none';
div.style.top = '0';
div.style.width = '100%';
div.style.height = '100%';
self.elt = div;
document.body.appendChild(div);
return () => {
document.body.removeChild(div);
};
}, [self]);
return (render) => (provided, ...args) => {
const element = render(provided, ...args);
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt);
}
return element;
};
};
Usage: Considering the following component:
const MyComponent = (props) => {
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
)}
</Draggable>
)}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Just call the hook and use the returned function to wrap the children callback of component:
const MyComponent = (props) => {
const renderDraggable = useDraggableInPortal();
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{renderDraggable((provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
))}
</Draggable>
))}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Another potential fix is to add a css rule to override whatever behaviour you've unintentionally set for your draggable:
HTML:
<div className="draggableClassName"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div>
<input
//...etc.
index.css
.draggableClassName {
left: auto !important;
top: auto !important;
}
Okay, I finally figured this out. I feel rather silly. I had originally rendered the Droppable part of my React component tree like so:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}>
{provided.placeholder} // incorrect
</TaskList>
)}
</Droppable>
This makes no sense as my TaskList component does not take children as a prop. I think I was trying to imitate some sample code that I had seen and I got confused.
The correct way to mark it up for my specific context is:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<>
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}
/>
{provided.placeholder} // correct
</>
)}
</Droppable>
The Eureka moment was when I read the warning in the console which said that the provided.placeholder must be a child of the Droppable.
Note
I have to put the provided.placeholder after my TaskList. If it is the first child of the fragment I still have the original problem (the entire list slides down when you drag a new item onto it)
I tried to implement a simple UI for smart contracts using polkadot.js in the next.js framework.
The content of the WEB UI is a simple one that calls the Flipper contract, which is famous for the sample contract of the substrate.
When compiling, the following error is output. Can you tell me how to solve it?
Souce Code:
import { useEffect, useState } from "react";
import {
web3Accounts,
web3Enable,
web3FromSource,
} from "#polkadot/extension-dapp";
import { InjectedAccountWithMeta } from "#polkadot/extension-inject/types";
const Home = () => {
const [allAccount, setAllAccount] = useState<InjectedAccountWithMeta[]>([]);
const getAccounts = async () => {
const extensions = await web3Enable("my cool dapp");
if (extensions.length === 0) {
return;
}
const allAccounts = await web3Accounts();
setAllAccount(allAccounts);
};
useEffect(() => {
getAccounts();
}, []);
return (
<>
<div>
{typeof allAccount !== "undefined"
? allAccount.map((account) => {
return (
<div key={account.address}>
<div className="font-bold mb-2 text-white">
{account.address}
</div>
</div>
);
})
: ""}{" "}
</div>
</>
);
};
export default Home;
Error Information:
> Build error occurred
ReferenceError: window is not defined
at file:///Users/shin.takahashi/develop/substrate/flipper_frontend/fillper_frontend/node_modules/#polkadot/extension-dapp/bundle.js:10:13
at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
at async Loader.import (node:internal/modules/esm/loader:178:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15) {
type: 'ReferenceError'
}
This issue occurs because next.js is a framework for server-side rendering.
In order to avoid this problem, it is necessary to control not to execute
server-side rendering for the relevant part.
Componentize the part that gets account information from the relevant Extension.
Adopt dynamic import when using this component and set server-side rendering to off.
component sample code:
import { useEffect, useState } from "react";
import { web3Accounts, web3Enable } from "#polkadot/extension-dapp";
import { InjectedAccountWithMeta } from "#polkadot/extension-inject/types";
const Extention = () => {
const [allAccount, setAllAccount] = useState<InjectedAccountWithMeta[]>([]);
const getAccounts = async () => {
const extensions = await web3Enable("my cool dapp");
if (extensions.length === 0) {
return;
}
const allAccounts = await web3Accounts();
setAllAccount(allAccounts);
};
useEffect(() => {
getAccounts();
}, []);
return (
<>
<div>
{typeof allAccount !== "undefined"
? allAccount.map((account) => {
return (
<div key={account.address}>
<div className="font-bold mb-2 text-white">
{account.address}
</div>
</div>
);
})
: ""}{" "}
</div>
</>
);
};
export default Extention;
Calling component sample code:
import dynamic from "next/dynamic";
import { useState } from "react";
const Extention = dynamic(() => import("../component/extention"), {
ssr: false,
});
const Home = () => {
const [showExtention, setShowExtention] = useState(false);
return (
<>
<button onClick={() => setShowExtention(true)}>show extention</button>
{showExtention == true && <Extention></Extention>}
</>
);
};
export default Home;
I'm currently working on a site that pulls Reddit data from different subreddits to show recipes, you can toggle between 3 different ones. That part works fine! I'm able to pull the list of posts from each subreddit, but now I'm trying to make it so when you click a button, it routes you to post details where it'll show the post and comments. I have to do another API call to get that information.
Somewhere along the way, it's getting messed up and it's showing "xhr.js:210 GET https://www.reddit.com/r/recipes/comments/[object%20Object].json 404:" It's a dynamic route, so there are two different parameters I'm trying to use. I did try and console log the parameter for ID(the one showing up as [object object] and it shows up fine by itself and is not an object from what I can tell.
Please see some of the code below where I think things could be going wrong. I'm guessing it's the API call because when I console log it, it only shows the subreddit as an arg but not sure..
redditAPI.js:
import axios from 'axios';
export default axios.create({
baseURL:"https://www.reddit.com/r/"
})
store.js:
import {configureStore} from '#reduxjs/toolkit';
import postReducer from './posts/postSlice';
export const store = configureStore({
reducer: {
posts: postReducer
}
});
postSlice.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import redditApi from '../../common/api/redditApi';
import { redditDetails } from '../../common/api/redditApi';
export const fetchAsyncPosts = createAsyncThunk('posts/fetchAsyncPosts', async (subreddit) => {
const response = await redditApi.get(subreddit)
return response.data.data.children;
});
export const fetchAsyncPostsDetail = createAsyncThunk('posts/fetchAsyncPostsDetail', async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
});
const initialState = {
posts: [],
selectedSubreddit: 'recipes.json',
selectedPost: []
}
const postSlice = createSlice({
name: "posts",
initialState,
reducers: {
addPosts: (state, { payload }) => {
state.posts = payload;
},
addSelectedPost: (state, {payload}) => {
state.selectedPost = payload;
},
setSelectedSubreddit(state, action) {
state.selectedSubreddit = action.payload;
}
},
extraReducers: {
[fetchAsyncPosts.pending] : () => {
console.log("Pending");
},
[fetchAsyncPosts.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, posts: payload};
},
[fetchAsyncPosts.rejected]: () => {
console.log("Rejected");
},
[fetchAsyncPostsDetail.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, selectedPost: payload};
},
},
});
export const {addPosts, setSelectedSubreddit} = postSlice.actions;
export const selectSelectedSubreddit = (state) => state.selectedSubreddit;
export const getAllPosts = (state) => state.posts.posts;
export const getSelectedPost = (state) => state.posts.selectedPost;
export default postSlice.reducer;
App.js
import "./App.scss";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import PageNotFound from "./components/PageNotFound/PageNotFound";
import Header from "./components/Header/Header";
import Home from "./components/Home/Home";
import PostDetails from "./components/PostDetails/PostDetails";
import Footer from "./components/Footer/Footer";
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<div className="App">
<Router>
<Header/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/:sub/comments/:postID" element={<PostDetails />} />
<Route path="*" element={ <PageNotFound />} />
</Routes>
</Router>
</div>
);
}
export default App;
Home.js:
import React, {useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import PostListing from '../PostListing/PostListing'
import { fetchAsyncPosts } from '../../features-redux/posts/postSlice';
function Home() {
const dispatch = useDispatch();
const subreddit = useSelector((state) => state.posts.selectedSubreddit);
useEffect(() => {
dispatch(fetchAsyncPosts(subreddit));
}, [dispatch, subreddit]);
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container text-center">
<h1 className="display-4">Welcome to Tasteful Reddit</h1>
<p className="lead">Toggle between subreddits above to view their recipes.</p>
</div>
</div>
<PostListing />
</div>
)
}
export default Home
postListing.js:
import React from 'react'
import { useSelector } from 'react-redux'
import { getAllPosts } from '../../features-redux/posts/postSlice'
import PostCard from '../PostCard/PostCard';
function PostListing() {
const posts = useSelector(getAllPosts);
const rendering = () => posts.map((post, key) => {
return <PostCard key={key} data={post.data} />;
});
console.log(posts);
return (
<div className="post-wrapper">
<div className="post-container">
{rendering()}
</div>
</div>
)
}
export default PostListing
postCard.js:
import React from 'react'
import parse from 'html-react-parser';
import {Link} from 'react-router-dom';
function PostCard(props) {
const {data} = props;
/* Function to change escaped HTML to string */
const htmlDecode = (input) => {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
/* Decode reddit JSON's youtube embed HTML */
const youtubeHtmlString = htmlDecode(data.media_embed.content);
/* This function runs through the reddit data to make sure that there is a
an image for the post. If so, shows image
and if its a reddit hosted video or youtube video it will render the video.
Gallery-style posts & all else shows empty div*/
const mediaRender = () => {
if (data.thumbnail !== 'self' && data.thumbnail !== 'default' && data.is_self !== true && data.is_gallery !== true && data.domain !== 'youtu.be' && data.domain !== 'v.redd.it') {
return <img src = {data.url} alt={data.title} className="card-img-top"/>;
} if ( data.is_video == true) {
return (
<div>
<video controls preload = "none">
<source src={data.media.reddit_video.fallback_url} type="video/mp4"/>
Your browser does not support the video tag.
</video>
</div>
)
} if (data.domain == 'youtu.be') {
return (
<div className="Container">
{parse(youtubeHtmlString)}
</div>
)
} else {
return <div></div>
}
}
/* If only text & no photos, render text info*/
const renderSelf = () => {
if(data.is_self == true) {
return (<p>{data.selftext}</p>)
} else {
return <p></p>
}
}
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/${data.subreddit}/comments/${data.id}`}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
export default PostCard
postDetails.js:
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import { fetchAsyncPostsDetail, getSelectedPost } from '../../features-redux/posts/postSlice';
function PostDetails() {
let {sub, postID} = useParams();
const dispatch = useDispatch();
const data = useSelector(getSelectedPost);
console.log(postID)
useEffect(() => {
dispatch(fetchAsyncPostsDetail(sub, postID));
console.log(dispatch(fetchAsyncPostsDetail(sub, postID)))
}, [dispatch, postID, sub]);
console.log(data);
return (
<div>
PostDetails
</div>
)
}
export default PostDetails
Any help would be appreciated, because I'm lost!
Thanks!
I figured it out today, for those interested!
I finally got it! I figured I was doing something wrong with react router, and that's why I couldn't get it to work but I didn't want to stop using react router. I tried many different ways to fix it, including putting both arguments in an object, I tried changing it by passing what I needed with a pathname AND a state, but the state didn't work right. think that I was passing the state in an incorrect way somehow. I think I wrote it like state{ permalink: data.permalink} or something. I also tried changing it so it only took one parameter, but for some reason the react router Link only wanted to use the post IDs and would go to my page not found if I tried to use permalink. After researching other ways to pass data using React Router, I landed on using From to grab the data from the previous page the user was on and used useLocation to grab the state.
Here is a snippet of what I changed in Link for in PostCard.js. The data was passed in props from postListing.js, just for reference:
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/post/${data.id}`}
state={{ from: data}}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
I also stopped using the postId to try and make the API call, and instead only used it to make the dynamic link to the component. After that, I used From's state to grab the permalink(from previous reddit post data) to make the API call to get the comment information.
Here is a snippet of PostDetails.js where I made the changes:
function PostDetails() {
const location = useLocation();
const {from} = location.state;
const comments = useSelector(getSelectedPost);
const dispatch = useDispatch();
useEffect( () => {
dispatch(getPostComments(from.permalink));
}, [dispatch]);
I also found out I was trying to access the data incorrectly in my API call, as the data was structured differently than I originally thought.
Here is the changed thunk in PostSlice.js:
export const getPostComments = createAsyncThunk('posts/getPostComments', async (permalink) => {
const response = await fetch(`http://www.reddit.com${permalink}.json`);
const json = await response.json();
return json[1].data.children.map((comment) => comment.data)
});
There were multiple things wrong with my react app, but hopefully this helps someone else! :)
The second argument to createAsyncThunk is the payloadCreator.
It's called with two arguments, arg which is a single value, and the thunkAPI object.
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
You are passing the thunkAPI argument as the postID value in the GET request.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
If you need to pass multiple values to your thunk then pack them into a single object.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async ({ sub, postID }, thunkAPI) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
...
useEffect(() => {
dispatch(fetchAsyncPostsDetail({ sub, postID }));
}, [dispatch, postID, sub]);
I've created a popup banner with the following code (sorry if the format is off)
//this is in folder Banner.tsx
import React, {useCallback} from "react";
type Properties = {
close: () => void;
text: string;
const Banner: React.FC<Properties> = ({close, text}) => {
const onClick = useCallback(() => {
close();},
[close, text]);
return (
<div className = "BannerBox">
<div className = "banner">
<span className = "popup"> {onClick}{close}[x]
</span>
{text}
</div>
</div>
);
};
export default Banner;
//this is App.tsx
import Banner from "./Components/Banner";
function App(): JSX.Element {
const [isOpen, setIsOpen]=useState(false);
const toggleBanner = () => {
SetIsOpen(!isOpen);
};
return (
<div>
<input type = "button"
value = "popup"
onClick={toggleBanner}/>
<p>hi</p>
{isOpen && <Banner text = {"hello"} close={function (): void { throw new Error("Function not implemented.");
} }/>}
</div>
export default App;
//this is my Banner.css file (let me know if you need the full code but this is some of it)
.BannerBox{
position: fixed;
background: blue;
width:50%;
}
.banner{
position: relative;
width: 100%;
}
.popup{
content: 'x';
position: fixed;
background: green;
}
the code compiles just fine, I'm not getting any errors but the problem is that when the banner pop-ups, I can't close it by clicking 'X' i have to refresh the page in order to close the banner and I'm not sure how to fix that. Any help is appreciated!
The close function needs to be passed to the onClick callback for the span which is acting as the button. These are added as "attributes" for the jsx element. See below onClick={onClick} where your onClick callback function is passed by reference (notice no invoking parentheses within the curly braces)
In the return of Banner.tsx
<span className="popup" onClick={onClick}>
[x]
</span>
close is passed into your Banner component, so this needs to be implemented in your App component. I ensured it always closes by explicitly setting isOpen to false (instead of calling toggle)
in the return of App.tsx
{isOpen && <Banner text={"hello"} close={() => setIsOpen(false)} />}
so in total
Banner.tsx
import React, { useCallback } from "react";
import "./Banner.css";
type Properties = {
close: () => void;
text: string;
};
const Banner: React.FC<Properties> = ({ close, text }) => {
const onClick = useCallback(() => {
close();
}, [close]);
return (
<div className="BannerBox">
<div className="banner">
<span className="popup" onClick={onClick}>
[x]
</span>
{text}
</div>
</div>
);
};
export default Banner;
and App.tsx
import React, { useState } from "react";
import Banner from "./Components/Banner";
function App(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const toggleBanner = () => {
setIsOpen(!isOpen);
};
return (
<div>
<input type="button" value="popup" onClick={toggleBanner} />
<p>hi</p>
{isOpen && <Banner text={"hello"} close={() => setIsOpen(false)} />}
</div>
);
}
export default App;
See codesandbox here
Let's assume that close() will actually close the popup banner since you did't show the implementation of it.
This line causes the issue
<span className = "popup">{onClick}{close}[x]</span>
You are supposed to pass a function to the onClick listener. Something like:
<span className = "popup" onClick={close}>[x]</span>
I can see the issue here. Stuff is going to fetch before it's still there in the order of code execution, but I don't know how to fix that at all.
I want to grab some infos from my firebase firestore database and put it in the dom in a way I can show it of nicely.
The other thing is, css file isn't working cause of the same issue. When I setAttribute to it it's already initialized and of course, it wouldn't show up at all (for example: border: 50% to make the pic round).
What do I miss to learn?
import React from "react";
import firebase from "./firebase";
import "./artists-styles.css";
const db = firebase.firestore();
const form = document.querySelector("#add-artist-avatar");
function renderArtists(doc) {
const artistsAvatar = document.querySelector(".content");
let image = document.createElement("img");
image.setAttribute("src", doc.data().avatar);
artistsAvatar.appendChild(image);
}
// getting data
db.collection("artists")
.get()
.then(snapshot => {
console.log(snapshot.docs);
snapshot.docs.forEach(doc => {
console.log(doc.data());
renderArtists(doc);
});
});
// saving data
form.addEventListener("submit", e => {
e.preventDefault();
db.collection("artists").add({
avatar: form.avatar.value,
});
form.name.value = "";
});
const Artists = () => {
return (
<div>
<h1>Cloud Cafe</h1>
<form id="add-artist-avatar">
<input type="text" name="avatar" placeholder="Artists Avatar Link" />
<button>Add Avatar</button>
</form>
<div className="content"></div>
</div>
);
};
export default Artists;
Perhaps you need something like this
import React, { useEffect, useState } from "react";
import firebase from "./firebase";
import "./artists-styles.css";
const db = firebase.firestore();
const Artists = () => {
const [docs, setDocs] = useState([]);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
db.collection("artists")
.get()
.then(snapshot => {
console.log(snapshot.docs);
setDocs(snapshot.docs);
});
}, []);
const handleSubmit = e => {
e.preventDefault();
db.collection("artists").add({
avatar: inputValue
});
};
return (
<div>
<h1>Cloud Cafe</h1>
<form id="add-artist-avatar" onSubmit={(e) => handleSubmit(e)}>
<input
type="text"
name="avatar"
placeholder="Artists Avatar Link"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
/>
<button>Add Avatar</button>
</form>
<div className="content">
{docs.map(doc => (
<img src={doc.data().avatar}/>
))}
</div>
</div>
);
};
export default Artists;