I'm experimenting on a custom modal using React.createPortal, everything's working if I am only opening and closing the modal via buttons inside the modal and the default result is displayed on the component below but what I want to achieve is when I click the modal container (dark overlay div) it should close the modal, so I added an exithandler on it, but the problem is the default form button behavior inside the modal content doesn't work anymore even if there's an event.stopPropagation() function in the exithandler.
Here's the codesandbox sample of two modals, one with a form and one without: https://codesandbox.io/s/react-modals-2dnei
Modal container:
import React from "react";
import { createPortal } from "react-dom";
const modalRoot = document.getElementById("modal-root");
function Modal({ exitHandler, children }) {
return createPortal(
<div className="modal" onClick={exitHandler}>
{children}
</div>,
modalRoot
);
}
export default Modal;
Modal content 1:
import React from "react";
const HelloWorld = ({ user, onClose }) => {
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Greetings</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<p>{user ? `Hello ${user}!` : "Greetings!"}</p>
</div>
</div>
);
};
export default HelloWorld;
Modal Content 2:
import React, { forwardRef, useEffect } from "react";
const SignUp = forwardRef((props, ref) => {
const { onClose, onSignUp, handleChange, inputError, user } = props;
useEffect(() => {
ref.current.focus();
});
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Sign Up</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<form onSubmit={onSignUp} className="modal-form">
<label className="input-label">
<span>Name:</span>
<input
type="text"
onChange={handleChange}
value={user}
ref={ref}
className={inputError === true ? "input-error" : null}
/>
</label>
<input className="btn" type="submit" value="Submit" />
</form>
</div>
</div>
);
});
export default SignUp;
Main:
const App = () => {
const [modalState, setModalState] = useState(false);
const [modalId, setModalId] = useState(null);
const [inputError, setInputError] = useState(false);
const [user, setUser] = useState("");
const [userStatus, setUserStatus] = useState("");
const nameRef = useRef(null);
const handleShowModal = (e) => {
const modalId = e.target.id.toString();
return [setModalState(true), setModalId(modalId)];
};
const handleHideModal = (event) => {
event.stopPropagation(); // This doesn't work
setModalState(false);
};
const runSignUpResult = useCallback(() => {
handleHideModal();
setUserStatus(`Thank you ${user} for signing up!`);
}, [user]);
const handleValidation = useCallback(
(nameParameter) => {
if (nameParameter.length === 0) {
nameRef.current.focus();
setInputError(true);
} else {
return [setInputError(false), runSignUpResult()];
}
},
[runSignUpResult]
);
const handleSignUp = useCallback(
(e) => {
e.preventDefault();
const name = nameRef.current.value;
handleValidation(name);
},
[nameRef, handleValidation]
);
const handleChange = (e) => {
setUser(e.target.value);
};
const modal = modalState ? (
<Modal exitHandler={handleHideModal}>
{modalId === "greeting" ? (
<HelloWorld onClose={handleHideModal} user={user} />
) : (
<SignUp
onClose={handleHideModal}
onSignUp={handleSignUp}
handleChange={handleChange}
user={user}
ref={nameRef}
inputError={inputError}
modalId={modalId}
/>
)}
</Modal>
) : null;
return (
<div className="App">
<AppHeader />
<main className="app-body">
<section className="modal-btns">
<button id="greeting" className="btn" onClick={handleShowModal}>
Greetings!
</button>
<button id="signup" className="btn" onClick={handleShowModal}>
Sign Up
</button>
</section>
<section>{modal}</section>
<section className="user-status">
<h3>{user.length === 0 ? null : userStatus}</h3>
</section>
</main>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Related
I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;
I created a Todo-list app in react
and inside each item placed one radio button and one edit, delete button for each and every item when a user enters in the input text.
Basically, the edit, delete buttons are disabled at first.
when a user clicks on the radio button then the particular item buttons get enabled.
But here I am getting all the buttons in each item gets enabled inside the list
import React,{useState} from 'react';
import Modal from 'react-modal';
import {AiFillDelete,AiFillEdit} from 'react-icons/ai';
import './App.css';
Modal.setAppElement('#root')
function App() {
const [todos,setTodos] = useState([{id:0,text:"item1"},{id:1,text:"item2"}])
const [todo,setTodo] = useState("")
const [todoEditing,setTodoEditing] = useState(null)
const [editingText,setEditingText] = useState("")
const [deleteItem,setDeleteItem] = useState()
const [editItem,setEditItem]=useState()
const [modalIsOpen,setModalIsOpen] = useState()
function handleSubmit(e){
e.preventDefault()
const newTodo = {
id:todos.length,
text : todo,
}
setTodos([newTodo].concat(...todos))
setTodo("")
}
function deleteTodo(id){
const updateTodos = [...todos].filter((todo)=>todo.id !== id)
setTodos(updateTodos)
}
function editTodo(id){
const updateTodos = [...todos].map((todo) => {
if(todo.id===id){
todo.text = editingText
}
return todo
})
setTodos(updateTodos)
setTodoEditing(null)
setEditingText("")
}
const handleRadioBtnItem = (event) =>{
setDeleteItem(event.target.value);
setEditItem(event.target.value);
}
return (
<div className="App">
<div className='todo-head'>
<h1 className='ForHeading'>Todo List</h1>
<form onSubmit={handleSubmit}>
<input className='User-Input' type='text' onChange={(e)=>setTodo(e.target.value)} value={todo}/>
<button className='Add-Btn' type='submit' disabled={!todo}>Add Todo</button>
</form>
</div>
{todos.map((todo)=>
<ul className='ul-Style' key={todo.id} id={todo.id}>
<input className='Rdo' type='radio' onClick={handleRadioBtnItem}/>
{todoEditing === todo.id ?
(
<div>
<Modal
isOpen={modalIsOpen}
shouldCloseOnOverlayClick={false}
style={
{
overlay:{
backgroundColor:'gray'
},
content:{
textAlign:'center'
}
}
}
>
<h2>Edit Items</h2>
<input type='text' onChange={(e)=> setEditingText(e.target.value)} value={editingText}/>
<div>
<button onClick={()=>editTodo(todo.id)} disabled=''>Save</button>
<button onClick={()=>setModalIsOpen(false)}>Close</button>
</div>
</Modal>
{todo.text}
</div>
)
:
(
<p>{todo.text}</p>
)
}
<button
className='Edit-Btn'
onClick={()=>{setTodoEditing(todo.id);setModalIsOpen(true)}}
disabled={!editItem}><AiFillEdit/>
</button>
<button
className='Del-Btn'
onClick={()=>deleteTodo(todo.id)}
disabled={!deleteItem}><AiFillDelete/>
</button>
</ul>)}
</div>
);
}
export default App;
I'm learning React and I created a simple todo list app and I'm trying to erase the input field as I did for my onClick function on my keypress function. However, it doesn't render the same when I use setTodoInput(""); on that keypress function. It only shows the first character of the input. If I comment out setTodoInput(""); out of the keypress function, it works fine, but the input field doesn't erase. I don't understand why although I have a controlled input, it doesn't function the same. if someone can please explain, it would be appreciated.
this is my code for my App file:
import React, { useState } from "react";
import InputArea from "./InputArea";
import ToDoTask from "./ToDoTask";
function App() {
const [todoTasks, setTodoTasks] = useState([]);
function addTask(todoInput) {
setTodoTasks((prevTodoTasks) => {
return [...prevTodoTasks, todoInput];
});
}
function handleKeyPress(event) {
if (event.key === "Enter") {
setTodoTasks((prevTodoInput) => {
const newTodoInput = event.target.value;
return [...prevTodoInput, newTodoInput];
});
// const newTodoInput = event.target.value;
// setTodoTasks((prevTodoTasks) => {
// console.log(newTodoInput);
// return [...prevTodoTasks, newTodoInput];
// });
// }
}
}
function deleteTodoTask(id) {
setTodoTasks((prevTodoTasks) => {
return prevTodoTasks.filter((task, i) => {
return i !== id;
});
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<InputArea onAdd={addTask} onKeyPress={handleKeyPress} />
</div>
<div>
<ul>
{todoTasks.map((todoTasks, i) => (
<ToDoTask
key={i}
id={i}
text={todoTasks}
onChecked={deleteTodoTask}
/>
))}
</ul>
</div>
</div>
);
}
export default App;
I also created an input component:
import React, { useState } from "react";
function InputArea(props) {
const [todoInput, setTodoInput] = useState("");
function handleChange(event) {
const newInput = event.target.value;
setTodoInput(newInput);
}
return (
<div className="form">
<input
onKeyDown={(event) => {
props.onKeyPress(event);
setTodoInput("");
}}
onChange={handleChange}
type="text"
value={todoInput}
/>
<button
onClick={() => {
props.onAdd(todoInput);
setTodoInput("");
}}
>
<span>Add</span>
</button>
</div>
);
}
export default InputArea;
this is my todoTask component:
import React from "react";
function ToDoTask(props) {
return (
<div
onClick={() => {
props.onChecked(props.id);
}}
>
<li>{props.text}</li>
</div>
);
}
export default ToDoTask;
If the goal is to clear the input when "enter" is pressed then I suggest using a form element. So long as there is just the one input then pressing enter while focused will submit the form. Use the form's submit handler to call the onAdd callback and reset the local todoInput state.
InputArea
function InputArea({ onAdd }) {
const [todoInput, setTodoInput] = useState("");
const submitHandler = (e) => {
e.preventDefault();
if (todoInput) {
onAdd(todoInput);
setTodoInput("");
}
};
function handleChange(event) {
const { value } = event.target;
setTodoInput(value);
}
return (
<form onSubmit={submitHandler}>
<input onChange={handleChange} type="text" value={todoInput} />
<button type="submit">
<span>Add</span>
</button>
</form>
);
}
Demo
function InputArea({ onAdd }) {
const [todoInput, setTodoInput] = React.useState("");
const submitHandler = (e) => {
e.preventDefault();
if (todoInput) {
onAdd(todoInput);
setTodoInput("");
}
};
function handleChange(event) {
const { value } = event.target;
setTodoInput(value);
}
return (
<form onSubmit={submitHandler}>
<input onChange={handleChange} type="text" value={todoInput} />
<button type="submit">
<span>Add</span>
</button>
</form>
);
}
function ToDoTask(props) {
return (
<div
onClick={() => {
props.onChecked(props.id);
}}
>
<li>{props.text}</li>
</div>
);
}
function App() {
const [todoTasks, setTodoTasks] = React.useState([]);
function addTask(todoInput) {
setTodoTasks((prevTodoTasks) => {
return [...prevTodoTasks, todoInput];
});
}
function deleteTodoTask(id) {
setTodoTasks((prevTodoTasks) => {
return prevTodoTasks.filter((task, i) => {
return i !== id;
});
});
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<InputArea onAdd={addTask} />
</div>
<div>
<ul>
{todoTasks.map((todoTasks, i) => (
<ToDoTask
key={i}
id={i}
text={todoTasks}
onChecked={deleteTodoTask}
/>
))}
</ul>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
I created a component (sort of popup box) which displays a sign of horoscope, there’s an image and description. The popup box works correctly. I added a button ‘more’ to see more description, so I used a useState for it, but it doesn’t work, when I click on it doesn't show the rest of the text.
Thanks for your help !
const Modal = ({
children, visible, hide, fermer, more,
}) => {
const popup = `popup ${visible ? 'block' : 'hidden'}`;
return (
<div className={popup}>
{fermer ? null : (
<button className="close" onClick={hide} type="button">X</button>
)}
{children}
<button className="more" onClick={more} type="button">more</button>
</div>
);
};
export default Modal;
import './App.css';
import { useState } from 'react';
import Element from './Element';
import Modal from './Modal';
import Bd from './Bd';
function App() {
const bd = Bd.map((element) => (
<Element
nom={element.nom}
image={element.image}
description={element.description}
modulo={element.modulo}
/>
));
const [year, setYear] = useState('');
function handleChange(event) {
setYear(event.target.value);
}
const [signe, setSigne] = useState([]);
const [vis, setVis] = useState(false);
const [desc, setDesc] = useState(true);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
Bd.map((element) => (
yearModulo === element.modulo ? setSigne(
[<h1>{element.nom}</h1>,
<div>{element.description.substr(0, 150)}</div>,
desc ? <div />
: <div>{element.description.substr(150, 600)}</div>,
<img src={`/images/${element.image}`} alt="" />,
],
) : false
));
}
return (
<div>
<div>
<input
className="text-center font-bold"
type="number"
id="year"
name="year"
value={year}
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit}>Valider</button>
</div>
<div className="flex flex-wrap">{bd}</div>
<Modal
visible={vis}
hide={() => setVis(false)}
more={() => setDesc(false)}
>
<div>
<div>{signe}</div>
</div>
</Modal>
</div>
);
}
export default App;
I would avoid storing in a local state a component (setSigne([<h1>{element.nom}</h1>,...). Prefer storing in the state the values that cannot be computed from other existing states, and generate the elements at rendering.
const [signe, setSigne] = useState(null);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
setSigne(Bd.find(element => yearModulo === element.modulo));
}
// ...
<div>
{signe && <div>
<h1>{signe.nom}</h1>
<div>{signe.description.substr(0, 150)}</div>
{desc ? <div /> : <div>{signe.description.substr(150, 600)}</div>}
<img src={`/images/${signe.image}`} alt="" />
</div>}
</div>
Also, don’t forget to add a key prop when generating elements from an array:
const bd = Bd.map(element => (
<Element
key={element.nom}
// ...
I have a simple feedback application that contains multiple component.
app.js - parent component
data.js - this contain a dummy data the have name and the listofreview
component
user - this component will display the name and the thumbnail
review - this will display the list of review about the user and also have button that say leave a review.
modal - after the user click the leave a review this modal will appear that have a list of element that will update the list of review
I used useReducer to update the state. But the problem is the review.js don't show the updated state. maybe because the useReducer is located on the modal.js. What should i do so that i can also update the data that been display on the review.js
App.js
function App() {
return (
<div className="main-container">
<DisplayUser />
<div className="mainContent-container">
<DisplayReview />
</div>
</div>
);
}
User.js
import { data } from '../../src/data';
const CommentHandler = () => {
const [user] = React.useState(data);
return (
<>
{user.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
Review.js
import DisplayModal from './Modal'
import Modal from 'react-modal';
import { useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
const ReviewHandler = () => {
const [user] = useState(data);
const [showModal, setShowModal] = useState(false);
return (
<>
{user.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
Modal.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, data);
const [hoverRating, setHoverRating] = useState(null);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { reviewId: new Date().getTime(), name, occupation, rating, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: newReview
});
}
}
return (
<div className="modal-container">
<div className="modal-backdrop">
<form className="modal-inner" onSubmit={handelSubmit}>
<h2>Feel feel to send us your review!</h2>
<div className='revieweRating-container'>
<h3>How was your experience?</h3><p onClick={() => props.onClick(false)}>X</p>
<div className="dynamic-review">
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label>
<input type="radio"
name="review-star"
value={ratingValue}
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
onClick={() => setRating(ratingValue)}>
</input>
<FontAwesomeIcon
icon="star"
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
color={ratingValue <= (hoverRating || rating) ? "#FAD020" : "#BCC5D3"}
className="review-star" />
</label>
)
})}
</div>
</div>
<input
type="text"
name="name"
className="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
name="occupation"
className="alioccupationas"
placeholder="Aloccupationias"
value={occupation}
onChange={(e) => setOccupation(e.target.value)}
/>
<textarea
name="review"
cols="30"
rows="6"
className="review"
placeholder="Enter your review here!"
value={reviews}
onChange={(e) => setReviews(e.target.value)}>
</textarea>
<button type="submit" className="submit">SEND A REVIEW</button>
</form>
<div>
{state.map((data) => (
<div key={data.id}>
{data.listOfReview.map((review) => (
<div key={review.reviewId}>
<h3>{review.name}</h3>
<p>{review.occupation}</p>
</div>
))}
</div>
))}
</div>
</div>
</div >
);
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
console.log(state);
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
data.js
export const data = [
{
id: 1607089645363,
name: 'Andress Bonifacio',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'Juan Dela Cruz',
occupation: 'Father of Phil. Revolution',
rating: 5,
review: 'Numquam labore or dolorem enim but accusantium and autem ratione.',
}
]
}
];
If you want to get the updated data across all component, then make sure to have this line of code const [state, dispatch] = useReducer(reducer, data); available in every component that use them like in:-
in User.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const CommentHandler = () => {
// not this
const [user] = React.useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
return (
<>
{state.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
in Review.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ReviewHandler = () => {
// not this
// const [user] = useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
const [showModal, setShowModal] = useState(false);
return (
<>
{state.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
I would suggest you try and use context api instead of just relying on useReducer.