I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.
Related
I had a child component UploadImage.js and parent component Parent.js. I am uploading an image and want to pass the value of file name to the Parent.js component. How can I do so?
UploadImage.js
import React, { useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
.
.
.
const UploadImage = () => {
const [files, setFiles] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
setFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}
});
//preview component
const thumbs = files.map(file => (
<div style={thumb} className="d-flex flex-row mt-1 col-12 mx-auto" key={file.name}>
<div style={thumbInner}>
<img
src={file.preview}
style={img}
// Revoke data uri after image is loaded
onLoad={() => { URL.revokeObjectURL(file.preview) }}
/>
</div>
</div>
)
);
//wanted to pass file[0].name to Parent Component
console.log(files.length > 0 ? files[0].name : "")
useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, []);
return (
<section className="container">
<div {...getRootProps({ className: 'dropzone mx-3 text-center mt-4 mb-2 p-3 bg-light border border-primary border-1 rounded-4 ' })}>
<input {...getInputProps()} />
<p className='fw-bold text-primary'>Drag 'n' drop some files here, or click to select files</p>
</div>
<aside style={thumbsContainer} className="d-flex flex-row">
{thumbs}
</aside>
</section>
);
}
export default UploadImage;
And my Parent component is like this
import React, { useState} from "react";
import UploadImage from "../components/uploadImage";
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage />
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
};
export default UploadButton;
How can I display the file name from UploadImage.js to Parent.js in the uploadFileName state ???
you create a function in your parent element like:
const NameSetter = imageName => {
setUploadFileName(imageName);
}
and then send the NameSetter as a prop to your child element like:
<UploadImage nameHandler={NameSetter} />
and then in your child element you call the nameHandler prop like:
(call this when you get the name, for ex: on the callback of your backend )
props.nameHandler('name of your image');
you can use call back props to update the children to parent.
import React, { useState} from "react";
import UploadImage from "../components/uploadImage";
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage setUploadFileName={setUploadFileName}/>
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
};
export default UploadButton;
Then you can set whereever you want to call in child it will update in parent component. You can check through by adding consoling on the parent component.
Hey MagnusEffect you're almost correct, just make these changes-
In UploadImage.js-
const UploadImage = ({setUploadFileName}) => {
<input {...getInputProps()} onChange=
{(e)=>setUploadFileName(e.target.files[0].name)} />
}
While in Parent Component just pass setvalues-
const Parent = () => {
const [uploadFileName, setUploadFileName] = useState("");
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage setUploadFileName={setUploadFileName} />
<h3 className="m-3">{uploadFileName} </h3>
</div>
);
}
Hope this code will help to solve your query if you still facing issue, just lemme know i will help you more. Thanks
You should move const [files, setFiles] = useState([]); to Parents.js and then pass them by Props for UploadImage.js.
// UploadImage Component
const UploadImage = (props) => {
const {files, onUpdateFiles} = props;
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
onUpdateFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}
});
...
}
// Parents component
const Parent = () => {
const [files, setFiles] = useState([]);
return (
<div className="mx-3 mt-4 mb-2">
<UploadImage files={files} onUpdateFiles={setFiles} />
{files.length > 0 && <h3 className="m-3">{files[0].name}</h3>}
</div>
);
};
i don't know how can i print user info react modal
this is my code
import React from "react";
import axios from "axios";
import { useEffect, useState } from "react";
import { Show, User } from "../api";
import UseUser from "../scroll/userInfo";
function ShowContents() {
const [storyIds, setStoryIds] = useState([]);
const [visible, setVisible] = useState(false);
const [getUser, setGetUser] = useState([]);
useEffect(() => {
Show().then((res) => {
this.res = res.data.slice(0, 10);
this.res.forEach(async (ele) => {
await axios
.get("https://hacker-news.firebaseio.com/v0/item/" + ele + ".json")
.then((res) => {
if (Array.isArray(this.res) && this.res.length === 0) {
return;
} else {
setStoryIds((value) => [
...value,
{
id: res.data.id,
title: res.data.title,
url: res.data.url,
user: res.data.by,
score: res.data.score
}
]);
}
});
});
});
}, []);
const menu = storyIds;
const menuList = menu.map((m, i) => (
<div className="box_show" key={i}>
<div className="flex">
<p className="numbers">{i + 1}</p>
<a href={m.url} className="titleFont">
{m.title}
</a>
<a href={m.url}>
<img src={`/assets/back.svg`} alt="" className="imgLink" />
</a>
</div>
<br />
<button
className="userShow"
onClick={() => {
setVisible(!visible);
}}
>
<div className="userNameShow">{m.user}</div>
</button>
</div>
));
return (
<>
{menuList}
{visible && (
<div className="modal-container" id="modal">
<div className="modal">
<div className="modal-top flex">
<p>User Info</p>
<button
className="close-btn"
onClick={() => {
setVisible(!visible);
}}
>
<img src={`/assets/close_black.svg`} alt="" />
</button>
</div>
<UseUser />
</div>
</div>
)}
</>
);
}
export default ShowContents;
The code above is my show partial code.
this is my UserInfo code
import { useState, useEffect } from "react";
import { getUser } from "../api";
const UseUser = (id) => {
// const [user, setUser] = useState({});
// useEffect(() => {
// getUser(id).then((user) => setUser(user));
// }, [id]);
return (
<>
<div className="user-detail flex">
<div className="user-profile"></div>
<div className="user-detail-info">
<p className="modal-user">user : </p>
<p className="modal-created">created : </p>
<p className="modal-karma">karma : </p>
</div>
</div>
<p className="about">about:</p>
<p className="email">Twitter:</p>);
</>
);
};
export default UseUser;
import axios from "axios";
const BASE_URL = "https://hacker-news.firebaseio.com/v0/";
export const storyUrl = `${BASE_URL}item/`;
function News() {
return axios.get(`${BASE_URL}newstories.json`);
}
function Jobs() {
return axios.get(`${BASE_URL}jobstories.json`);
}
function Top_API() {
return axios.get(`${BASE_URL}topstories.json`);
}
function Ask() {
return axios.get(`${BASE_URL}askstories.json`);
}
function Show() {
return axios.get(`${BASE_URL}showstories.json`);
}
function User() {
return axios.get(`${BASE_URL}user`);
}
export { News, Jobs, Top_API, Ask, Show, BASE_URL, User };
this is my api code
When you click the user button in the show part, I want to get information about the user. It's too difficult for me right now.
I tried to implement it using use Effect, but I don't know how to send information about the user when I press the button.
I need help.
I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
I would like to ask for some help with this, i don't know if its normal or not.
Have This components, one is a container that fetch the data and the second one receive the data and display it in a div. Nothing fancy.
const ProjectContainer = () => { // component
const projects = useSelector((state) => state.projectReducer.projects);
const count = useSelector((state) => state.projectReducer.count);
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
const [isLoading, setIsLoading] = useState(false);
const limit = 5;
const dispatch = useDispatch();
useEffect(() => {
console.log("INSIDE USEFFECT");
if (projects.length > 0) return; // avoid fetching data if the state has data already
async function getData() {
setIsLoading(true);
try {
const projectsCollectionRef = db.collection("project-collection");
const projectsCountRef = db
.collection("aggregation")
.doc("project-collection");
console.log("FETCHING DATA");
const responseCount = await projectsCountRef.get();
const count = await responseCount.data().count;
//dispatch
dispatch({ type: "SET_PROJECTS_COUNT", payload: count });
const response = await projectsCollectionRef
.orderBy("createdAt")
.limit(limit)
.get();
let dataSend = [];
response.forEach((document) => {
dataSend.push({ ...document.data(), uid: document.id });
});
//dispatch
dispatch({ type: "SET_PROJECTS", payload: dataSend });
setIsLoading(false);
} catch (error) {
console.error(error);
}
}
getData();
}, [dispatch, projects.length]);
return (
<div className="container mx-auto text-center">
<div>
Proyectos
</div>
{isLoading && projects.length === 0 ? (
<div >
<div id="title">
<p>
Cargando....
</p>
</div>
</div>
) : (
<>
{projects.length === 0 ? (
<div >
<div id="title" >
<p>
No hay proyectos que mostrar....
</p>
</div>
</div>
) : (
<>
<div >
{projects.map((project, index) => {
return (
<Project data={project} index={index} key={project.uid} />
);
})}
</div>
{count !== projects.length && (
<button>
Cargar más
</button>
)}
</>
)}
</>
)}
</div>
);
};
export default ProjectContainer;
The component that shows the data is something like this
import React from "react";
import { useSelector } from "react-redux";
const Project = (props) => {
const { data, index } = props;
console.log({ index });
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
return (
<div>
<div id="image">
<div>
<img
src={data.imageURL}
alt=""
/>
</div>
</div>
<div id="textblock">
<h1 >
{data.name}
</h1>
<div id="description">
<span >{data.description}</span>
<div >
<p>
Tecnologías
</p>
{data.technologies.map((technology) => {
return (
<span key={technology}>
{technology}
</span>
);
})}
</div>
<div >
<div >
<span>
Api Utilizada:
</span>
</div>
<div >
<span>
{data.usedAPI}
</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default Project;
I mean, it works, it does its job, but I don't know if it's correct, in a more realistic company work it should work like this ?
I read that Strict mode can force to do some re renders, but i checked and don't have it.
At the end console looks like this ..
thanks in advance everyone :)
React will re-render once for each dispatch, even if multiple dispatch functions are called in the same cycle. This article has some good info on it:
https://medium.com/unsplash/react-redux-performance-considerations-when-dispatching-multiple-actions-5162047bf8a6
Fortunately, there is a solution: the batch function provided by Redux:
https://react-redux.js.org/api/batch
Just call both of your dispatch calls from within a batch, and you should see it only re-render once.
I am using React JS and I have a text field which is supposed to change its content as the user clicks on different UI components. I also want to be able to edit the text in that text field (and later I would like to send that text to the UI component, but that is another story). So far I got this code
import React, { useContext } from 'react'
import './ContextualMenu.css'
import { EditorContext } from '../../EditorBase'
const ContextualMenu = props => {
const editorContext = useContext(EditorContext)
const handleUpdates = (event) => {
console.log(event.target.value)
}
const displayNodeAttr = () => {
return (
<>
<div className="menu-title">{editorContext.selectedNode.nodeType}</div>
<div>
<div className="menu-item">
<div className="menu-item-label">Name</div>
<div className="menu-item-value">
<input
className="menu-item-input"
type="text"
value={editorContext.selectedNode.nodeAttr.name}
onChange={handleUpdates}
/>
</div>
</div>
</div>
</>
)
}
return (
<div id="c-contextual-menu">
{editorContext.selectedNode.nodeAttr && displayNodeAttr()}
</div>
)
}
export default ContextualMenu
This makes the text always return to the original text that was set when the user clicked on the component. If i replace line 21 (value={editorContext.selectedNode.nodeAttr.name}) with placeholder={editorContext.selectedNode.nodeAttr.name} then the hint always shows the correct text as the user click on UI components but it shows it as a hint and i would like to have it as text.
It seems to me that the input text field detects a change (has a listener on the change event or something like that) and it immediately reverts the text to the original text, which makes it basically uneditable. Any ideas?
Update:
After the answers by #alireza and #Juviro I changed the code due to the fact that initially the selected node is null and as the user selects the node then it becomes not null. So the code now looks like this (it is just the relevant part):
const ContextualMenu = props => {
const editorContext = useContext(EditorContext)
const val = editorContext.selectedNode && editorContext.selectedNode.nodeAttr ? editorContext.selectedNode.nodeAttr.name : ''
const [value, setValue] = useState(val)
const handleUpdates = (event) => {
setValue(event.target.value)
console.log(event.target.value)
}
const displayNodeAttr = () => {
return (
<>
<div className="menu-title">{editorContext.selectedNode.nodeType}</div>
<div>
<div className="menu-item">
<div className="menu-item-label">Name</div>
<div className="menu-item-value">
<input
className="menu-item-input"
type="text"
value={value}
onChange={handleUpdates}
/>
</div>
</div>
</div>
</>
)
}
return (
<div id="c-contextual-menu">
{editorContext.selectedNode.nodeAttr && displayNodeAttr()}
</div>
)
}
The problem now is that the input field is never set to any value when the user clicks on the UI components (nodes). It is as if the value is set on page load and then never updated as the user selects components (nodes). If now I use val instead of value like this: value={val} then the input field is updated correctly but then i get back to the old problem of not being able to edit its content.
You can use the effect hook to call the setValue function when the value of val changes
import React, { useEffect, useState, useContext } from 'react'
const ContextualMenu = props => {
const editorContext = useContext(EditorContext)
const val = editorContext.selectedNode && editorContext.selectedNode.nodeAttr ? editorContext.selectedNode.nodeAttr.name : ''
const [value, setValue] = useState(val)
useEffect(() => {
setValue(val)
}, [val])
const handleUpdates = (event) => {
setValue(event.target.value)
console.log(event.target.value)
}
const displayNodeAttr = () => {
return (
<>
<div className="menu-title">{editorContext.selectedNode.nodeType}</div>
<div>
<div className="menu-item">
<div className="menu-item-label">Name</div>
<div className="menu-item-value">
<input
className="menu-item-input"
type="text"
value={value}
onChange={handleUpdates}
/>
</div>
</div>
</div>
</>
)
}
return (
<div id="c-contextual-menu">
{editorContext.selectedNode.nodeAttr && displayNodeAttr()}
</div>
)
}
The solution from #alireza looks good, just replace
useState(event.target.value)
with
setValue(event.target.value)
This component always shows the same value as editorContext.selectedNode.nodeAttr.name
you should use state in the component to handle it's value.
import React, { useContext, useState } from 'react'
import './ContextualMenu.css'
import { EditorContext } from '../../EditorBase'
const ContextualMenu = props => {
const editorContext = useContext(EditorContext)
const [value, setValue] = useState(editorContext.selectedNode.nodeAttr.name)
const handleUpdates = (event) => {
setValue(event.target.value)
console.log(event.target.value)
}
const displayNodeAttr = () => {
return (
<>
<div className="menu-title">{editorContext.selectedNode.nodeType}</div>
<div>
<div className="menu-item">
<div className="menu-item-label">Name</div>
<div className="menu-item-value">
<input
className="menu-item-input"
type="text"
value={value}
onChange={handleUpdates}
/>
</div>
</div>
</div>
</>
)
}
return (
<div id="c-contextual-menu">
{editorContext.selectedNode.nodeAttr && displayNodeAttr()}
</div>
)
}
I have not tested but it should work.
update
To update the value based on the values coming from props, you should use useEffect.
import React, { useContext, useEffect, useState } from 'react'
import './ContextualMenu.css'
import { EditorContext } from '../../EditorBase'
const ContextualMenu = props => {
const editorContext = useContext(EditorContext)
const val = editorContext.selectedNode && editorContext.selectedNode.nodeAttr.name ? editorContext.selectedNode.nodeAttr.name : ''
const [value, setValue] = useState(val)
const handleUpdates = (event) => {
setValue(event.target.value)
console.log(event.target.value)
}
useEffect(()=>{
if( val !== value) // Prevent redundant updates
setValue(val)
})
const displayNodeAttr = () => {
return (
<>
<div className="menu-title">{editorContext.selectedNode.nodeType}</div>
<div>
<div className="menu-item">
<div className="menu-item-label">Name</div>
<div className="menu-item-value">
<input
className="menu-item-input"
type="text"
value={value}
onChange={handleUpdates}
/>
</div>
</div>
</div>
</>
)
}
return (
<div id="c-contextual-menu">
{editorContext.selectedNode.nodeAttr && displayNodeAttr()}
</div>
)