How to update only selected component with react hooks - react-hooks

I'm coding a to-do list using React hooks.
Every added item has two dropdown list where the user can decide how urgent the task (urgency value) is and how long the thing to do will take (speed value).
Updating either list will add their value into a 'score' property.
By clicking a "Sort" button I can sort the entries based on the score.
Right now the problem is that if I have more then one to-do item with different urgency and speed value, the score will always be the same for both components.
Can somebody help? Thanks
function ToDo(){
const [ input, setInput ] = React.useState('')
const [ toDo, setToDo ] = React.useState([])
const [ score, setScore ] = React.useState(0)
const [ speed, setSpeed ] = React.useState(0)
const [ urgency, setUrgency ] = React.useState(0)
return(
<div>
<h2>List of things to do</h2>
<input
value={ input }
onChange={ (e) => setInput( e.target.value ) }/>
<button
onClick={ () => {
setToDo( toDo.concat(input))
setInput('')
}}>Add
</button>
<ul>
{ toDo.map(( task, idTask ) => {
return (
<li
key={idTask}
score={ speed + urgency }>
{task}<br/>
<select onChange={(e) => { setSpeed(Number(e.target.value)) }}>
<option value={1}>slow</option>
<option value={2}>medium</option>
<option value={3}>fast</option>
</select><br/>
<select onChange={(e) => { setUrgency(Number(e.target.value)) }}>
<option value={1}>non-urgent</option>
<option value={3}>urgent</option>
</select>
<span
onClick={
(index) => {
const newTodos = [...toDo]
newTodos.splice(index, 1);
setToDo( newTodos)
}}>
[-------]
</span>
</li>
)
})
}
</ul>
<button onClick={
() => {
const sortMe = [...toDo].sort((a, b) => b - a)
setToDo( sortMe )
}}>Sort!</button>
</div>
)
}
ReactDOM.render(<ToDo/>, document.getElementById('app'));

You should implement a different data model to achieve that. You should hold an array of objects for your todos (each todo will be an object) and each object should have an urgency property so you can set that individually.
Something like this:
function App() {
const [todos,setTodos] = React.useState([
{ id: 'todo1', text: 'This is todo1', urgency: 0 },
{ id: 'todo2', text: 'This is todo2', urgency: 1 }
]);
function handleClick(id) {
setTodos((prevState) => {
let aux = Array.from(prevState);
aux = aux.map((todo) => {
if (todo.id === id) {
todo.urgency === 0 ? todo.urgency = 1 : todo.urgency = 0;
}
return todo;
});
return aux;
});
}
const todoItems = todos.map((todo) =>
<li
key={todo.id}
className={todo.urgency === 1 ? 'urgent' : 'normal'}
onClick={()=>handleClick(todo.id)}
>
{todo.text}
{!!todo.urgency && '<--- This is urgent'}
</li>
);
return(
<React.Fragment>
<div>
Click on the todos!
</div>
<ul>
{todoItems}
</ul>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
li {
cursor: pointer;
}
.urgent {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Related

Todo list refreshing there is a list item with empty name and buttons

When I'm refreshing or first opening there is a list item with empty name and its buttons. Moreover i can't disappear the empty line when i'm using && this in todolist still shows.when im console.log(todos) i got array with 1 item on it.
How can i solve this problem?
const App = () => {
const [todos, setTodos] = useState([{
userInput: null,
isDone: false,
}])
const addItem = (userInput) => {
const newTodos = [...todos, {userInput}]
setTodos(newTodos)
}
const markItem = index =>{
const newTodos = [...todos];
newTodos[index].isDone = true;
setTodos(newTodos);
}
const removeItem = index => {
const newTodos = [...todos];
newTodos.splice(index,1);
setTodos(newTodos)
}
return (
<div className=>
<Calender />
<TodoInput addItem={addItem} />
{(todos?.length > 0 ) && <TodoList todos={todos} removeItem={removeItem} markItem={markItem} />}
</div>
);
}
export default App;
const TodoList = ({ todos,removeItem,markItem }) => {
return (
<div>
<ul>
{todos?.map((todo,index) => {
return (
<li key={index} >
<TodoItem todo={todo} index={index} removeItem={removeItem} markItem={markItem} />
</li>
)
})}
</ul>
</div>
)
}
const TodoItem = ({todo,index,removeItem,markItem}) => {
return (
<div>
<span className={(todo.isDone ? "line-through" : "")}>{todo.userInput}</span>
<div>
<button onClick={()=>markItem(index)}>✔</button>
<button onClick={()=>removeItem(index)}>X</button>
</div>
</div>
)
}
export default TodoItem
Why there is empty task like in the image:
You have assigned 1 item in useState
const [todos, setTodos] = useState([{
userInput: null,
isDone: false,
}])
You should declare it empty
const [todos, setTodos] = useState([])

Mysterious transparent box blocking mesh renderring

I have a React-Three-Fiber map project i'm working on, but running into a weird bug that I can't isolate.
On desktops the little map works perfectly, but once on Android, a transparent roughly 50(?)px size box in the middle of the viewport shows up and blocks and additional renderring in it until I pan the map past it.
Here's a video of what its doing:
https://youtu.be/4L1I9cZX1OM
Heres the code i'm using:
function App({moduleData}) {
const [floor, setFloor] = useState(1);
const [currentFloor, setCurrentFloor] = useState(floor);
const [riverwalkMap, setRiverwalkMap] = useState(moduleData.floor1.map_image.src);
const [currentPinSet, setCurrentPinSet] = useState('floor1');
const [pinInfo, setPinInfo] = useState()
const [zoomLevel, setZoomLevel] = useState(15);
const [pinHtml, setPinHtml] = useState("");
const [showMap, setShowMap] = useState(true);
const [pinHeader, setPinHeader] = useState("Unit: ");
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
function setPinColor(pinColor){
switch (pinColor){
case "green":
return 'pin_green.png'
case "red":
return 'pin_red.png'
case "yellow":
return 'pin_yellow.png'
default:
return 'pin_green.png'
}
}
function Scene({mapImage}){
const riverwalkMap = useLoader(TextureLoader, mapImage);
return(
<>
<mesh>
<planeGeometry args={[70,50]}/>
<meshBasicMaterial map={riverwalkMap} toneMapped={false}/>
</mesh>
</>
)
}
function Pin({props,pinInfo}){
const [active, setActive] = useState(0);
const [showPopup, setShowPopup] = useState(false);
const [hovered, setHovered] = useState(false);
useEffect(()=>{
document.body.style.cursor = hovered ? 'pointer' : 'auto'
},[hovered])
const { spring } = useSpring({
spring: active,
onChange: () => {
invalidate()
},
config: {mass:5, tension: 400, friction: 50, precision: 0.0001}
})
function returnPinClass(pin){
console.log('pin is: ' + pin);
switch (pin){
case 'available':
return '<span class="greenText">Available</span>';
default:
return '<span class="redText">Sold</span>';
}
}
function returnPinImage(pin){
console.log('pin image is: ' + pin);
return `<img src=${pin.src} alt=${pin.alt}/>`;
}
function setHtml(){
setPinHeader(`<h2>Unit: ${pinInfo.popup_title}</h2>`);
setPinHtml(`
<div class="popupContentBlock">
<div class="contentBlocks">
<div class="contentBlockLeft">
<div class="pdfViewer">
<iframe src="${pinInfo.pdf_file}"></iframe>
</div>
<div class="fractionalDiv">
Download PDF
</div>
</div>
</div>
</div>
`);
}
const scale = spring.to([0,1], [.6,1.25]);
const pinTexture = useLoader(TextureLoader, setPinColor(pinInfo.pin_color));
return(
<>
<a.mesh {...props}
scale-x={scale}
scale-y={scale}
scale-z={scale}
onPointerOver={(e)=> {
setActive(Number(!active));
}}
onPointerOut={(e)=>{
setActive(Number(!active));
}}
onClick={e => {
setHtml();
setOpen(o => !o);
// setShowMap(false);
}}
onPointerMissed={() => {setShowPopup(false)}}
position={[pinInfo.pin_position.x_pos,pinInfo.pin_position.y_pos,0]}
>
<planeGeometry args={[5,5]}/>
<meshBasicMaterial map={pinTexture} toneMapped={false} transparent={true}/>
</a.mesh>
</>
)
}
function setFloorButton(floor) {
setFloor(floor)
floorHeading(floor)
changePins(floor)
setCurrentFloor(floor)
}
function floorHeading(sentFloor) {
switch (sentFloor) {
case 1:
changeMap(moduleData.floor1)
return ReactHtmlParser(moduleData.floor1.heading);
case 2:
changeMap(moduleData.floor2)
return ReactHtmlParser(moduleData.floor2.heading);
case 3:
changeMap(moduleData.floor3)
return ReactHtmlParser(moduleData.floor3.heading);
case 4:
changeMap(moduleData.floor4)
return ReactHtmlParser(moduleData.floor4.heading);
default:
return 'No Floor Selected';
}
}
function changeMap(floorData) {
setRiverwalkMap(floorData.map_image.src);
}
function changePins(sentFloor){
setCurrentPinSet('floor'+sentFloor);
}
function closePop(){
setOpen(false);
}
function drawPins(currentSet){
switch (currentSet){
case 'floor1':
return moduleData.floor1.pins;
case 'floor2':
return moduleData.floor2.pins;
case 'floor3':
return moduleData.floor3.pins;
case 'floor4':
return moduleData.floor4.pins;
}
}
// THREE JS STUFF
// Drop Pins Programmatically
console.log(moduleData);
return (
<div className="cms-react-boilerplate__container">
<div className={"mapInfo"}>
<h1>Floor {currentFloor}</h1>
<p>Floors:</p>
<div className={"buttonSelector"}>
<button onClick={(e) => {
setFloorButton(1);
setPinHtml('');
}}>1</button>
<button onClick={(e) => {
setFloorButton(2);
setPinHtml('');
}}>2</button>
<button onClick={(e) => {
setFloorButton(3);
setPinHtml('');
}}>3</button>
<button onClick={(e) => {
setFloorButton(4);
setPinHtml('');
}}>4</button>
</div>
</div>
<div className={"mapGrid"}>
<div className={"mapDiv"} style={{ border: "2px solid black" }}>
<Canvas linear flat frameloop="demand" orthographic
camera={{position: [0, 0, 20], zoom: zoomLevel, up: [0, 0, 1], far: 10000}}
>
{showMap ? <Suspense fallback={null}>
{
drawPins(currentPinSet).map(e =>
<Pin pinInfo={e}/>
)}
<Scene mapImage={riverwalkMap}/>
</Suspense> : null}
<MapControls enableRotate={false}/>
</Canvas>
</div>
<div className={'infoLeft'} >
{!showMap ?
<div className={"infoGridBlock"}>
{ReactHtmlParser(pinHeader)}
<div className="closeButton" onClick={() => {
setShowMap(true);
setPinHtml('');
}}>
<p>✖</p>
</div>
</div>
: null }
<Popup open={open} closeOnDocumentClick onClose={closeModal} lockScroll={true}>
<div className={"modalBlock"} role="dialog">
<div className={"popupContentDiv"}>
<div className={"popupHeaderLeft"}>{ReactHtmlParser(pinHeader)}</div>
<div className={"popupHeaderRight"}><a onClick={closePop} aria-label="Close Popup">×</a></div>
</div>
{ReactHtmlParser(pinHtml)}
</div>
</Popup>
</div>
</div>
</div>
);
}
export default App;
What i've tried: I tried messing with some CSS but the closest i've gotten is if I force the React-Three-Fiber Canvas tag to have a unset height and width on mobile everything will render but no clickable elements will work.
Thank you for any help!

Plus and Minus the Quantity of selected item only in the Cart and total price - React Redux

I want to plus or minus the quantity of an item which is added in the cart. The issue is when I add another item to the cart and then when I plus or minus the quantity then the other item's quantity also changes. Also when quantity changes I want to add the total price of the quantity. I am using React Redux. Please help.
CART.JS
import React, { useState } from "react";
import "./cart.css";
import { useDispatch, useSelector } from "react-redux";
import {
clearCart,
minusQuantity,
plusQuantity,
removeFromCart,
} from "../../../redux/actions/CartActions";
const Cart = ({ id }) => {
const dispatch = useDispatch();
const product = useSelector((state) => state.addToCartReducer.products);
const qty = useSelector((state) => state.addToCartReducer.quantity);
// const minus = useSelector((state) => state.addToCartReducer.minus);
console.log(qty);
// console.log(minus);
const [quantity, setQuantity] = useState(1);
return (
<>
{product && (
<div
className={`dvCart ${id ? `col-lg-3` : `col-lg-2`} d-none d-lg-block`}
>
<div className="sticky-top" style={{ top: "90px" }}>
<div className="row">
<div className="col-sm-12 mb-1">
<h5 className="heading-5 d-inline-block mr-2">
Cart {product.length} items
</h5>
<span
className={`paragraph cp ${
product.length === 0 ? `d-none` : ``
}`}
onClick={() => dispatch(clearCart({}))}
>
clear all
</span>
</div>
{product && product.length === 0 ? (
<div className="dvCartEmpty col-12 d-none- mb-2">
<h5 className="heading-5 text-danger">Cart is Empty.</h5>
<p className="paragraph text-danger">
All Good No Bad! Go ahead, order some items from the menu.
</p>
{/* <!-- <img src="images/cart-empty.png" className="img-fluid" width="200" alt=""> --> */}
</div>
) : (
<div className="dvCartItems col-sm-12 mb-2">
<div className="row">
<div className="scrollbar mr-3">
{product.map((item) => {
const { id, heading, img, price, pack, size } = item;
return (
<div key={id} className="item col-sm-12 mb-2 pr-0">
<div className="bg-light pt-2 pb-2">
<div className="col-12">
<h5 className="heading-5">{heading}</h5>
<span className="paragraph mb-1 mr-2">
{size}
</span>
<span
className={`paragraph mb-1 ${
pack === `pack of 0` ? `d-none` : ``
}`}
>
{pack}
</span>
</div>
<div className="col-sm-12">
<div className="d-flex justify-content-between align-items-center">
<div className="addBtn d-flex justify-content-center align-items-center flex-1">
<div className="flex-1 text-center">
<i
className="fa fa-minus mr-1 cp p-1"
onClick={() =>
dispatch(
minusQuantity({ quantity: 1 })
)
}
></i>
</div>
<div className="flex-3 text-center">
<input
type="text"
value={qty}
onChange={(e) =>
setQuantity(e.target.value)
}
className="form-control text-center p-0"
/>
</div>
<div className="flex-1 text-center">
<i
className="fa fa-plus ml-1 cp p-1"
onClick={() =>
dispatch(
plusQuantity({ quantity: 1, id })
)
}
></i>
</div>
</div>
<div className="paragraph text-right">
<i className="fa fa-inr"></i>
{price}
</div>
</div>
</div>
<button
onClick={() =>
dispatch(
removeFromCart({ id, heading, price, size })
)
}
className="btn btn-remove"
>
<i className="fa fa-close"></i>
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
)}
<div className="dvSubTotal col-sm-12 mb-2">
<div className="d-flex justify-content-between">
<div>
<h5 className="heading-5 text-success">Subtotal</h5>
</div>
<div>
<p className="heading-5 text-success">
<i className="fa fa-inr"></i>
{product
.map((item) => item.price)
.reduce((prev, curr) => prev + curr, 0)}
.00
</p>
</div>
</div>
<p className="paragraph">Extra charges may apply.</p>
</div>
<div className="dvProceed col-sm-12 mb-2">
<button
disabled={product.length !== 0 ? false : true}
className="btn btn-black w-100"
>
Proceed
</button>
</div>
</div>
</div>
</div>
)}
</>
);
};
export default Cart;
PRODUCTLIST.JS
import React, { useState } from "react";
import { Link } from "react-router-dom";
import "./productlist.css";
import ProductNotFound from "../../other/product-not-found/ProductNotFound";
import { useDispatch, useSelector } from "react-redux";
import { addToCart } from "../../../redux/actions/CartActions";
const ProductList = ({
id,
img,
pack,
price,
size,
heading,
description,
notfound,
}) => {
const dispatch = useDispatch();
// const [isDisabled, setIsDisabled] = useState(false);
const products = useSelector((state) => state.addToCartReducer.products);
// const isDisabled = useSelector((state) => state.addToCartReducer.isDisabled);
// console.log(isDisabled);
// console.log(products);
return (
<>
{notfound ? (
<ProductNotFound />
) : (
<div className="col-6 col-md-4 col-lg-6 col-xl-3 mb-4">
<div className="border border-light shadow-sm p-1 h-100">
<div className="image">
<p className={pack !== "pack of 0" ? "packs" : "d-none"}>
{pack}
</p>
<div className="bg-light text-center pt-2 pb-2 mb-1">
<Link className="d-inline-block" to={`/${id}`}>
<img src={img} className="img-fluid" alt={heading} />
</Link>
</div>
<h5 className="heading-5 text-center">{heading}</h5>
</div>
<div className="description d-flex justify-content-between mb-1">
<div className="paragraph">
<p>{size}</p>
</div>
<div className="paragraph mr-2">
<span>
<i className="fa fa-inr"></i>
<span>{price}</span>
</span>
</div>
</div>
<div className="addBtn text-center">
<button
onClick={() =>
dispatch(
addToCart({
id,
img,
pack,
price,
size,
heading,
description,
})
)
}
className="btn btn-white w-100"
disabled={products.find((item) => item.id === id) && true}
>
Add to Bag
</button>
</div>
</div>
</div>
)}
</>
);
};
export default ProductList;
CART ACTIONS.JS
import { actionTypes } from "../constants/action-types";
//ADD TO CART
export const addToCart = (item) => {
return {
type: actionTypes.ADD_TO_CART,
payload: item,
};
};
//PLUS QUANTITY
export const plusQuantity = (item) => {
return {
type: actionTypes.PLUS_QUANTITY,
payload: item,
};
};
//MINUS QUANTITY
export const minusQuantity = (item) => {
return {
type: actionTypes.MINUS_QUANTITY,
payload: item,
};
};
//REMOVE FROM CART
export const removeFromCart = (item) => {
return {
type: actionTypes.REMOVE_FROM_CART,
payload: item,
};
};
//CLEAR CART
export const clearCart = (item) => {
return {
type: actionTypes.CLEAR_CART,
payload: item,
};
};
CART REDUCER.JS
import { actionTypes } from "../constants/action-types";
const addToCartiState = {
products: [],
quantity: 1,
// minus: 0,
};
//CART REDUCER
export const addToCartReducer = (state = addToCartiState, action) => {
console.log(state);
console.log(action);
switch (action.type) {
case actionTypes.ADD_TO_CART:
return {
...state,
products: [...state.products, action.payload],
};
case actionTypes.PLUS_QUANTITY:
return {
...state,
//i am not sure if this logic is correct
quantity: state.quantity + action.payload.quantity,
};
case actionTypes.MINUS_QUANTITY:
return {
...state,
//i am not sure if this logic is correct
quantity:
state.quantity > 1 ? state.quantity - action.payload.quantity : 1,
};
case actionTypes.REMOVE_FROM_CART:
const filteredID = state.products.filter(
(item) => item.id !== action.payload.id
);
return {
...state,
products: filteredID,
};
case actionTypes.CLEAR_CART:
return {
...state,
products: [],
};
default:
return state;
}
};
Total cart quantity should be derived state and not stored in state. You should instead store an item quantity and compute a total quantity when rendering.
Add a quantity property when adding an item to the cart. If the item has already been added then simply update the quantity instead of adding a duplicate item.
Example item object:
{
id,
img,
pack,
price,
size,
heading,
description,
}
Cart reducer
const addToCartiState = {
products: [],
};
export const addToCartReducer = (state = addToCartiState, action) => {
switch (action.type) {
case actionTypes.ADD_TO_CART:
const isInCart = state.products.some(item => item.id === action.payload.id);
if (isInCart) {
// update existing item in cart
return {
...state,
products: state.products.map(
item => item.id === action.payload.id
? {
...item,
quantity: item.quantity + 1,
}
: item
),
};
}
// add new item to cart
return {
...state,
products: [
...state.products,
{
...action.payload,
quantity: 1
},
],
};
case actionTypes.PLUS_QUANTITY:
return {
...state,
products: state.products.map(
item => item.id === action.payload.id
? {
...item,
quantity: item.quantity + action.payload.quantity
}
:item
),
};
case actionTypes.MINUS_QUANTITY:
const item = state.products.find(item => item.id === action.payload.id);
if (item?.quantity === 1) {
// new quantity is 0, remove item from cart
return {
...state,
products: state.products.filter(item => item.id !=== action.payload.id),
};
}
// decrement quantity
return {
...state,
products: state.products.map(
item => item.id === action.payload.id
? {
...item,
quantity: item.quantity - action.payload.quantity
}
:item
),
};
case actionTypes.REMOVE_FROM_CART:
return {
...state,
products: state.products.filter(
(item) => item.id !== action.payload.id
),
};
case actionTypes.CLEAR_CART:
return {
...state,
products: [],
};
default:
return state;
}
};
Compute the derived cart total item quantity in the UI.
const Cart = ({ id }) => {
const dispatch = useDispatch();
const products = useSelector((state) => state.addToCartReducer.products);
const quantity = products.reduce((total, { quantity }) => total + quantity, 0);
console.log(quantity);

reducer case set value delayed response

When I dispatch "REMOVE_TODO" on button click it does what I want it to do, the problem I'm having is that when it executes. It doesn't return the correct current array length.
Now when I click an item, it will dispatch "TOGGLE_TODO" which will change the font color and put a line-through the text.
Now while toggled and I click the "Clear Completed" button, it toggles "REMOVE_TODO" and works fine. It removes the items toggled. The problem I'm having is that The number doesn't reflex the current amount of items left in the list when I click the button once..
However if I click the button once more (or however many more times) the number updates to the correct total
This is my app code
import React, { useState, useReducer } from 'react';
import { Reducer } from './reducers/reducer';
import './App.css';
function App() {
const [{ todos, todoCount }, dispatch] = useReducer(Reducer, {
todos: [],
todoCount: 0,
completedCount: 0
});
const [text, setText] = useState("");
return (
<div className="App">
<header className="App-header">
<div>ToDo List [ <span style={{color: '#61dafb', margin: '0px', padding: '0px'}}>{ todoCount }</span> ]</div>
<div>
{ todos.map((todo, index) => (
<div
key={index}
onClick={() => dispatch(
{ type: "TOGGLE_TODO", index }
)}
style={{
fontFamily: 'Tahoma',
fontSize: '1.5rem',
textDecoration: todo.completed ? 'line-through' : "",
color: todo.completed ? '#61dafb' : 'dimgray',
cursor: 'pointer'
}}
>
{ todo.text }
</div>
))
}
<form
onSubmit={e => {
e.preventDefault();
text.length === 0 ? alert("No Task To Add!") : dispatch({ type: "ADD_TODO", text });
setText("");
}}
>
<input
type="text"
name="input"
value={ text }
onChange={e => setText(e.target.value)}
/><br />
<button>
Add
</button>
</form>
<button onClick={() => {dispatch({ type: "REMOVE_TODO" })}}>
Clear Completed
</button>
</div>
</header>
</div>
);
}
export default App;
and this is my reducer code
export const Reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: [...state.todos, { text: action.text, completed: false, id: Date.now() }],
todoCount: state.todoCount + 1,
completedCount: 0
};
case 'TOGGLE_TODO':
return {
todos: state.todos.map((todo, index) => index === action.index ? { ...todo, completed: !todo.completed } : todo),
todoCount: state.todoCount,
completedCount: 0
};
case 'REMOVE_TODO':
return {
todos: state.todos.filter(t => !t.completed),
todoCount: state.todos.length
}
default:
return state;
};
};
Does anyone have any idea what I'm doing wrong, or what I'm not doing? Thanks in advance!
Remove "todoCount" from reducer, then derive count using "todos":
<div>
ToDo List [{" "}
<span style={{ color: "#61dafb", margin: "0px", padding: "0px" }}>
{todos.filter((todo) => !todo.completed).length}
</span>{" "}
]
</div>
View in CodeSandbox here

How to Display search result on another page in react hooks

I m developing an e-commerce application with react hooks in search component result is shown beneath the component I want to render on search Result component how to redirect search result on the search Result component
import React, { useState, useEffect } from "react";
import { Link, Redirect } from "react-router-dom";
import { getCategories, list } from "./apiCore";
import SearchResult from "./SearchResult";
const Search = () => {
const [data, setData] = useState({
categories: [],
category: "",
search: "",
results: [],
searched: false,
});
const { categories, category, search, results, searched } = data;
const loadCategories = () => {
getCategories().then(data => {
if (data.error) {
console.log(data.error);
} else {
setData({ ...data, categories: data });
}
});
};
useEffect(() => {
loadCategories();
}, []);
const searchData = () => {
// console.log(search, category);
if (search) {
list({ search: search || undefined, category: category }).then(
response => {
if (response.error) {
console.log(response.error);
} else {
setData({ ...data, results: response, searched: true });
}
}
);
}
};
const searchSubmit = e => {
e.preventDefault();
searchData();
};
const handleChange = name => event => {
setData({ ...data, [name]: event.target.value, searched: false });
};
const searchMessage = (searched, results) => {
if (searched && results.length > 0) {
return `Found ${results.length} products`;
}
if (searched && results.length < 1) {
return `No products found`;
}
};
const searchedProducts = (results = []) => {
return (
<div>
<h2 className="text-muted mb-4">
{searchMessage(searched, results)}
</h2>
<div className="row">
{results.map((product, i) => (
<CarouselCard key={i} product={product} />
))}
</div>
</div>
);
};
return (
<div className="site-navbar-top">
<div className="container">
<div className="row align-items-center">
<div className="col-6 col-md-4 order-2 order-md-1 site-search-icon text-left">
<form className="site-block-top-search" onSubmit={searchSubmit}>
<span className="icon icon-search2"></span>
<input type="text" className="form-control border-0" placeholder="Search" onChange={handleChange("search")} />
</form>
</div>
<div className="col-12 mb-3 mb-md-0 col-md-4 order-1 order-md-2 text-center">
<div className="site-logo">
<Link to="/" className="js-logo-clone">Shoppers</Link>
</div>
</div>
<div className="col-6 col-md-4 order-3 order-md-3 text-right">
<div className="site-top-icons">
<ul>
<li><Link to="#"><span className="icon icon-person" /></Link></li>
<li><Link to="#"><span className="icon icon-heart-o" /></Link></li>
<li>
<Link to="cart.html" className="site-cart">
<span className="icon icon-shopping_cart" />
<span className="count">2</span>
</Link>
</li>
</ul>
</div>
</div>
</div>
</div>
{searchedProducts(results)}
</div>
)
}
export default Search;
The main part of the hook is pretty straightforward, but I'm having a difficult time finding a nice way to handle that redirect. The current working solution is to wrap the functional component with withRouter, then pass props.history

Resources