Background
So I build a front-end project for a delivery service, based on React and Material UI.
I was asked to use a Dialog window that will be opened when clicking on item, and there the user will have the opportunity to customize the item.
The dialog can be seen here(very simple: item photo, name, desc, and customization options):
https://ibb.co/GFkpy8Q
(Sorry for the pixelization)
The problem
I use react hooks in this project, and that why manage the Dialog's state.
Although simple, I stumbled upon few problems with how elements got re-rendered/not re-rendered(when expected):
The "checked" prop of the checkboxes uses Array.some, to see if the unique ID of the checkbox is in the state Array. The checkboxes are not being set to checked when clicking on them. The onChange prop simply pushes the checkbox's value to the state array and sets the state:
const [array, setArray] = React.useState([]);
...
<Checkbox
checked={
array.some(
item => item._id === subOption._id
)
}
onChange={() => setArray(array.push(subOption))}
/>
The onChange action works properly, so why the "checked" prop doesn't work properly?
When a checkbox is checked, I want to add a small quantity field next to it, so the user will be able to choose the quantity of the subOption he shall receive.
So I use the next well-practiced pattern:
{array.some(item => item._id === subOption._id) &&
(
<QuantityField />
)
}
The problem is that the QuantityField is not shown after the checkbox get checked.
If I exit the Dialog and enter it again, I suddenly see the checked checkbox is checked, and the QuantityField is shown next to it 😕
If I change the item's quantity with the general QuantityField you can see at the bottom left of the Dialog image, suddenly all the checked checkboxes gets unchecked 😕
The general QuantityField uses a state of it's own, and is not connected to any of the checkboxes.
From what I could see after I tried to debug the weird behavior, I can say that the render action of the Dialog component isn't working as expected. The states are updated, but doesn't trigger a re-render of the Dialog tree. Actually, it is wrong to say that, as the Dialog tree gets re-rendered, but the "checked" prop is not being re-checked during the re-render; but a complete un-mount and re-mount of the Dialog shakes the tree right.
Hope for an interesting answer. Thank you.
I would change how you are using setArray. See from Array.prototype.push() documentation:
The push() method adds one or more elements to the end of an array and returns the new length of the array.
Also using .push() on the state is not allowed because never should mutate the state directly.
Suggested solution instead:
onChange={() => setArray(prevState => [...prevState, subOption])}
Related
I want to use DetailsList component https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist and select items. As I understand, the component doesn't provide us a prop to get selected items. I only see onActiveItemChanged prop which is only giving me single item but not all selected items.
I checked the question below, but it seems the answer doesn't work
Getting selected items in Fluent UI DetailsList
I wanted to create a state with an empty array and push or filter selected items into it, but there is also another problem. Scenario:
Click checkbox on header to select all items
If I click an item's checkbox, then it becomes unselected and other items still selected
If I click an item's any field instead of checkbox, then other items become unselected, and the one I clicked become selected.
Because of this reason -maybe I can also have other problem in the future-, I couldn't use state, as well.
I will be very glad if I can get current selected checkboxes on DetailsList component.
I found the answer.
import Selection from #fluentui/react
Add selection={selection} to DetailsList props
and then do this before returning the component:
const selection = new Selection({
onSelectionChanged: () => {
const selectedItems = selection.getSelection();
// Do something with the selected items'
console.log(selectedItems, "ewgergre");
},
});
I have a react app similar to what is shown in this codesandbox link
https://codesandbox.io/s/eatery-forked-t44bx?fontsize=14&hidenavigation=1&theme=dark
Clicking the Menu at the bottom center of the page renders the MenuFilter component.
The way I have it now, checking the checkbox immediately updates the checked items. This is good from a user interface point of you.
User gets instant feedback on what he is clicking on (what is getting un-clicked if something is getting un-clicked).
The problem with though is that the OK/Cancel button loose there use. In other words, after making changes to the selection, if the user decides that we does not want any of these new selection & rather revert back to old selection (the one before the Menu button was clicked), he cannot do that because all the states have been updated (updating happens as the check boxes are being clicked).
How to update the state ONLY if OK (currently only CANCLE button is working, so you can consider that) button is clicked. However, the check-marks should none the less change as the user is clicking them.
Is there a way to do this other than creating a 'temporary' state (I don't want to do this) to update the visual changes & only when a button is clicked, the changes are done one the data.
You can change the checkedItems to a local state of the MenuFilter component, and pass it as an argument on onClick. That way, when you open the modal, the component mounts again and the state should return to its default.
Something like:
export const MenuFilter = ({ onClick, onCheck }) => {
const [checkedItems, setCheckedItems] = useState(["allItems"])
return (
<button onClick={() => onClick(checkedItems)}>Cancel</button>
)
}
Edit: You'll still need some sort of state to handle the data on the parent component, but you won't have to manually handle resetting the data.
I have a react app similar to what is shown in this codesandbox link https://codesandbox.io/s/eatery-v1691
Clicking the Menu at the bottom center of the page renders the MenuFilter component.
The MenuFilter component in turn uses a checkbox component to render several check boxes & the checked status of these checkboxes depends on the selectedMeals prop that I pass to the MenuFilter in app.tsx.
I read that when the state is passed as a prop, the re-rendering happens as soon as the state changes but this is not happening.
The selectedMeals state is initialized with allItems and then managed such that when any other items is click, that items is set & allItems and these individual items can be several at once. The state management works fine but the checkbox is not getting updated based on the state!
If you however, click cancel (ok button does not do anything yet) & then click Menu button again, only the values in the selectedMeals prop are rendered.
See the video here (https://youtu.be/Zmfc0MQGaIU this is the full app, codesandbox app is representation of this but not the same) where the state change (allItems gets removed & when allItems is checked other items get removed) works perfectly but the check boxes are not re-rendering appropriately
What am I doing wrong & more importantly how do I get the result I want?
I initially put the question here re-render as soon as the state is changed but there was no sandbox for it & since the sandbox is different from what I put in that question, I created a new question. Thanks.
In this video https://youtu.be/2v3lOPaIPzU I have captured the change in the selectedMeals in console log & I also show how the checkboxes does not match the selectedMeals.
The problem is in the Checkbox component.
const [checked, setChecked] = useState(isChecked);
isChecked will only be used as the initial value, checked will not update every time isChecked changes. You could manually update checked in a useEffect hook with isChecked as a dependency, but it is unnecessary as the Checkbox component can be implemented without any state.
If you however, click cancel (ok button does not do anything yet) &
then click Menu button again, only the values in the selectedMeals
prop are rendered.
That's because the modal is conditionally rendered based on menuModalIsShown and when this boolean gets toggled, the component unmounts and mounts back causing useState(isChecked) to use the updated state.
Updated Checkbox:
export const CheckBox = ({
standAlone,
text,
onCheck,
isChecked,
value
}:
CheckBoxProps) => {
return (
<div
className={`${styles.checkbox} ${
standAlone ? "" : styles.notStandAlone
} `}
>
<input
type="checkbox"
checked={isChecked}
onChange={onCheck}
value={value}
/>
<label htmlFor={styles.checkbox}>{text}</label>
</div>
);
};
I am new with react-spring. I am having trouble animating a component whenever it unmounts. I have a simple card with an onClick handler that's responsible for conditionally displaying my Overlay component. The animation works fine when mounting (from & enter works), but when closing the overlay, the component just disappears without animation (leave does not work). I suspect it's because of the conditional rendering of the component but I've been struggling for hours trying to find a solution for this one. Any help would be appreciated!
My current code: https://codesandbox.io/s/dry-leftpad-h3vmv
What I'm trying to achieve: https://codesandbox.io/s/048079xzw
P.S. The latter is using mauerwerk's lib. I don't want to use that.
What you were missing is this:
return expand.map(({ item, props, key }) => (
item && <animated.div
// ...etc
When you're controlling the mounting of a single component with useTransition, you need to conditionally render it based on the item being passed. In your case, when it's false it won't render (which will unmount if already mounted) and when it's true it will render (mount if unmounted).
Here's a working sandbox forked from yours: https://codesandbox.io/s/infallible-agnesi-cty5g.
A little more info
The first argument to useTransition is the list you want to transition. That watches for changes and sends back an array mapped with each item, a key and a style object (props) based on whether the item is truthy (entering) or falsy (leaving). So for a transition that mounts/unmounts a single element, conditionally rendering based on the truthiness of the item is key.
Check out the examples again here and you'll see the differences between transitioning a list, a toggle between two elements, and a single item.
For a list, no need to check for the existence of the item because the array changes.
For toggling between two elements, you use the truthiness of item to determine which element to render.
For a single element, item determines whether to render at all. This means it won't mount initially when you default to false, and will make sure you don't render 2 items whenever your isActive value changes.
I was creating a Dropdown component for React. Inside the dropdown, I have a form of radio group buttons.
<DropdownButton />
<DropdownForm />
In the DropdownButton, I have an state to know if it is open or not. Depends on that, DropdownForm it's hidden or not (using display: none).
The use case is: User selects a radio button, click apply and something happen. However, if user selects some radio button, and mouse out the dropdown (without clicking the apply button), the one that is selected should be the one that I get from the store.
Something like:
render: function () {
...
if(store.getSomeParam() != this.state.someParam && !this.props.isOpen){
someParam = store.getSomeParam()
}
Then the radio buttons are like:
<input checked={someParam == "something"} ... />
It doesn't really work. It re-renders but it doesn't change the button that is checked. I also tried with refs:
this.refs.myInput.getDOMNode().checked = true
But still nothing. Is this a correct behaviour?
The only solution I found so far is not using a css hiding class (display: none). So what I do is that the DropdownButton renders the DropdownForm depending on if it's open or not (so if you close it, you are forcing DropdownForm to unmount). Then when opening again, it is taking the values from the store (getInitialState) and it shows the correct radio button selected. But, I am not sure if this is the best solution and if there is any drawback in unmounting the component instead of just css hiding it.
This probably has nothing to do with React at all.
Most browsers don't validate the value of the checked attribute, but merely if it is there or not: http://jsfiddle.net/7jzm7gvw/
Just set the checked attribute to either true or null:
<input checked={someParam == "something" ? true: null} ... />
TL;DR: You must use the componentDidMount lifecycle method, not render, to work with the rendered dom nodes directly.
I was struggling with this as well, and after doing some online research I figured I might as well look into it for myself. Here's what I came up with:
Use the componentDidMount lifecycle method and update whatever you need to in there. Here's a Pen I used to prototype this, and I think it looks okay: http://codepen.io/gholts/pen/GpWzdb
You could drop this in pretty easily to what your'e working on by just putting a componentDidMount method on your object and doing it there. I used document.getElementById but you could definitely use jQuery or whatever else you wanted in there, as once the component has mounted it's available to DOM selectors.
I'm using this now to update 20 radio button groups (so it has to check a prop for three different states and update accordingly) and it loads instantly.
Hope it helps! I used the ES6 class syntax in my Pen, you should check it out if you have some time to refactor :) It's fun.
EDIT: So I figured it out, I'm a dummy. You don't need to do the whole document.getElementById business that I was doing. Just use your this.refs.whichever.getDOMNode().checked = true and it'll work, so long as you do it in componentDidMount. It works there because there is an actual DOM element on the page at that point.