I have a container with boxes (divs), and inside these Boxes there's N checkboxes. The boxes and the checks are fetched from a server and listed on screen. I need to Control the checkboxes state to set a checked according a other fetch. How can i do that?
Edit:
I did a fetch to get the box (modules). Each module have an array of checkboxes (that i call features):
modules = [{
id,
name,
features: [{
id,
value
}]
}
]
i created a new state 'checkModules' to control the 'checked' propertie from checkbox. When i do the fetchModules, i set the checkModules according to the fetched data.
const PermissionsPage = () => {
const [modules, setModules] = useState([]);
const [checkModules, setCheckModules] = useState([]); //state to control the checkboxes 'checked' propertie
useEffect(() => {
api.get('/modules').then(res => {
setModules(res.data.modules);
setCheckModules(
res.data.modules.map(m => ({
id: m.id,
features: m.features.map(f => ({ id: f.id, checked: false }))
}))
);
}
});
const handleChange = (m,f,e) => {
let temp = checkModules;
//the propertie is changed, but the checkbox don't 'check'.
temp[m].features[f].checked = !temp[m].features[f].checked;
setCheckModules(temp);
}
return (){
<div>
{modules.map((module, m) => (
<div>
<span>{module.name}</span>
{module.features.map((feature, f) => (<checkbox onChange={() => handleChange(m,f)} checked={checkModules[m] !== undefined ? checkModules[m].features[f].checked : false}/>))}
</div>
)}
</div>
}
}
i tried to reproduce the code simplifying the use of components =).
I solve it. I'll post the changes =).
const PermissionsPage = () => {
const [modules, setModules] = useState([]);
const [checkModules, setCheckModules] = useState([]); //state to control the checkboxes 'checked' propertie
useEffect(() => {
api.get('/modules').then(res => {
setModules(res.data.modules);
setCheckModules(
res.data.modules.map(m => ({
id: m.id,
name: m.name,
features: m.features.map(f => ({ id: f.id, name: f.name, checked: false }))
}))
);
}
});
const handleChange = (m,f,e) => {
let temp = [...checkModules]; //that's the point. You need to take the state as a new array;
temp[m].features[f].checked = !temp[m].features[f].checked;
setCheckModules(temp);
}
return (){
<div>
{checkModules.map((module, m) => (
<div>
<span>{module.name}</span>
{checkModules[m].features.map((feature, f) => (<checkbox onChange={() => handleChange(m,f)} checked={feature.checked}/>))}
</div>
)}
</div>
}
}
Related
I would like to update text which is displayed inside a <div> element. I would love to do it when the cursor enters the <div> element.
Basically I'm fetching some data from the API and I display only one parameter (name). If a user enters the <div> with the name displayed I would like to show some details, i.e. description and price.
This is my code which I tried to complete my task.
import {useEffect, useState} from "react";
import requestOptionsGet from "../utilities/requestOptions";
import validateResponse from "../utilities/validators";
const Warehouse = () => {
const [items, setItems] = useState([]);
const [texts, setTexts] = useState([]);
const getItems = async () => {
const url = "http://localhost:8000/api/items/"
return await fetch(url, requestOptionsGet)
.then((response) => validateResponse(response, url))
.then((response) => response.json())
.then((data) => setItems(data))
};
useEffect(() => {
getItems();
}, []);
useEffect(() => {
setTexts(items.map((item) => (
{
id: item.id,
name: item.name,
description: item.description,
price: item.price,
currentDisplay: <h2>{item.name}</h2>,
})
))
}, [items]);
const displayName = (data) => {
console.log(
"displayName"
);
};
const displayDetails = (data) => {
const itemID = parseInt(data.currentTarget.getAttribute("data-item"));
const displayInfo = texts.find(text => text.id === itemID);
displayInfo.currentDisplay = <p>{displayInfo.description}</p>
setTexts(texts);
console.log(texts);
console.log(
"displayDetails"
);
return(
displayInfo.currentDisplay
);
};
return(
<div className="container">
<h1>Your warehouse.</h1>
<h2>All your items are listed here.</h2>
<hr />
{texts.map((text) => (
<button className="container-for-single-item" id={text.id} key={text.id}
onMouseEnter={displayDetails} onMouseLeave={displayName} data-item={text.id}>
{text.currentDisplay}
</button>
))}
</div>
);
}
export default Warehouse;
The functions work (everything is displayed in the console as it should be) and even the texts change. However the paragraph does not appear. How can I fix my code? Thanks!
Never modify state directly
const newTexts = texts.map(text => text.id === itemID ? { ...text, currentDisplay: <p>{text.description}</p> } : text);
setTexts(newTexts);
I want to create a function that will color the hearts when clicked.
I wrote a function that prints out elements for me, but when I click on any heart, it colors them all.
Where could the problem be?
My code:
const \[userInput, setUserInput\] = useState("");
const \[list, setList\] = useState(\[\]);
const \[hearth, setHearth\] = useState(false);
const \[active, setActive\] = useState(-1);
const handleChange = (e) =\> {
e.preventDefault();
setUserInput(e.target.value);
};
const handleSubmit = (e) =\> {
e.preventDefault();
setList(\[userInput, ...list\]);
setUserInput("");
};
const wishList = (e) =\> {
setHearth(!hearth);
};
useEffect(() =\> {}, \[userInput, list\]);
return (
\<div className="favMusic"\>
<h1>FavMusicList</h1>
\<form\>
\<input value={userInput} onChange={handleChange} type="text" /\>
\<button onClick={handleSubmit}\>Submit\</button\>
\</form\>
<ul className="favMusic__list">
{list.map((i, idx) => {
console.log(idx);
return (
<li key={idx}>
{i}{" "}
<div
id={idx}
onClick={() => wishList(idx)}
className={"hearth" + " " + (hearth ? "true" : "false")}>
<AiOutlineHeart
/>
</div>
</li>
);
})}
</ul>
</div>
I have tried all possible ways from setState to others found on the net but I have no idea how to solve it
Here's a working demo.
Assuming your state data is an array of items, each with its own boolean property indicating whether it's been "liked" by the user:
[
{
id: 1,
liked: true,
title: 'ListItem 1',
},
{
id: 2,
liked: false,
title: 'ListItem 2',
},
// ...
]
Then in your click handler, you'd want to loop over each of the objects to find the item with the corresponding id to change just the boolean property for that one item. For example:
const handleClick = (id) => {
const newLikes = items.map((item) => {
// check the current element's id against the
// id passed to the handler
if (item.id === id) {
// if it matches, update the liked property
// and return the modified object
return { ...item, liked: !item.liked };
}
// if it doesn't match, just return the
// original object
return item;
});
// update state with the new data
setItems(newLikes);
};
So this is my app which i have created in react and store data in firestore, i have a form in which ingredients is an array, i can dynamically add the input feilds and when i submit the form it gets submiited and the data gets stored in firebase. My problem is when i click the add feild button instead of one feild two feilds are simultaneously created and i am unable to understand how to do that so if anyone can explain what to do thanks .
code :
function App() {
const [recipes, setRecipes] = useState([])
const [form, setForm] = useState({
ingredients: [],
})
const [popupActive, setPopupActive] = useState(false)
const recipesCollectionRef = collection(db, "recipes")
useEffect(() => {
onSnapshot(recipesCollectionRef, snapshot => {
setRecipes(snapshot.docs.map(doc => {
return {
id: doc.id,
viewing: false,
...doc.data()
}
}))
})
}, [])
const handleView = id => {
const recipesClone = [...recipes]
recipesClone.forEach(recipe => {
if (recipe.id === id) {
recipe.viewing = !recipe.viewing
} else {
recipe.viewing = false
}
})
setRecipes(recipesClone)
}
const handleSubmit = e => {
e.preventDefault()
if (
!form.ingredients ||
) {
alert("Please fill out all fields")
return
}
addDoc(recipesCollectionRef, form)
setForm({
ingredients: [],
})
setPopupActive(false)
}
const handleIngredient = (e, i) => {
const ingredientsClone = [...form.ingredients]
ingredientsClone[i] = e.target.value
setForm({
...form,
ingredients: ingredientsClone
})
}
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, ""]
})
{ recipe.viewing && <div>
<h4>Ingredients</h4>
<ul>
{ recipe.ingredients.map((ingredient, i) => (
<li key={i}>{ ingredient }</li>
))}
</ul>
As far as I have understood. Just do like below,
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, "", ""],
})
}
You will be created with two input fields simultaneously instead of one when you click the add ingredient button.
How can I retrieve the dishId selected from my options react - select that shows me thedishType in order to send them my parent component FormRender and to the backend.
My first dropdown shows me: Menu1, Menu2...
My second one: type2...
So if I click ontype4, how can I store the related dishId(here = 4). I can click on several values i.e: type2 andtype3.
How do I keep the dish ids i.e : 2 and 3 and send them to my FormRender parent
Menus(first page of my multi - step form):
export default function Menus() {
const [selectionMenus, setSelectionMenus] = useState({});
const [selectionDishes, setSelectionDishes] = useState({});
const [menus, setMenus] = useState([])
const [date, setDate] = useState('')
useEffect(() => {
axios
.post(url)
.then((res) => {
console.log(res);
setMenus(res.data.menus);
})
.catch((err) => {
console.log(err);
});
}, []);
const names = menus?.map(item => {
return {
label: item.menuId,
value: item.name
}
})
const types = menus?.flatMap(item => {
return item.dishes.map(d => ({
label: d.dishId,
value: d.dishType
}))
})
const handle = (e) => {
if (e?.target?.id === undefined) return setInfo(e);
if (e?.target?.id === undefined) return setSelectionMenus(e);
if (e?.target?.id === undefined) return setSelectionDishes(e);
switch (e.target.id) {
case "date":
setDate(e.target.value);
break;
...
default:
}
}
};
return (
<>
<form>
<div>My menus</div>
<label>
Dishes :
<Dropdown
options={names}
value={selectionMenus}
setValue={setSelectionMenus}
isMulti={true}
/>
</label>
<label>
<Dropdown
options={types}
value={selectionDishes}
setValue={setSelectionDishes}
isMulti={true}
/>
</label>
<label>
Date:
<div>
<input
type="date"
name='date'
value={date}
onChange={handle}
id="date"
/>
</div>
</label>
...
</form>
<div>
<button onClick={() => nextPage({ selectionDishes, selectionMenus, date })}>Next</button>
</div>
</>
);
}
Here the parent Component FormRender that is supposed to retrieve the values of all dishId selected and send them to the backend:
export default function FormRender() {
const [currentStep, setCurrentStep] = useState(0);
const [info, setInfo] = useState();
const [user, setUser] = useState();
const headers = ["Menus", "Details", "Final"];
const steps = [
<Menus
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>,
<Details
backPage={() => setCurrentStep((s) => s - 1)}
nextPage={setUser}
/>,
<Final />
];
useEffect(() => {
if (info === undefined || user === undefined) return;
const data = {
date: info.date,
id: //list of dishId selected but don't know how to do that??
};
}, [info, user]);
return (
<div>
<div>
<Stepper steps={headers} currentStep={currentStep} />
<div >{steps[currentStep]}</div>
</div>
</div>
);
}
Dropdown:
export default function Dropdown({ value, setValue, style, options, styleSelect, isMulti = false }) {
function change(option) {
setValue(option.value);
}
return (
<div onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
value={value}
onChange={change}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
Here my json from my api:
{
"menus": [
{
"menuId": 1,
"name": "Menu1",
"Description": "Descritption1",
"dishes": [
{
"dishId": 2,
"dishType": "type2"
},
{
"dishId": 3,
"dishType": "type3"
},
{
"dishId": 4,
"dishType": "type4"
}
]
},
...
]
}
You already store the selected values inside the selectionMenus and selectionDishes states. So, if you want to send them to the parent FormRender component you can instead create those two states inside that component like this:
export default function FormRender() {
const [selectionMenus, setSelectionMenus] = useState();
const [selectionDishes, setSelectionDishes] = useState();
....
}
Then pass those values to the Menus component:
<Menus
selectionMenus={selectionMenus}
setSelectionMenus={setSelectionMenus}
selectionDishes={selectionDishes}
setSelectionDishes={setSelectionDishes}
nextPage={(menu) => {
setInfo(menu);
setCurrentStep((s) => s + 1);
}}
/>
Subsequently, you will have to remove the state from the Menus component and use the one you receive from props:
export default function Menus({ selectionMenus, setSelectionMenus, selectionDishes, setSelectionDishes }) {
/*const [selectionMenus, setSelectionMenus] = useState({}); //remove this line
const [selectionDishes, setSelectionDishes] = useState({});*/ //remove this line
...
}
Finally, you can use inside your useEffect hook the two states and map them to only get the selected ids:
useEffect(() => {
// ... other logic you had
if(selectionDishes?.length && selectionMenus?.length){
const data = {
date: info.date,
id: selectionDishes.map(d => d.dishId),
idMenus: selectionMenus.map(m => m.menuId)
};
}
}, [info, user, selectionMenus, selectionDishes]);
react-select has options to format the component:
getOptionLabel: option => string => used to format the label or how to present the options in the UI,
getOptionValue: option => any => used to tell the component what's the actual value of each option, here you can return just the id
isOptionSelected: option => boolean => used to know what option is currently selected
onChange: option => void => do whatever you want after the input state has changed
value => any => if you customize the above functions you may want to handle manually the value
Hope it helps you
I'm building an app using react, redux, and redux-saga.
The situation is that I'm getting information from an API. In this case, I'm getting the information about a movie, and I will update this information using a basic form.
What I would like to have in my text fields is the value from the object of the movie that I'm calling form the DB.
This is a brief part of my code:
Im using 'name' as an example.
Parent component:
const MovieForm = (props) => {
const {
movie,
} = props;
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({
name,
});
};
const handleSetValues = () => {
console.log('hi');
console.log(movie, name);
setName(movie.name);
setValues(true);
};
useEffect(() => {
if (movie && values === false) {
handleSetValues();
}
});
return (
<Container>
<TextField
required
**defaultValue={() => {
console.log(movie, name);
return movie ? movie.name : name;
}}**
label='Movie Title'
onChange={(e) => setName(e.target.value)}
/>
</Container>
);
};
export default MovieForm;
....
child component
const MovieUpdate = (props) => {
const { history } = props;
const { id } = props.match.params;
const dispatch = useDispatch();
const loading = useSelector((state) => _.get(state, 'MovieUpdate.loading'));
const created = useSelector((state) => _.get(state, 'MovieUpdate.created'));
const loadingFetch = useSelector((state) =>
_.get(state, 'MovieById.loading')
);
const movie = useSelector((state) => _.get(state, 'MovieById.results'));
useEffect(() => {
if (loading === false && created === true) {
dispatch({
type: MOVIE_UPDATE_RESET,
});
}
if (loadingFetch === false && movie === null) {
dispatch({
type: MOVIE_GET_BY_ID_STARTED,
payload: id,
});
}
});
const updateMovie = (_movie) => {
const _id = id;
const obj = {
id: _id,
name: _movie.name,
}
console.log(obj);
dispatch({
type: MOVIE_UPDATE_STARTED,
payload: obj,
});
};
return (
<div>
<MovieForm
title='Update a movie'
buttonTitle='update'
movie={movie}
onCancel={() => history.push('/app/movies/list')}
onSubmit={updateMovie}
/>
</div>
);
};
export default MovieUpdate;
Then, the actual problem is that when I use the default prop on the text field the information appears without any problem, but if i use defaultValue it is empty.
Ok, I kind of got the answer, I read somewhere that the defaultValue can't be used int the rendering.
So I cheat in a way, I set the properties multiline and row={1} (according material-ui documentation) and I was able to edit this field an receive a value to display it in the textfield