OnClick toggles all items - javascript

I want to toggle each of the item that I clicked on but Its keeps toggling all the Items. Using the useContext api
import React, { useState, useEffect } from "react";
const MyContext = React.createContext({
addToFavorites: () => {},
likeHandler: () => {},
fetchRequest: () => {},
});
export const MyContextProvider = (props) => {
const [favorites, setfavorites] = useState([]);
const [toggle, setToggle] = useState(false);
const [items, setItems] = useState([]);
const fetchRequest = async () => {
const api_Key = "oLfD9P45t23L5bwYmF2sib88WW5yZ8Xd7mkmhGSy";
const response = await fetch(
`https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?
sol=50&api_key=${api_Key}`
);
const data = await response.json();
const allItems = data.photos.map((item) => {
return {
id: item.id,
title: item.camera.full_name,
img: item.img_src,
date: item.rover.launch_date,
like: false,
};
});
setItems(allItems);
};
const likeHandler = (item) => {
const found = items.find((x) => x.id === item.id);
setToggle((found.like = !found.like));
console.log(found); //this logs the particular item that is clicked on
};
return (
<MyContext.Provider
value={{
likeHandler,
fetchRequest,
toggleLike: toggle,
data: items,
}}
>
{props.children}
</MyContext.Provider>
);
};
export default MyContext;
I also have a NasaCard component where I call the likeHandler function and the toggle state, onClick of the FavoriteIcon from my context.And I pass in the toggle state to a liked props in my styled component to set the color of the favorite Icon
import {
Container,
Image,
Name,
InnerContainer,
Titlecontainer,
Date,
FavouriteContainer,
FavouriteIcon,
ImageContainer,
SocialContainer,
} from "./index";
import MyContext from "../../Context/store";
import React, { useState, useEffect, useContext } from "react";
const NasaCards = (props) => {
const { likeHandler, toggleLike } = useContext(MyContext);
return (
<Container>
<InnerContainer>
<ImageContainer>
<Image src={props.Image} alt="" />
</ImageContainer>
<Titlecontainer>
<Name>{props.title}</Name>
<Date>{props.date}</Date>
<FavouriteContainer>
<FavouriteIcon
liked={toggleLike}
onClick={() => {
likeHandler({
id: props.id,
title: props.title,
Image: props.Image,
});
}}
/>
</InnerContainer>
</Container>
);
};
export default NasaCards;

I think you're making this a little more complicated than it needs to be. For starters you are using a single boolean toggle state for all the context consumers, and then I think you're mixing your like property on the items state array with the toggle state.
The items array objects have a like property, so you can simply toggle that in the context, and then also use that property when mapping that array.
MyContextProvider - Map the items state to a new array, updating the like property of the matching item.
const likeHandler = (item) => {
setItems(items => items.map(
el => el.id === item.id
? { ...el, like: !el.like }
: el
));
console.log(item); // this logs the particular item that is clicked on
};
NasaCards - Use item.like property for the liked prop on FavouriteIcon and pass the entire props object to the likeHandler callback.
const NasaCards = (props) => {
const { likeHandler } = useContext(MyContext);
return (
<Container>
<InnerContainer>
<ImageContainer>
<Image src={props.Image} alt="" />
</ImageContainer>
<Titlecontainer>
<Name>{props.title}</Name>
<Date>{props.date}</Date>
<FavouriteContainer>
<FavouriteIcon
liked={props.like} // <-- use like property
onClick={() => {
likeHandler(props); // <-- props has id property
}}
/>
</FavouriteContainer>
</Titlecontainer?
</InnerContainer>
</Container>
);
};

Related

How can I make a todo list using functional components?

I'm making it so that every component is one element (button, the whole list, a single element...) I'm having trouble figuring out how to make my list print below the form. Tasks are shown in console.log() but I can't seem to get the right data transferred.
Thanks in advance for any help
This is items.jsx code
import React, { useState} from 'react'
import './todo.css'
import List from './list'
import Button from './button';
function Items () {
const [tasks, setTasks] = useState([]);
const [value, setvalue] = useState("");
/* const onChange = (e) => {
setvalue(e.target.value)
// console.log('type')
} */
const onAddTask = (e) =>{
e.preventDefault();
console.log('submit')
const obj = {
name: value ,
id: Date.now(),
};
if (value !== "") {
setTasks(tasks.concat(obj));
setvalue("")
console.log(obj)
}
};
return(
<div className="form">
<header>Your todo list</header>
<input
placeholder="type your task"
value={value}
onChange={(e) => setvalue(e.target.value)}/>
<input type="date" placeholder='Set your date!'/>
<button onClick={onAddTask}>Submit task</button>
<List data = {List}/>
</div>
)
}
export default Items
This is list.jsx code
import React , { useState } from "react";
import "./Items"
import Button from "./button"
const List = (tasks) => {
return(
<div>
{tasks.map}
</div>
)
console.log(task.map)
}
export default List
step 1
Here's a fully functioning demo to get you started -
function Todo() {
const [items, setItems] = React.useState([])
const [value, setValue] = React.useState("")
const addItem = event =>
setItems([...items, { id: Date.now(), value, done: false }])
return <div>
<List items={items} />
<input value={value} onChange={e => setValue(e.target.value)} />
<button type="button" onClick={addItem}>Add</button>
</div>
}
function List({ items = [] }) {
return <ul>
{items.map(item =>
<ListItem key={item.id} item={item} />
)}
</ul>
}
function ListItem({ item = {} }) {
return <li>{item.value}</li>
}
ReactDOM.render(<Todo />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
start with good state
Note using an Array to store the items is somewhat inefficient for the kinds of operations you will need to perform. Consider using a Map instead. Run the demo again and click on a list item to toggle its state -
const update = (m, key, func) =>
new Map(m).set(key, func(m.get(key)))
function Todo() {
const [items, setItems] = React.useState(new Map)
const [value, setValue] = React.useState("")
const addItem = event => {
const id = Date.now()
setItems(update(items, id, _ => ({ id, value, done: false })))
}
const toggleItem = id => event =>
setItems(update(items, id, item => ({ ...item, done: !item.done })))
return <div>
<List items={items} onClick={toggleItem} />
<input value={value} onChange={e => setValue(e.target.value)} />
<button type="button" onClick={addItem}>Add</button>
</div>
}
function List({ items = new Map, onClick }) {
return <ul>
{Array.from(items.values(), item =>
<ListItem key={item.id} item={item} onClick={onClick(item.id)} />
)}
</ul>
}
function ListItem({ item = {}, onClick }) {
return <li onClick={onClick}>
{ item.done
? <s>{item.value}</s>
: item.value
}
</li>
}
ReactDOM.render(<Todo />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
do more with less
Functional programming goes a long way in React. Using a curried update function we can take advantage of React's functional updates -
const update = (key, func) => m => // <-
new Map(m).set(key, func(m.get(key)))
function Todo() {
// ...
const addItem = event => {
const id = Date.now()
setItems(update(id, _ => ({ id, value, done: false }))) // <-
}
const toggleItem = id => event =>
setItems(update(id, item => ({ ...item, done: !item.done }))) // <-
// ...
}
but don't stop there
Avoid creating the todo item data by hand { id: ..., value: ..., done: ... }. Instead let's make an immutable TodoItem class to represent our data. A class also gives us an appropriate container for functions that would operate on our new data type -
class TodoItem {
constructor(id = 0, value = "", done = false) {
this.id = id
this.value = value
this.done = done
}
toggle() {
return new TodoItem(id, value, !this.done) // <- *new* data
}
}
Now our Todo component is unmistakable with its intentions -
function Todo() {
// ...
const [items, setItems] = useState(new Map)
const addItem = event => {
const id = Date.now()
setItems(update(id, _ => new TodoItem(id, value))) // <- new TodoItem
}
const toggleItem = id => event =>
setItems(update(id, item => item.toggle())) // <- item.toggle
// ...
}

How can I send the state (useState) of one file component to another file's component?

REACT.js:
Let say I have a home page with a search bar, and the search bar is a separate component file i'm calling.
The search bar file contains the useState, set to whatever the user selects. How do I pull that state from the search bar and give it to the original home page that
SearchBar is called in?
The SearchBar Code might look something like this..
import React, { useEffect, useState } from 'react'
import {DropdownButton, Dropdown} from 'react-bootstrap';
import axios from 'axios';
const StateSearch = () =>{
const [states, setStates] = useState([])
const [ stateChoice, setStateChoice] = useState("")
useEffect (()=>{
getStates();
},[])
const getStates = async () => {
let response = await axios.get('/states')
setStates(response.data)
}
const populateDropdown = () => {
return states.map((s)=>{
return (
<Dropdown.Item as="button" value={s.name}>{s.name}</Dropdown.Item>
)
})
}
const handleSubmit = (value) => {
setStateChoice(value);
}
return (
<div>
<DropdownButton
onClick={(e) => handleSubmit(e.target.value)}
id="state-dropdown-menu"
title="States"
>
{populateDropdown()}
</DropdownButton>
</div>
)
}
export default StateSearch;
and the home page looks like this
import React, { useContext, useState } from 'react'
import RenderJson from '../components/RenderJson';
import StateSearch from '../components/StateSearch';
import { AuthContext } from '../providers/AuthProvider';
const Home = () => {
const [stateChoice, setStateChoice] = useState('')
const auth = useContext(AuthContext)
console.log(stateChoice)
return(
<div>
<h1>Welcome!</h1>
<h2> Hey there! Glad to see you. Please login to save a route to your prefered locations, or use the finder below to search for your State</h2>
<StateSearch stateChoice={stateChoice} />
</div>
)
};
export default Home;
As you can see, these are two separate files, how do i send the selection the user makes on the search bar as props to the original home page? (or send the state, either one)
You just need to pass one callback into your child.
Homepage
<StateSearch stateChoice={stateChoice} sendSearchResult={value => {
// Your Selected value
}} />
Search bar
const StateSearch = ({ sendSearchResult }) => {
..... // Remaining Code
const handleSubmit = (value) => {
setStateChoice(value);
sendSearchResult(value);
}
You can lift the state up with function you pass via props.
const Home = () => {
const getChoice = (choice) => {
console.log(choice);
}
return <StateSearch stateChoice={stateChoice} giveChoice={getChoice} />
}
const StateSearch = (props) => {
const handleSubmit = (value) => {
props.giveChoice(value);
}
// Remaining code ...
}
Actually there is no need to have stateChoice state in StateSearch component if you are just sending the value up.
Hello and welcome to StackOverflow. I'd recommend using the below structure for an autocomplete search bar. There should be a stateless autocomplete UI component. It should be wrapped into a container that handles the search logic. And finally, pass the value to its parent when the user selects one.
// import { useState, useEffect } from 'react' --> with babel import
const { useState, useEffect } = React // --> with inline script tag
// Autocomplete.jsx
const Autocomplete = ({ onSearch, searchValue, onSelect, suggestionList }) => {
return (
<div>
<input
placeholder="Search!"
value={searchValue}
onChange={({target: { value }}) => onSearch(value)}
/>
<select
value="DEFAULT"
disabled={!suggestionList.length}
onChange={({target: {value}}) => onSelect(value)}
>
<option value="DEFAULT" disabled>Select!</option>
{suggestionList.map(({ id, value }) => (
<option key={id} value={value}>{value}</option>
))}
</select>
</div>
)
}
// SearchBarContainer.jsx
const SearchBarContainer = ({ onSelect }) => {
const [searchValue, setSearchValue] = useState('')
const [suggestionList, setSuggestionList] = useState([])
useEffect(() => {
if (searchValue) {
// some async logic that fetches suggestions based on the search value
setSuggestionList([
{ id: 1, value: `${searchValue} foo` },
{ id: 2, value: `${searchValue} bar` },
])
}
}, [searchValue, setSuggestionList])
return (
<Autocomplete
onSearch={setSearchValue}
searchValue={searchValue}
onSelect={onSelect}
suggestionList={suggestionList}
/>
)
}
// Home.jsx
const Home = ({ children }) => {
const [result, setResult] = useState('')
return (
<div>
<SearchBarContainer onSelect={setResult} />
result: {result}
</div>
)
}
ReactDOM.render(<Home />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Just pass a setState to component
parent component:
const [state, setState] = useState({
selectedItem: ''
})
<StateSearch state={state} setState={setState} />
change parent state from child component:
const StateSearch = ({ state, setState }) => {
const handleStateChange = (args) => setState({…state, selectedItem:args})
return (...
<button onClick={() => handleStateChange("myItem")}/>
...)
}

React OnClick iteration

I want to do an onClick counter but I have a problem with the counter iterating correctly. In the app there are 3 "products" and after clicking "Add To Cart" button the state of the object is updated but all of the products are generated separately. I think that is cousing the problem where the counter is different for each of the products or everything will work correctly if I lift the state up, but the console.log is just freshly generated for all of the products. I'm not really sure so I need help with that.
Here is some code in the order from the parent to the last child:
import { useEffect, useState } from "react";
import ProductList from "./ProductList";
const Products = () => {
const [products, setProducts] = useState (null);
useEffect (() => {
fetch('http://localhost:8000/products')
.then(res => {
return res.json();
})
.then(data => {
setProducts(data);
})
}, []);
return (
<div className="ProductList">
{products && <ProductList products={products}/>}
</div>
);
}
export default Products;
import Card from "./Card";
const ProductList = (props) => {
const products = props.products;
return (
<div className="ProductList" >
{products.map((product) => (
<Card product={product} key={product.id} />))}
</div>
);
}
export default ProductList;
import { useState } from "react";
const Card= ({ product }) => {
const [showDescription, setShowDescription] = useState(false);
const [CartCounter, setCartCounter ] = useState(0);
console.log(CartCounter);
return (
<div className="Product-Preview" >
<div className="backdrop" style={{ backgroundImage: `url(${product.image})` }}></div>
<h2>{product.title}</h2>
<div>{product.price}</div>
<button className="ShowDescription" onClick={() => setShowDescription(!showDescription)}>Details</button>
<button className="AddToCart" onClick={() => setCartCounter(CartCounter + 1)}>Add To Cart </button>
{showDescription && <p>{product.description}</p>}
<br />
</div>
);
};
export default Card;
Ok, you want to keep track of an aggregated value. I'll list code in some high level.
const ProductList = () => {
const [count, setCount] = useState(0)
const addOrRemove = n => { setCount(v => v + n) }
return products.map(p => <Card addOrRemove={addOrRemove} />)
}
const Card = ({ addOrRemove }) => {
// optional if you want to track card count
// const [count, setCount] = useState(0)
return (
<>
<button onClick={() => { addOrRemove(1) }>Add</button>
<button onClick={() => { addOrRemove(-1) }>Remove</button>
</>
)
}
Essentially either you track the local count or not, you need to let the parent to decide what is the final count, otherwise there'll be some out of sync issue between the child and parent.

How to access history prop from a parent component using react?

i have a method showInfo that is accessed by two components Child1 and Child2.
initially i had the showInfo method within Child1 component like below
const Child1 = ({history}: RouteComponentProps) => {
type Item = {
id: string,
name: string,
value: string,
};
const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` : `/value2/?item=${itemId}`;
history.push(path);
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
the above code works. but now i have another component child2 that needs to use the same method showInfo.
the component child2 is like below
const Child2 = () => {
return (
<Component
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
Instead of writing the same method showInfo in Child2 component i thought of having it in different file from where child1 and child2 components can share the method showInfo.
below is the file with name utils.tsx that has showInfo method
export const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` :
`/value2/?item=${itemId}`;
history.push(path); //error here push is not identified as no
//history passed
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
With the above, i get the error where i use history.push in showInfo method. push not defined.
this is because history is not defined in this file utils.tsx
now the question is how can i pass history from child1 or child2 components. or what is the other way that i can access history in this case.
could someone help me with this. thanks.
EDIT:
notify in child1 and child2 is coming from useNotifications which is like below
const useNotifications = () => {
const [activeNotifications, setActiveNotifications] = React.useContext(
NotificationsContext
);
const notify = React.useCallback(
(notifications) => {
const flatNotifications = flatten([notifications]);
setActiveNotifications(activeNotifications => [
...activeNotifications,
...flatNotifications.map(notification => ({
id: notification.id,
...notification,
});
},
[setActiveNotifications]
)
return notify;
}
While you could potentially create a custom hook, your function returns JSX. There's a discussion about returning JSX within custom hooks here. IMO, this is not so much a util or custom hook scenario, as it is a reusable component. Which is fine!
export const ShowInfo = (item: item) => { // capitalize
const history = useHistory(); // use useHistory
// ... the rest of your code
}
Now in Child1 and Child2:
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: <ShowHistory item={item} />
})
}}
/>
);
You may have to adjust some code in terms of your onDone property and notify function, but this pattern gives you the reusable behavior you're looking for.

How to use React component's custom hook with "map"

I'm trying to make a Checkbox component.
Here is my Checkbox.tsx.
import React from "react";
import * as S from "./style";
const Checkbox: React.FC<S.ICheckboxProps> = ({ checked, setChecked }) => {
return <S.StyledCheckbox checked={checked} onClick={setChecked} />;
};
and this is my useCheckbox.tsx,
import { useState } from "react";
export const useCheckbox = (initialState: boolean) => {
const [checked, _setChecked] = useState<boolean>(initialState);
const setCheckedToggle = () => _setChecked((prev) => !prev);
const setCheckedTrue = () => _setChecked(true);
const setCheckedFalse = () => _setChecked(false);
return { checked, setCheckedToggle, setCheckedTrue, setCheckedFalse };
};
export default Checkbox;
It works good. I can use this like
import Layout from "components/Layout";
import { useCheckbox } from "hooks/useCheckbox";
import Checkbox from "components/Checkbox";
const Home = () => {
const { checked, setCheckedToggle } = useCheckbox(false);
return (
<Layout>
<Checkbox checked={checked} setChecked={setCheckedToggle} />
</Layout>
);
};
export default Home;
But I have trouble in the List component.
List has a Checkbox component, and I have to use this List with data.
const Home = ({data}) => {
return (
<Layout>
{data.map((d) => <List />)}
</Layout>
);
};
In this case, is there a way to determine if the list is selected?
If the List has useCheckbox, the Home component doesn't know the checked state.
Should I use useCheckbox in the Home component for data.length times? I think this is not good.
Thanks for reading, and Happy new year.
If you want the checkbox state to exist at the level of Home then you'll need state in the Home component that can handle multiple items, either as an array or object.
Then where you map over data you can pass down checked and setChecked as props to List, with all the logic defined in Home using the item index (or preferably an ID if you have one) in relation to your Home state.
Here's an example of a hook you could use in Home
import { useState } from "react";
export const useCheckboxes = () => {
const [checkedIds, setCheckedIds] = useState([]);
const addToChecked = (id) => setCheckedIds((prev) => [...prev, id]);
const removeFromChecked = (id) =>
setCheckedIds((prev) => prev.filter((existingId) => existingId !== id));
const isChecked = (id) => !!checkedIds.find(id);
const toggleChecked = (id) =>
isChecked(id) ? removeFromChecked(id) : addToChecked(id);
return { isChecked, toggleChecked };
};
And you would use it like this
const Home = ({ data }) => {
const { isChecked, toggleChecked } = useCheckboxes();
return (
<Layout>
{data.map((d) => (
<List
key={d.id}
checked={isChecked(d.id)}
toggleChecked={() => toggleChecked(d.id)}
/>
))}
</Layout>
);
};

Categories

Resources