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

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

Related

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")}/>
...)
}

OnClick toggles all items

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

React: Map over array and dynamically render a component in a specific div

Probably I'm missing something really simple, but:
I have an array:
const [weight, setWeight] = useState([]);
And I want to map over it to dynamically render a component:
const renderWeight = () => {
weight.map((e) => {
<Weight />;
});
};
I want this to happen when I click on a submit button, and I want the components to render in a specific div.
I can't find a way to do this.
Thank you!
You can use a show variable as toggler to show/hide the weight component like this
import React, { useState } from "react";
const App = () => {
const [weight, setWeight] = useState([1, 2, 3]);
const [show, setShow] = useState(false);
return (
<div className="container">
<div className="weight">{show && weight.map((e) => <Weight />)}</div>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
</div>
);
};
export default App;
I thing you need to return the mapped array
const renderWeight = () => {
return weight.map((e) => {
<Weight />;
});
};
note: Make sure to assign a key to each component rendered
You have to return it.
const [show ,setShow] = useState(false)
const renderWeight = () => {
return weight.map((e) => {
return <Weight />;
});
};
return (
<>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
{
show?
renderWeight():''
}
</>
);

Get value from response and transfer to another component in React

I have this handleSubmit that returns me a key (verifyCode) that I should use in another component. How can I pass this verifyCode to another component?
const SendForm = ({ someValues }) => {
const handleSubmitAccount = () => {
dispatch(createAccount(id, username))
.then((response) => {
// I get this value from data.response, its works
const { verifyCode } = response;
})
.catch(() => {
});
};
return(
//the form with handleSubmitAccount()
)
}
export default SendForm;
The other component is not a child component, it is loaded after this submit step. But I don't know how to transfer the const verifyCode.
This is the view where the components are loaded, it's a step view, one is loaded after the other, I need to get the const verifyCode in FormConfirmation
<SendForm onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
<FormConfirmation onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
Does anyone know how I can do this?
You need to move up the state to a component that has both as children and then pass down a function that updates as a prop
import React from "react";
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
<Updater onClick={() => setValue(value + 1)} />
<ValueDisplay number={value} />
</div>
);
}
const Updater = (props) => <div onClick={props.onClick}>Update State</div>;
const ValueDisplay = (props) => <div>{props.number}</div>;
Check out the docs here
For more complex component structures or where your passing down many levels you may want to look into reactContext
import React from "react";
//Set Default Context
const valueContext = React.createContext({ value: 0, setValue: undefined });
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
{/** Pass in state and setter as value */}
<valueContext.Provider value={{ value: value, setValue }}>
<Updater />
<ValueDisplay />
</valueContext.Provider>
</div>
);
}
const Updater = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return (
<div onClick={() => context.setValue(context.value + 1)}>Update State</div>
);
};
const ValueDisplay = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return <div>{context?.value}</div>;
};

Why does the component not re-render after callback?

Given the following two components, I expect the EntryList component to re-render after the state changes in the handleEnttryDelete after the button in EntryForm is clicked. Currently the state changes, but the UI isn't updating itself:
import React, { useState } from "react";
import Button from "#material-ui/core/Button";
import { render } from "#testing-library/react";
const EntryList = (props) => {
const [entryList, setEntryList] = useState(props.data);
const handleEntryDelete = (entry) => {
const newState = entryList.filter(function (el) {
return el._id != entry._id;
});
setEntryList(() => newState);
};
return (
<div>
{entryList.map((entry) => {
return (
<EntryForm entry={entry} handleEntryDelete={handleEntryDelete} />
);
})}
</div>
);
};
const EntryForm = (props) => {
const [entry, setEntry] = useState(props.entry);
return (
<div>
<Button onClick={() => props.handleEntryDelete(entry)}>
{entry._id}
</Button>
</div>
);
};
export default EntryList;
Your code probably works, but not as intended. You just have to use key while mapping arrays to components.
Therefore, React can distinguish which elements should not be touched during reconciliation when you delete one of the nodes
<div>
{entryList.map((entry) => {
return <EntryForm key={entry._id} entry={entry} handleEntryDelete={handleEntryDelete} />;
})}
</div>;

Categories

Resources