I am using TreePicker & CheckTreePicker from rsuite package.
I would like to clear the selections programmatically for the tree picker when certain props value is changed. I am able to trigger event in the useEffect when value of selectItemchanges , and I would like to clear all the current selections for treePicker just after it.
const Categories = ({ selectItem }) => {
useEffect(() => {
// INCLUDE LOGIC HERE TO RESET ALL THE FILTERS WHEN the value of selectItem change
}, []);
const handleCategFilters = (value) => {
console.log("do something here with value", value);
};
return (
<CheckTreePicker
data={pickerDT}
onChange={(i) => {
handleCategFilters(i);
}}
/>
);
};
I appreciate yur help. Thank you.
You can manually control the value
const Categories = ({ selectItem }) => {
const [value, setValue] = React.useState([]);
useEffect(() => {
// INCLUDE LOGIC HERE TO RESET ALL THE FILTERS WHEN the value of selectItem change
setValue([]);
}, []);
const handleCategFilters = (value) => {
console.log("do something here with value", value);
};
return (
<CheckTreePicker
data={pickerDT}
value={value}
onChange={(i) => {
handleCategFilters(i);
}}
/>
);
};
Related
Suppose I have a list of items I would like to render and select (like a Todo app).
I'd like to keep the selection logic inside custom react hook and have items live somewhere else in local state.
Now, I would like to update the selection list, kept in the custom hook, whenever I fetch some more items. For this task I am passing data as parameter to selection hook and I am using useEffect to update the selection:
import { useEffect, useState } from "react";
const itemsArrayToObject = (items) =>
Object.fromEntries(items.map((i) => [i.id, { ...i, selected: false }]));
export function useSelection({ data }) {
const [selection, setSelection] = useState(itemsArrayToObject(data));
useEffect(() => {
setSelection((selection) => {
return {
...itemsArrayToObject(data),
...selection
};
});
}, [data]);
const isSelected = (itemId) => selection?.[itemId]?.selected ?? false;
const toggle = (itemId) => {
setSelection((s) => {
const item = s[itemId];
return {
...s,
[itemId]: {
...item,
selected: !item.selected
}
};
});
};
return {
isSelected,
toggle
};
}
This almost works but the problem is if I want to synchronize two things: fetching data and toggling items. Eg.
const onLoadAndToggle = async () => {
await load();
toggle(0);
};
load is a async function that fetches the data. It also triggers state update so that data is updated and the selection can be updated inside useSelection hook.
Example how it all can work:
const [data, setData] = useState([]);
const addItems = (items) => {
setData((state) => [...state, ...items]);
};
const { load } = useFetch({ addItems });
const { isSelected, toggle } = useSelection({ data });
const onLoadAndToggle = async () => {
await load();
toggle(0);
};
Now, the problem is that when calling toggle(0) my custom hook has a stale selection, even when using setState(state => ... singature.
It is because the whole fetching and updating data in state takes too long.
I can see some ugly ways to solve that problem but I wonder what would be the elegant or idiomatic react way to solve that.
I have made a code sandbox, if it helps: https://codesandbox.io/s/selection-fetch-forked-nyl0kt?file=/src/App.js:376-512
Try clicking "Load and toggle first" first to see how the app crashed because the selection is not yet updated.
What you need is to initialize toogled items from the code itself. We can do this by providing the id's of the items that we want to toggle to the hook itself.
Updated hook -
const itemsArrayToObject = (items, itemsToggled) => {
if (Array.isArray(itemsToggled)) {
return Object.fromEntries(
items.map((i) => [i.id, { ...i, selected: itemsToggled.includes(i.id) }])
);
}
return Object.fromEntries(
items.map((i) => [i.id, { ...i, selected: false }])
);
};
export function useSelection({ data }, itemsToggled) {
const [selection, setSelection] = useState(
itemsArrayToObject(data, itemsToggled)
);
useEffect(() => {
setSelection((selection) => {
return {
...itemsArrayToObject(data, itemsToggled),
...selection
};
});
}, [data, itemsToggled]);
Now call to hook becomes -
const { isSelected, toggle } = useSelection({ data }, [0, 1]);
Updated codesandbox
This also decouples loading data & toggling of an item initially.
I'm working on a hotel feature where the user can filter through and display the corresponding rooms available, however when I set the onClick to update the filters and display the filtered rooms, the rooms display correctly after the second click and there after.
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
filterRooms();
};
const filterRooms = () => {
....
....
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useState() (and class component's this.setState()) are asynchronous, so your second state updater won't have an up to date value for filtered_rooms when it runs.
Rather than:
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
const someHandler = e => {
setSomeState(...);
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
};
You need to setSomeOtherState within a useEffect hook, and ensure to mark some_state as a dependency.
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
useEffect(() => {
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
}, [some_state]);
const someHandler = e => {
setSomeState(...);
};
It is hard to give an suggestion for your code since it is fairly edited, but it'd probably look like this:
const filterRooms = () => {
// ...
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useEffect(() => {
filterRooms();
}, [filtered_rooms]);
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
};
See this codepen for a simple (albeit a bit contrived) example.
I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}
Im new to programming, just a junior for now.
I have list of checkboxes. And when I uncheck a checkbox, I want unchecked checkbox to be saved in my local storage. Here is my code snipped, what im doing wrong? Cant fully understand what i wrote. Thank you
export default () => {
const [isDefaultChecked, setDefaultChecked] = useState(true);
const [isChecked, setChecked] = useState();
const [isColumn, setColumn] = useState(true);
const [hiddenColumns, setHiddenColumns] = useState([]);
const [Checked, setIsChecked] = useState([]);
const onCheckboxChange = (key: string, value: boolean) => {
const array = JSON.parse(localStorage.getItem("hiddenColumns"));
// here im trying to check if value = to
if (value) {
const column = hiddenColumns.find((item) => item.value == key);
const filtered = hiddenColumns.filter(
(item) => item.key !== column.key
)
setHiddenColumns(filtered);
if (!value) {
setHiddenColumns([...hiddenColumns, { 'label': key } ]);
}
localStorage.setItem("hiddenColumns", JSON.stringify(key));
}
};
return(
<div>
<Checkbox
defaultChecked={isDefaultChecked}
label="Delivery Methods"
onChange={(value) => onCheckboxChange("delieveryMethods", value)}
/>
</div>
)
if (!value) {
setHiddenColumns([...hiddenColumns, { 'label': key } ]);
}
localStorage.setItem("hiddenColumns", JSON.stringify(key));
You are saving a single key to localStorage, to be consistent with your code, you must save the entire array again.
Now, you hiddenColumns array seems to be a collection of { label } where label is the key.
Also, it stores the values that are hidden only. So, on your onCheckboxChange, if value is true, it means that you should add the key of the checkbox to the array and if value is false, you should take it out. The following codes achieves that
const onCheckboxChange = (key: string, value: boolean) => {
if (value) setHiddenColumns(hiddenColumns => [...hiddenColumns, { label: key }]);
else setHiddenColumns(hiddenColumns.filter(x => x.label !== key));
};
after that, you can add a useEffect hook that would be triggered when hiddenColumns array changes that would maintain your localStorage sync with hiddenColumns array
React.useEffect(() => {
localStorage.setItem("hiddenColumns", JSON.stringify(hiddenColumns));
}, [hiddenColumns]);
The ReactSelect component in my app is clearing what I have typed after I make a selection. I don't want it to do that.
I want it to leave it as is. When the user has not entered anything, I want it to show the Placeholder text.
I'm using this component as a search box. Selecting an item from the list accomplishes the task. There's no reason to set the component's value to the selection.
react-select does not support autocompletion out of the box so it requires a bit of extra work and hacks to get what you want.
First off, you need to control the inputValue state, some operations like menu-close or set-value will clear the input afterward.
const [input, setInput] = React.useState("");
const handleInputChange = (e, meta) => {
if (meta.action === "input-change") {
setInput(e);
}
};
return (
<Select
value={selected}
onChange={handleChange}
inputValue={input}
isSearchable
{...}
/>
);
react-select also hides the input after an option is selected so you also need to override that behavior as well.
const [selected, setSelected] = React.useState([]);
const handleChange = (s) => {
setSelected({ ...s });
};
React.useLayoutEffect(() => {
const inputEl = document.getElementById("myInput");
if (!inputEl) return;
// prevent input from being hidden after selecting
inputEl.style.opacity = "1";
}, [selected]);
return (
<Select
value={selected}
inputId="myInput"
onChange={handleChange}
{...}
/>
);
Last but not least, you may want to update your input accordingly after a successful selection, here is a basic example, the code below will append the new option value to your current input after a selection.
Also make sure to override the default filterOption to only taking into account the last word when filtering or nothing will match with the options after the first few words.
const [selected, setSelected] = React.useState([]);
const [input, setInput] = React.useState("");
const handleChange = (s) => {
setSelected({ ...s });
setInput((input) => removeLastWord(input) + s.value);
};
const customFilter = () => {
return (config, rawInput) => {
const filter = createFilter(null);
return filter(config, getLastWord(rawInput));
};
};
return (
<Select
value={selected}
filterOption={customFilter()}
onChange={handleChange}
inputValue={input}
components={{
SingleValue: () => null
}}
{...}
/>
);
Where removeLastWord and getLastWord are just some utility functions.
function getLastWord(str: string) {
return str.split(" ").slice(-1).pop();
}
function removeLastWord(str: string) {
var lastWhiteSpaceIndex = str.lastIndexOf(" ");
return str.substring(0, lastWhiteSpaceIndex + 1);
}
Here is a complete example after combining all of the above.
import React from "react";
import Select, { components, createFilter } from "react-select";
import options from "./options";
function getLastWord(str: string) {
return str.split(" ").slice(-1).pop();
}
function removeLastWord(str: string) {
var lastWhiteSpaceIndex = str.lastIndexOf(" ");
return str.substring(0, lastWhiteSpaceIndex + 1);
}
export default function MySelect() {
const [selected, setSelected] = React.useState([]);
const [input, setInput] = React.useState("");
const handleChange = (s) => {
setSelected({ ...s });
setInput((input) => removeLastWord(input) + s.value);
};
const handleInputChange = (e, meta) => {
if (meta.action === "input-change") {
setInput(e);
}
};
React.useLayoutEffect(() => {
const inputEl = document.getElementById("myInput");
if (!inputEl) return;
// prevent input from being hidden after selecting
inputEl.style.opacity = "1";
}, [selected]);
const customFilter = () => {
return (config, rawInput) => {
const filter = createFilter(null);
return filter(config, getLastWord(rawInput));
};
};
return (
<Select
value={selected}
filterOption={customFilter()}
inputId="myInput"
onChange={handleChange}
blurInputOnSelect={false}
inputValue={input}
onInputChange={handleInputChange}
options={options}
isSearchable
hideSelectedOptions={false}
components={{
SingleValue: () => null
}}
/>
);
}
Live Example
You can also override the input styles using the styles props. There is a whole discussion about this issue on this thread
{
input: (provided) => ({
...provided,
input: {
opacity: "1 !important",
},
}),
}