React how to add existing item to new state in reducer - reactjs

I try to do Todo App, I stack on add existing pending task to completed task. How can i add existing item to new state in reducer or there is a another way to do that?
When i click the button which has completed id, i want to push that task to completed state.
Main:
const App = () => {
const [state, dispatch] = useReducer(todoAppReducer, [])
return (
<TodoAppContext.Provider value={{
state: state,
dispatch,
}}>
<BrowserRouter>
<Header/>
<AddTask />
<Sections />
<Routes>
<Route path='' element={ <Pending /> }/>
<Route path='completed' element={ <Completed /> }/>
<Route path='deleted' element={ <Deleted /> }/>
</Routes>
</BrowserRouter>
</TodoAppContext.Provider>
)
}
export default App
Add Task:
const AddTask = () => {
const {dispatch} = useContext(TodoAppContext)
const [task, setTask] = useState("");
const [taskDetail, setTaskDetail] = useState("");
const [error, setError] = useState("")
const onAddTaskSubmit = (e) => {
e.preventDefault();
if(task == "") {
setError("Please enter task!")
} else {
dispatch({
type: 'ADD_PENDING',
task,
taskDetail
})
setError("")
}
setTask("")
setTaskDetail("")
}
return (
<div className='non-bootstrap-container add-task-form'>
<form onSubmit={onAddTaskSubmit}>
<input value={task} onChange={(e) => setTask(e.target.value)} type="text" placeholder='Enter Task'/>
<textarea value={taskDetail} onChange={(e) => setTaskDetail(e.target.value)} placeholder='Task Details (Optional)'></textarea>
<div className='add-button-and-error'>
<p className='error'>{error}</p>
<button type='submit' className='btn btn-primary'>Add Task</button>
</div>
</form>
</div>
)
}
export default AddTask
Reducer
const todoAppReducer = (state, action) => {
switch(action.type) {
case "ADD_PENDING":
return [
...state,
{title: action.task, detail: action.taskDetail}
]
default:
return state
}
}
export default todoAppReducer
When i click the button which has completed id, i want to push that task to completed state.
const Pending = () => {
const {state} = useContext(TodoAppContext)
return (
<div className='non-bootstrap-container tasks'>
{
<ul>
{
state.map((task,index) => (
<li key={index}>
<div>
<span>{index}. </span>
<span>{task.title}</span>
</div>
<span>{task.detail}</span>
<div>
<button className='btn btn-primary' id='completed'><i className="fa-solid fa-check"></i></button>
<button className='btn btn-danger'><i className="fa-solid fa-trash-can"></i></button>
</div>
</li>
))
}
</ul>
}
</div>
)
}
export default Pending

You first need to set the case in the reducer.
Reducer
case "COMPLETED":
return [
...state,
{title: action.task, detail: action.taskDetail}
]
Now try calling it in submit button when the task is completed with dispatch.
Hope it helps! =)

Related

I want to pass data to parent component foom a child component using props

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;

localStorage.setItem from component to reducer

Pretty new to react. Im trying to set a value in local state when I submit my form.
I´ve done it in the wrong order, so I think that the getItem part in the reducer looks fine, but I dont really know how to setItem.
Probably want to do that in the onSubmit function.
// survej.js (reducer)
const initialState = localStorage.getItem('survey')
? {
klar: JSON.parse(localStorage.getItem('survey')).klar,
fraga1: null,
fraga2: null,
fraga3: null,
fraga4: null
}
: {
klar: false,
fraga1: null,
fraga2: null,
fraga3: null,
fraga4: null
}
const survey = createSlice({
name: 'survey',
initialState,
reducers: {
setFraga1: (store, action) => {
store.fraga1 = action.payload
},
setFraga2: (store, action) => {
store.fraga2 = action.payload
},
setFraga3: (store, action) => {
store.fraga3 = action.payload
},
setFraga4: (store, action) => {
store.fraga4 = action.payload
},
setKlar: (store, action) => {
store.klar = action.payload
}
}
})
export default survey`
// Form.js
import React, { useState, useEffect } from 'react'
import { useDispatch, batch } from 'react-redux'
import survey from '../reducers/survey'
import Summary from './Summary'
import TextWrapper from './TextWrapper'
import NextButton from './NextButton'
import SubmitBtn from './SubmitBtn'
const Form = () => {
const [newCounter, setNewCounter] = useState(0)
const [newFraga1, setNewFraga1] = useState('')
const [newFraga2, setNewFraga2] = useState('')
const [newFraga3, setNewFraga3] = useState('')
const [newFraga4, setNewFraga4] = useState('')
const dispatch = useDispatch()
const onCounterIncrease = () => {
setNewCounter(newCounter + 1)
}
const onSubmit = () => {
batch(() => {
dispatch(survey.actions.setFraga1(newFraga1))
dispatch(survey.actions.setFraga2(newFraga2))
dispatch(survey.actions.setFraga3(newFraga3))
dispatch(survey.actions.setFraga4(newFraga4))
dispatch(survey.actions.setKlar(true))
setNewCounter(newCounter + 1)
})
}
const on1Change = (event) => {
setNewFraga1(event.target.value)
}
const on2Change = (event) => {
setNewFraga2(event.target.value)
}
const on3Change = (event) => {
setNewFraga3(event.target.value)
}
const on4Change = (event) => {
setNewFraga4(event.target.value)
}
return (
<div className="wrapper">
<form className="form" onSubmit={(event) => event.preventDefault()}>
<div className="form-wrapper">
<div className="questions-wrapper">
{newCounter === 0 && (
<div>
<TextWrapper />
<NextButton onCounterIncrease={onCounterIncrease} />
</div>
)}
{newCounter === 1 && (
<div>
<p>Vem är: </p>
<input
tabIndex={1}
type="text"
className="text-input"
value={newFraga1}
onChange={on1Change} />
<NextButton onCounterIncrease={onCounterIncrease} />
</div>
)}
{newCounter === 2 && (
<div>
<p>Vem är: </p>
<input
tabIndex={1}
type="text"
className="text-input"
value={newFraga2}
onChange={on2Change} />
<NextButton onCounterIncrease={onCounterIncrease} />
</div>
)}
{newCounter === 3 && (
<div>
<p>Vem är: </p>
<input
tabIndex={1}
type="text"
className="text-input"
value={newFraga3}
onChange={on3Change} />
<NextButton onCounterIncrease={onCounterIncrease} />
</div>
)}
{newCounter === 4 && (
<div>
<p>Vem är: </p>
<input
tabIndex={1}
type="text"
className="text-input"
value={newFraga4}
onChange={on4Change} />
<SubmitBtn onSubmit ={onSubmit} />
</div>
)}
{newCounter === 5 && (
<div>
<Summary />
</div>
)}
</div>
</div>
</form>
</div>
)
}
When I press the submit button at the last question I want to update localStorage so that klar: is true whenever I refresh the page.
As you have figured out. Yes, you can use localStorage.setItem in the onSubmit function.
like
const onSubmit = () => {
const survey = localStorage.getItem('survey');
const newSurvey = {
...JSON.parse(survey),
klar: true,
// if you want to store this as well.
fraga1: newFraga1,
fraga2: newFraga2,
fraga3: newFraga3,
fraga4: newFraga4
};
localStorage.setItem('survey', JSON.stringify(newSurvey));
// update your state here
}
And bonus tips If you use useState the component will rerender every time you type. so you useRef.
Here is a detailed link from Web Dev Simplified: Form example react mistake.
Hope this helps, comment if there is anything and if still didn't work provide more details or recreate it in codepen online tool.
Thanks.

How to prevent my refinementList component from rerendering and losing state?

I am using react instant search library and my issue is that my custom refinement list components loses its selections when I open modal.
I control my modal with useState:
const [modalIsOpen, setModalIsOpen] = useState(false);
Everytime I call setModalIsOpen(true); the refinements reset.
My custom refinement list component:
const RefinementList = ({ items, refine }: RefinementListProvided) => {
// return the DOM output
return (
<div className="">
{items.map(({ value, label, count, isRefined }: any) => (
<div key={value}>
<motion.button
onClick={() => {
refine(value);
}}
className={``}
>
<div className="">
{label}
</div>
</motion.button>
</div>
))}
</div>
);
};
I connect it with connectRefinementList
const CustomRefinementList = connectRefinementList(RefinementList);
This is my main jsx:
<InstantSearch searchClient={searchClient} indexName="foods">
<CustomSearchBox />
<CustomRefinementList
transformItems={(items) => orderBy(items, "label", "asc")} // this prevents facets jumping
attribute="tags"
/>
<InfiniteHits hitComponent={Hit} cache={sessionStorageCache} />
<ModalForMealPreview
handleOpen={modalIsOpen}
handleClose={handleModalClose}
/>
</InstantSearch>
What can I do to persist state or prevent RefinementList component from rerendering?
Here is a basic Example of React.memo, this will help your code
import React, { useEffect, useState } from "react";
const MemoComp = React.memo(({ ...props }) => <Test {...props} />); // Main Area to watch
function ClassSearch() {
const [state, setState] = useState(1);
return (
<div>
<button onClick={() => setState(state + 1)}>Increase</button> <br />
<MemoComp data="memorized" /> <br />
<Test data="original" /> <br />
</div>
);
}
export default ClassSearch;
const Test = ({ data }) => {
const date = new Date().getTime();
return (
<>
Test {date} {data}
</>
);
};

How to pass the useReducer State to the child component?

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.

Why does React.createPortal doesn't process event.stopPropagation?

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);

Resources