Unable to set the checked state of the checkbox - reactjs

I created a custom react component. I am able to fetch the state of the checkbox, when it is changed on a user action. However i am not able to set the checked state when i invoke the component and set the checked state to the prop. Here is my code
import React, { FunctionComponent, SyntheticEvent, useState } from 'react';
import nanoid from 'nanoid';
import classNames from 'classnames';
import { mapToCssModules } from 'utils';
import VolumeComponentProps from 'utils/props';
export interface CheckboxProps extends VolumeComponentProps {
/** Is selected */
checked?: boolean;
/** disabled - Sets or Gets the property if the input is disabled or not */
disabled?: boolean;
/** on Change event, raised when the input is clicked */
onInputChange?: (e: SyntheticEvent, updatedState: boolean) => void;
}
export const Checkbox: FunctionComponent<CheckboxProps> = ({
checked = false,
children,
className,
cssModule,
disabled = false,
onInputChange,
...attributes
}: CheckboxProps) => {
const valueCheck = checked ? true : false;
const [inputId] = useState(`checkbox_${nanoid()}`);
const [isChecked, setIsChecked] = useState(valueCheck);
const containerClasses = mapToCssModules(classNames(className, 'vol-checkbox'), cssModule);
const checkboxInputClass = mapToCssModules(classNames('vol-checkbox__input'), cssModule);
const checkboxClass = mapToCssModules(classNames('vol-checkbox__input-control'), cssModule);
const onChangeHandler = (e: SyntheticEvent) => {
setIsChecked((e.currentTarget as HTMLInputElement).checked);
if (onInputChange) {
onInputChange(e, isChecked);
}
};
return (
<>
{isChecked && <span>true</span>}
<label className={containerClasses} htmlFor={inputId}>
<input
{...attributes}
id={inputId}
className={checkboxInputClass}
disabled={disabled}
checked={isChecked}
type="checkbox"
onChange={onChangeHandler}
/>
<span className={checkboxClass} />
</label>
</>
);
};
export default Checkbox;
So, if i invoke my checkbox as
<Checkbox onChange=()=>{} checked />
The checked value is not set to the checkbox, it works when i click it manually.
Update
This issue is only happening from Storybook. When i create a knob Checked, to alter states, that feature is not working. Here is my story.
import React from 'react';
import { storiesOf } from '#storybook/react';
import { boolean } from '#storybook/addon-knobs';
import { action } from '#storybook/addon-actions';
import Checkbox from './Checkbox';
storiesOf('Pure|Checkbox ', module).add(' Checkbox', () => {
return (
<Checkbox
disabled={boolean('Disabled', false)}
checked={boolean('Checked', false)}
onInputChange={action('onChangeHandler')}
/>
);
});

Your code should work, but you can simplify it by just toggling the isChecked state when the onChange event is fired. onChange means value changed and since a checked input has only two possible variables, we can safely assume that the value toggles, so no need to get the value from the event object each time.
Like this:
const onChangeHandler = () => {
setIsChecked(!isChecked);
if (onInputChange) {
onInputChange(e, !isChecked); // also, this line should pass the updated value
}
};

Related

Context API using the initial value not the one set in UseState

I just started using typescript with react and tried to deal with ContextAPI with the typescript.
So Far I've set a context and tried to use a provider inside my app.tsx.
My context file is looking like this:
import { createContext, ReactNode, useState } from "react";
type props = {
children: ReactNode
}
type GlobalContextType = {
currentValue: number;
setCurrentValue: (value: number) => void;
}
const INITIAL_VALUE = {
currentValue: 1,
setCurrentValue: () => {},
}
export const GlobalContext = createContext<GlobalContextType>(INITIAL_VALUE);
export const GlobalProvider = ({ children }: props) => {
const [currentValue, setCurrentValue] = useState(3);
return(
<GlobalContext.Provider value={{ currentValue, setCurrentValue }}>
{ children }
</GlobalContext.Provider>
)
}
while my app.tsx file is looking like this:
import './App.css';
import { useContext } from 'react';
import { GlobalContext, GlobalProvider } from './ContextAPI/GlobalContext';
function App() {
const { currentValue, setCurrentValue } = useContext(GlobalContext);
return (
<GlobalProvider>
<h1>{ currentValue }</h1>
<button onClick={() => setCurrentValue(currentValue + 1)}>Increment</button>
</GlobalProvider>
);
}
export default App;
There are few things that didn't work as I expected.
First: when I go to my localhost page it displays the inital value, which is 1, not the
one that I set using useState(3).
Second: When I click the button, it doesn't update the value.
I imagine that I'm always using the initial state value and not the one that I'm trying to set inside the provider.
Try to set the function increment in the context API then call it in the app right just after onClick event

States returned from custom hook undfined

State returned from custom hook is undefined. Why is it undefined I do not understand, in the form below I am trying to get states from the custom useInput hook but they are undefined, what is the problem here?
Custom hook
import {useState} from 'react';
export default function useInput (checkValidity){
const [value,setValue] = useState('');
const[isTouched,setIstouched]= useState(false);
let isThisInValid = !checkValidity(value) && isTouched;
var changeValidity = (event)=>{
setValue(event.target.value);
}
var submitTheValue = (event)=>{
event.preventDefault();
setIstouched(true);
}
return {
isThisInValid :isThisInValid,
changeValidity:changeValidity,
submitTheValue:submitTheValue,
}
}
Form in which i am trying to get value from custom hook
import React from 'react'
import Button from '../Button/Button.js'
import {useState,useRef} from 'react'
import Style from '../Input.module.css'
import useInput from '../hooks/useInput'
function BasicInput (props){
const {validityofInput, //trying to get state from custom hook
changeInputValidity,
submitTheValue,
}
= useInput((value) => value.trim()=='');
const {validityofEmail,
changeEmailValidity,
submitTheSecondValue,
}
= useInput((value) => value.includes('#'));
console.log(validityofEmail) //undefined
console.log(validityofInput) //undefined
return <>
<form className = {Style.form}
onSubmit = {submitTheValue}
noValidate
>
<div>Name</div>
<input
id="input"
className = { validityofInput && Style.wrong}
onChange = {
changeInputValidity
}/>
<div>Email</div>
<input
type = "email"
id="input"
className = {validityofEmail && Style.wrong}
onChange = {
changeEmailValidity
}/>
<Button name = "CLICK ME"> </Button>
</form>
</>
}
export default BasicInput;
You are returning an object in the hook with these properties:
return {
isThisInValid :isThisInValid,
changeValidity:changeValidity,
submitTheValue:submitTheValue,
}
and you are destructing from the hook these properties:
const {validityofInput, // notice here it must be "isThisInValid"
changeInputValidity, // and here must be "changeValidity"
submitTheValue,
}
You can rename the properties like this if you want:
const {isThisInValid: validityofInput,
changeValidity:changeInputValidity,
submitTheValue,
}

How do I populate Select Options from Redux store?

I am using react-select
I have a form that creates a new contact. In my redux store I have groups that are already created. When I click the Select to show the options I would like to load the options from my redux store.
The "groups" from redux store has only one value, that is title: String in the GroupModel in the backend.
I understand that react-select needs to have a label: '', value: ''
If I create an array myself and pass the values in it works fine. But with redux nothing is working. I cant find any answers anywhere online which seems trivial to me....
Here is my component below
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { createContact } from '../../actions/index';
import { Button, Form, FormGroup, Input, Label } from 'reactstrap';
import Divider from '../common/Divider';
const ContactForm = ({ hasLabel }) => {
const dispatch = useDispatch()
// State
const [contact, setContact] = useState({
group: '',
})
// Submit Handler
const handleSubmit = e => {
e.preventDefault()
dispatch(createContact(contact))
};
// Change Handler
const handleChange = e => {
setContact({...contact, [e.target.name]: e.target.value})
};
// bringing in our state from redux
const groups = useSelector((state) => state.groups)
return (
<>
<Form onSubmit={handleSubmit}>
<div>
<FormGroup>
<Label>
Choose Group/List</Label>
<Select
name="group"
options={groups}
getOptionlabel={({title}) => title}
getOptionValue={({_id}) => _id }
onChange={() => {}}
isMulti
/>
</FormGroup>
</div>
</Form>
</>
);
};
ContactForm.propTypes = {
hasLabel: PropTypes.bool
};
ContactForm.defaultProps = {
layout: 'basic',
hasLabel: false
};
export default ContactForm;
Ok.... So my solution above was pretty much correct. The issue I had was the getOptionlabel needed to be getOptionLabel <--- notice I forgot to capitalize the L in label....
I hope someone who needs to use react-select with redux finds this post and it helps.
So basically just bring in your redux state with useSelector or connect,
then make sure to use the props below in your Select component
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}

React.js Dropdown component doesn't hide on button click

I'm facing a problem in the making of clickable Dropdown component. My task is to show a menu when a button is clicked and hide the menu when the user clicks anywhere in the document or whether a click on the same button, also all components should be functional components.
I'm using 3rd party package named classnames which help to conditionally join CSS classes, also using a React ContextAPI to pass props to Dropdown child components.
Dropdown component depends upon 2 child components.
DropdownToggle -
(Renders a clickable button)
DropdownMenu -
(Renders a div with menu items)
Problem:
Whenever I open a menu and click anywhere in the document menu works perfectly, but when I open a menu and want to hide with a button click it didn't work. I think the problem is inside the useEffect hook of the Dropdown component.
Codesandbox
Demo:
Here is the main App component which renders all components.
App.js
import React, { Component } from "react";
import Dropdown from "./Dropdown";
import DropdownToggle from "./DropdownToggle";
import DropdownMenu from "./DropdownMenu";
import "./dropdown.css";
// App component
class App extends Component {
state = {
isOpen: false
};
toggle = () => {
alert("Button is clicked");
this.setState({
isOpen: !this.state.isOpen
});
};
render() {
return (
<div className="app">
<Dropdown isOpen={this.state.isOpen} toggle={this.toggle}>
<DropdownToggle>Dropdown</DropdownToggle>
<DropdownMenu>
<div>Item 1</div>
<div>Item 2</div>
</DropdownMenu>
</Dropdown>
</div>
);
}
}
export default App;
Main src code:
DropdownContext.js
import {createContext} from 'react';
// It is used on child components.
export const DropdownContext = createContext({});
// Wrap Dropdown with this Provider.
export const DropdownProvider = DropdownContext.Provider;
Dropdown.js
import React, { useEffect } from "react";
import classNames from "classnames";
import { DropdownProvider } from "./DropdownContext";
/**
* Returns a new object with the key/value pairs from `obj` that are not in the array `omitKeys`.
* #param obj
* #param omitKeys
*/
const omit = (obj, omitKeys) => {
const result = {};
// Get object properties as an array
const propsArray = Object.keys(obj);
propsArray.forEach(key => {
// Searches the array for the specified item, if the item is not found it returns -1 then
// construct a new object and return it.
if (omitKeys.indexOf(key) === -1) {
result[key] = obj[key];
}
});
return result;
};
// Dropdown component
const Dropdown = props => {
// Populate context value based on the props
const getContextValue = () => {
return {
toggle: props.toggle,
isOpen: props.isOpen
};
};
// toggle function
const toggle = e => {
// Execute toggle function which is came from the parent component
return props.toggle(e);
};
// handle click for the document object
const handleDocumentClick = e => {
// Execute toggle function of the parent
toggle(e);
};
// Remove event listeners
const removeEvents = () => {
["click", "touchstart"].forEach(event =>
document.removeEventListener(event, handleDocumentClick, true)
);
};
// Add event listeners
const addEvents = () => {
["click", "touchstart"].forEach(event =>
document.addEventListener(event, handleDocumentClick, true)
);
};
useEffect(() => {
const handleProps = () => {
if (props.isOpen) {
addEvents();
} else {
removeEvents();
}
};
// mount
handleProps();
// unmount
return () => {
removeEvents();
};
}, [props.isOpen]);
// Condense all other attributes except toggle `prop`.
const { className, isOpen, ...attrs } = omit(props, ["toggle"]);
// Conditionally join all classes
const classes = classNames(className, "dropdown", { show: isOpen });
return (
<DropdownProvider value={getContextValue()}>
<div className={classes} {...attrs} />
</DropdownProvider>
);
};
export default Dropdown;
Dropdown component has a parent i.e. a Provider whenever Provider values will change child components will access those values.
Secondly, on the DOM it will render a div which consists of Dropdown markup structure.
DropdownToggle.js
import React, {useContext} from 'react';
import classNames from 'classnames';
import {DropdownContext} from './DropdownContext';
// DropdownToggle component
const DropdownToggle = (props) => {
const {toggle} = useContext(DropdownContext);
const onClick = (e) => {
// If props onClick is not undefined
if (props.onClick) {
// execute the function
props.onClick(e);
}
toggle(e);
};
const {className, ...attrs} = props;
const classes = classNames(className);
return (
// All children would be render inside this. e.g. `svg` & `text`
<button type="button" className={classes} onClick={onClick} {...attrs}/>
);
};
export default DropdownToggle;
DropdownMenu.js
import React, { useContext } from "react";
import classNames from "classnames";
import { DropdownContext } from "./DropdownContext";
// DropdownMenu component
const DropdownMenu = props => {
const { isOpen } = useContext(DropdownContext);
const { className, ...attrs } = props;
// add show class if isOpen is true
const classes = classNames(className, "dropdown-menu", { show: isOpen });
return (
// All children would be render inside this `div`
<div className={classes} {...attrs} />
);
};
export default DropdownMenu;
Jayce444 answer is correct. When you click the button, it fires once, then the event bubbles up to the document and fires again.
I just want to add another alternative solution for you. You can use useRef hook to create a reference of Dropdown node and check if the current event target is button element or not. Add this code to your Dropdown.js file.
import React, { useRef } from "react";
const Dropdown = props => {
const containerRef = useRef(null);
// get reference of the current div
const getReferenceDomNode = () => {
return containerRef.current;
};
// handle click for the document object
const handleDocumentClick = e => {
const container = getReferenceDomNode();
if (container.contains(e.target) && container !== e.target) {
return;
}
toggle(e);
};
//....
return (
<DropdownProvider value={getContextValue()}>
<div className={classes} {...attrs} ref={containerRef} />
</DropdownProvider>
);
};
export default Dropdown;
The toggling function is linked to both the document, and the button itself. So when you click the button, it fires once, then the event bubbles up to the document and fires again. Gotta be careful attaching event listeners to the entire document object. Add a line to stop the event propagation in your Dropdown.js file:
// toggle function
const toggle = e => {
// Execute toggle function which is came from the parent component
e.stopPropagation(); // this stops it bubbling up to the document and firing again
return props.toggle(e);
};

Why is my search function not being passed?

I am trying to pass a search filter function into my search bar components. But i keep getting this error TypeError: Cannot read property 'search' of undefined
the search function is not recognized my context file is here
https://github.com/CodingOni/Ecommerce-Store/blob/master/src/context.js
import React, { useContext, useEffect, useRef } from 'react';
import ProductContext from '../../src/context';
const ProductFilter = () => {
const productConsumer = useContext(ProductContext);
const text = useRef('');
const { search, searchResults } = productConsumer;
useEffect(() => {
console.log(` product context ${productConsumer}`)
});
const onChange = e => {
if (text.current.value !== '') {
search(e.target.value);
} else {
}
};
return (
<form>
<input
ref={text}
type="text"
placeholder="Search Keywords..."
onChange={onChange}
id=""
/>
</form>
);
};
export default ProductFilter;
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value for that
context.
You pass react component to useContext which is default export from '../../src/context'.
In context file you need export PoductContext
export { ProductProvider, ProductConsumer, ProductContext };
..
import {ProductContext} from '../../src/context';

Resources