Update state only on OK, revert back to previous state on CANCEL - javascript

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.

Related

Component re-render on state change not happening

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

Weird re-render behavior with react material-ui dialog

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

Hide component when another identical component is shown with React

I have a list of items (ResultItem), which have a component inside (ResultTag) which when clicked, shows a tooltip above it (a HTML class is added to it and removed when clicked again, to hide it).
However, when I click on ResultTag, and then click on ResultTag in one of the ResultItem's below it, both show; how would I go about hiding all of the ResultTag's apart from the one I just clicked on, so that only one can show at a time.
Currently, in the ResultItem, I have an onClick function which sets the state showTooltip in the ResultTag to false/hidden (using props) whenever the user clicks anywhere within ResultItem and the ResultTag is visible. However, I need this to work across every ResultItem, which means working cross-component.
Here is some simplified code:
/* ResultTag */
showTooltip() {
this.setState({ showTooltip: true })
}
render() {
return (
<div onClick={this.showTooltip}>
{this.renderTooltip()} { /* function which contains the JSX/HTML to show the toolip */ }
<span className="tag--label">Tags</span>
</div>
)
}
Hiding is done in the ResultItem, by setting the state and then receiving that as props in the ResultTag.
To summarise:
I have many ResultItem components in a list view
Each ResultItem has a ResultTag in, which when clicked, shows a tooltip above the tag/label
When a ResultTag is visible, and another one in a different ResultItem is clicked, hide all the other ResultTags
You could move the state from within each individual ResultItem into the parent, that way it is centralized in one place and you could enforce logic such that only a certain ResultItem will show its tooltip. You would need to manage the state from within the parent and then pass down a function to each ResultItem (and probably down again into its ResultTag) to handle the click.
I wrote a sample app which shows similar behaviour (although slightly different), I wrote it to demonstrate how to add a border to each item in a list. You can see how I stored the state in the parent and how I passed down the means to read and update it to the children via props. You would of course have to change the logic to enforce only a single item being active, currently it supports any item in the list being 'active'. I wrote it for an answer located here: https://stackoverflow.com/a/38646533/1515758

ReactJS - render with default state

How can I rerender component with state from getInitialState()?
For example: https://jsbin.com/pajapoyipo/edit?html,output
In example code I click on the button, component Button change state and button is Yellow - its work, but when I click on another button I would like to clicked the button was yellow (its work), and the old button changed state and color to red (it doesn't work).
Avoid stuff like this:
childState.setState({klasa: "yellow"});
You should not be setting child state from the parent. State is internal to a component, it should only be changed by itself. Instead, the parent can re-render the child with different props.
If you want only one button to be active, then you have to have some piece of state that will only permit a single active button. Since children can't know about their fellow siblings, this piece of state must reside in the parent. I've called it activeBtn. If activeBtn is 1, then the first button appears yellow. This piece of state in the parent corresponds to the boolean property active in child. I've also defined a click handler in the parent to change the state. This click handler is passed down to each child as a prop.
If that doesn't make sense, the modified code should serve as a better explanation:
https://jsbin.com/filasisemu/1/edit?html,output

React render, change radio button programmatically

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.

Categories

Resources