I am trying to change the ckeckbox property, every time I write a new value to the map, in my case when I click on the checkbox, it only changes the value of the checked property, for example, the initial value is true, on all subsequent clicks it will be false, false, false ... What's wrong here?
import React,{ useState,useEffect } from "react";
import {useSelector} from 'react-redux'
const ChangeArticle = (props) => {
const prevArticle = props.changeArtcle;
const age_groups = useSelector(state => state.app.age_groups);
const [checkedAges, setCheckAges] = useState(new Map());
const handleChangeCheckBoxe = (event) => {
setCheckAges(checkedAges => checkedAges.set(event.target.value, event.target.checked));
console.log("checkedItems: ", checkedAges);
}
useEffect(() => {
if(prevArticle.ages){
prevArticle.ages.forEach((age) =>{
setCheckAges(checkedAges => checkedAges.set(age.toString(), true));
});
}
},[prevArticle]);
return (<div>
{age_groups.map(age => {
return (<div key={age.id}>
<input type="checkbox" checked={checkedAges.has(age.id.toString()) ? checkedAges.get(age.id.toString()) : false} value={age.id} onChange={handleChangeCheckBoxe}
/>
{ age.title }
</div>)
}) }
</div>);
}
export default ChangeArticle;
In the handleChangeCheckBoxe function, you are only changing the values withing the Map. React only does a shallow reference check to see if the Map had changed. Since the reference is the same, the it will not re-render the component as you would expect.
You can change the function to be similar to the following to create a new Map and assign it to state.
const handleChangeCheckBoxe = (event) => {
setCheckAges(checkedAges => new Map(checkedAges.set(event.target.value, event.target.checked)));
console.log("checkedItems: ", checkedAges);
}
You can see this working in the following code sandbox https://codesandbox.io/s/wispy-leftpad-9sji0?fontsize=14&hidenavigation=1&theme=dark
Related
I am building an application that has a database of videos that can be filtered by category and sorted by rating.
Filtering works after changing the options. However, when I change the categories of the video the filtering does not start automatically. I added useEffect but I don't know what else I can change and why it happens. Please help how to make the sorting not disappear when changing the cateogry.
UPDATE:
import * as _ from "lodash";
import { useEffect, useState } from "react";
import { getAllPrograms } from "../../helpers/getData";
import { TVProgram } from "../../models/models";
import Filters from "../Filters/Filters";
import ProgramsList from "../ProgramsList/ProgramsList";
import Sorting from "../Sorting/Sorting";
import "./HomePage.scss";
const HomePage = () => {
const [programs, setPrograms] = useState<Array<TVProgram>>([]);
const [category, setCategory] = useState<string>("movie,series");
const [sortedPrograms, setSortedPrograms] = useState<TVProgram[]>(programs);
const getPrograms = async (category: string) => {
const programs = await getAllPrograms(category);
setPrograms(programs);
};
useEffect(() => {
getPrograms(category);
}, [category]);
const updateCategory = (categoryName: string): void => {
setCategory(categoryName);
console.log("catName", categoryName);
};
const updatePrograms = (sortedPrograms: TVProgram[]): void => {
setSortedPrograms(sortedPrograms);
console.log("sortedPrograms", sortedPrograms);
};
return (
<div className="container">
<div>
<Filters
updateCategory={updateCategory}
currentCategory={category}
></Filters>
<Sorting programs={programs} setPrograms={updatePrograms}></Sorting>
</div>
<ProgramsList programs={sortedPrograms}></ProgramsList>
</div>
);
};
export default HomePage;
import _ from "lodash";
import { ChangeEvent, useEffect, useState } from "react";
import { sortProgramsByOrder } from "../../helpers/helpers";
import { TVProgram } from "../../models/models";
import "./Sorting.scss";
interface SortingListProps {
programs: TVProgram[];
setPrograms: (programs: TVProgram[]) => void;
}
const Sorting = ({ programs, setPrograms }: SortingListProps) => {
const OPTIONS = ["imdb rating descending", "imdb rating ascending"];
const [selectedOption, setSelectedOption] = useState<string>("");
const [sortedPrograms, setSortedPrograms] = useState<TVProgram[]>([]);
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
setSortedPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
setSortedPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption, programs]);
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
console.log();
setSelectedOption(event.target.value);
setPrograms(sortedPrograms);
};
return (
<div>
<select value={selectedOption} onChange={handleChange}>
<option selected>Sortuj</option>
{OPTIONS.map((option) => (
<option
key={option}
value={option}
selected={option === selectedOption}
>
{option}
</option>
))}
</select>
</div>
);
};
export default Sorting;
useEffect() is a hook that prevents updates to a variable except in specific cases. Any variable passed into the array at the end of the useEffect() hook will cause the code inside to be run again when its value changes. The problem looks, at first glance, to be in the following part of your code:
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
sortPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
sortPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption]);
The [selectedOption] is telling the hook to only do the sorting if the sorting order has changed. However, you want to call this hook if the order or the contents changes. As such, you want to replace this array with [selectedOption, programs] so that changes to the contents of the programs variable will also lead to the sorting being re-run.
If programs is updated in the hook and also set by the hook, this leads to a recursive call which is not good. Instead, let's change the displayed value to be a new variable (defined with useState) called sortedPrograms. Then your hook should look like this:
useEffect(() => {
if (selectedOption === OPTIONS[0]) {
setSortedPrograms(sortProgramsByOrder(programs, "desc"));
} else if (selectedOption === OPTIONS[1]) {
setSortedPrograms(sortProgramsByOrder(programs, "asc"));
}
}, [selectedOption, programs]);
I am building a form where the hotel owners will add a hotel and select a few amenities of the same hotel. The problem is If I use state in the onChange function the checkbox tick is not displayed. I don't know where I made a mistake?
import React from "react";
import { nanoid } from "nanoid";
const ListAmenities = ({
amenities,
className,
setHotelAmenities,
hotelAmenities,
}) => {
const handleChange = (e) => {
const inputValue = e.target.dataset.amenitieName;
if (hotelAmenities.includes(inputValue) === true) {
const updatedAmenities = hotelAmenities.filter(
(amenitie) => amenitie !== inputValue
);
setHotelAmenities(updatedAmenities);
} else {
//If I remove this second setState then everything works perfectly.
setHotelAmenities((prevAmenities) => {
return [...prevAmenities, inputValue];
});
}
};
return amenities.map((item) => {
return (
<div className={className} key={nanoid()}>
<input
onChange={handleChange}
className="mr-2"
type="checkbox"
name={item}
id={item}
data-amenitie-name={item}
/>
<label htmlFor={item}>{item}</label>
</div>
);
});
};
export default ListAmenities;
The problem is that you are using key={nanoid()}. Instead, using key={item] should solve your probem.
I believe your application that uses ListAmenities is something like this:
const App = () => {
const [hotelAmenities, setHotelAmenities] = useState([]);
return (
<ListAmenities
amenities={["A", "B", "C"]}
className="test"
setHotelAmenities={setHotelAmenities}
hotelAmenities={hotelAmenities}
/>
);
};
In your current implementation, when handleChange calls setHotelAmenities it changed hotelAmenities which is a prop of ListAmenities and causes the ListAmenities to rerender. Since you use key={nanoid()} react assumes that a new item has been added and the old one has been removed. So it re-renders the checkbox. Since there is no default value of checkbox, it is assumed that it is in unchecked state when it is re-rendered.
I have this component:
import "./styles.css";
import React, { useEffect, useRef, useState } from "react";
import JSONEditor from "jsoneditor";
import "jsoneditor/dist/jsoneditor.css";
const JSONReact = ({ json, mode, onChange }) => {
const ref1 = useRef(null);
const ref2 = useRef(null);
useEffect(() => {
const props = {
onChangeText: (value) => {
console.log(value, "vv");
onChange(value);
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
if (json) {
ref1.current.set(json);
}
return () => {
ref1.current.destroy();
};
}, []);
useEffect(() => {
if (json) {
ref1?.current.update(json);
}
}, [json]);
return <div ref={ref2} />;
};
export default function App() {
const [state, setState] = useState('{"cars": "22w-08w-23"}');
const onChange = (j) => {
console.log(j);
setState(j);
};
return (
<div className="App">
<JSONReact mode="code" onChange={onChange} json={JSON.parse(state)} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
When i type something inside editor i get undefined in console.log(j);, but i don't understand why. Who can help to fix it?
https://codesandbox.io/s/admiring-khorana-pdem1l?file=/src/App.js:909-929
Well it is expected as in the docs it clearly says "This callback does not pass the changed contents", over here https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#:~:text=This%20callback%20does%20not%20pass%20the%20changed%20contents
However, There are two ways to do it, that I found from here
https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#configuration-options
First Way: use getText() from JSONEditor object to get the current value, since it will return you string so just parse it in json and pass inside your callback (onChange) prop.
JavaScript
const props = {
onChange: () => {
onChange(JSON.parse(ref1.current.getText()));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
Second way: just use onChangeText and it will give the string in the callback,
const props = {
onChangeText: (value) => {
onChange(JSON.parse(value));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
EDIT 1: added JSON validation check before calling onChange Props
const isValidJSON = (jsonString) => {
try {
JSON.parse(jsonString);
} catch (e) {
return false;
}
return true;
};
useEffect(() => {
const props = {
onChangeText: (value) => {
isValidJSON(value) && onChange(JSON.parse(value));
},
modes: ["code"]
};
ref1.current = new JSONEditor(ref2.current, props);
EDIT 2: adding error validation using validate(), here in this case we can remove isValidJSON(value) check from below code since we are already checking for JSON errors using validate().
onChangeText: (value) => {
const errors = ref1.current.validate();
errors.then((err) => {
if (!err.length) {
isValidJSON(value) && onChange(JSON.parse(value));
} else {
console.log(err);
}
});
}
I think it is a normal behaviour of JSONEditor, according to docs:
{function} onChange()
Set a callback function triggered when the contents of the JSONEditor
change. This callback does not pass the changed contents, use get() or
getText() for that. Note that get() can throw an exception in mode
text, code, or preview, when the editor contains invalid JSON. Will
only be triggered on changes made by the user, not in case of
programmatic changes via the functions set, setText, update, or
updateText. See also callback functions onChangeJSON(json) and
onChangeText(jsonString). `
Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.
I am working on a component where the user searches a term and it is returned to them through a filter. I am using useContext hook to pass data from db via axios. I would like to use the button in the CompSearch component to render the results rather than having them show up on a keystroke. My question is how do I render the results via button click?
Here is the code
Follow these steps to achieve that.
Change the input element into an uncontrolled component.
Get the value using reference in React.
import React, { useContext, useRef, useState } from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
const [company, setCompany] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = company.filter((res) => {
return res.company.toLowerCase().includes(searchField.toLowerCase());
});
const inputRef = useRef(null); // 1. Create the ref
const handleClick = () => {
const val = inputRef.current.value; // 3. Get the value
setSearchField(val);
if (val === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
const searchList = () => {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
};
return (
<>
<div>
<em>
NOTE: if you search "ABC" or "EFGH" you will see results - my goal is
to see those results after clicking button
</em>
<br />
<input
type="search"
placeholder="search Company Title"
ref={inputRef} {/* 2. Assign the ref to the Input */}
/>
<button onClick={handleClick}>Enter</button>
</div>
{searchList()}
</>
);
};
export default CompSerach;
https://codesandbox.io/s/show-on-button-click-68157003-rot6o?file=/src/TestTwo/CompSearch.js
Let me know if you need further support.
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
**setCompany(e.target.value);**
};
i think your question is similar with autocomplete.