Invoke helper text method on typing - reactjs

In the following, there is a MUI textfield method, which has a helperText attribute.
I want to invoke that method, when somebody starts typing in the text field.
import { useState } from "react";
import TextField from "#mui/material/TextField";
export default function App() {
const [inputText, setInputText] = useState(null);
const [txtLength, setTxtLength] = useState(0);
const handleChange = (event) => {
const textValue = event.target.value;
setInputText(textValue);
setTxtLength(textValue.length);
};
const displayHelperText = () => {
return `${12 - txtLength} characters left`;
};
return (
<TextField
onChange={handleChange}
inputProps={{ maxLength: 12 }}
helperText={displayHelperText} // Invoke on typing in text field
/>
);
}
CodeSandbox demo.

Just do
helperText={ txtLength > 0? displayHelperText() : ""}
You could potentially use undefined instead of "" for the last portion, I don't know offhand if MUI allows undefined for that prop.
(also I'd just use the string inline, no need for a stand alone displayHelperText function here)

Related

How to add searchinputs as array in localStorage with React TypeScript

I'm trying to make a search bar, where the search will occur once the button (or enter) is clicked. To this, I want to save the searched phrases in localStorage.
I have no problem with the first part. Things work fine when searching with a button or on enter-click. However, when I try to add the search as an array to localStorage I keep getting issues (like string doesn't work with an array, and many more. I've tried A LOT of different things). I've done this with JS and vanilla React, but never with TS.
In the provided code, the search works, and to put in at least something (as simple as I could) - the latest value is also stored in localStorage.
The code can be found here
Or here:
// index file
import { useState } from "react";
import { Searchbar } from "./searchbar";
import { SearchContainer } from "./style";
export const Search = () => {
const [search, setSearch] = useState<string>("");
return (
<SearchContainer>
<Searchbar setSearch={setSearch} />
<p>SEARCHED: {search}</p>
</SearchContainer>
);
};
// search bar file
import { useRef, KeyboardEvent, useEffect } from "react";
type SearchProps = {
setSearch: React.Dispatch<React.SetStateAction<string>>;
};
export const Searchbar = ({ setSearch }: SearchProps) => {
// with useRef we don't have to reload the page for every input
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
setSearch(String(inputRef.current?.value));
localStorage.setItem("form", JSON.stringify(inputRef.current?.value)); // Issue here
};
// Enable search with the enter-key
const log = (e: KeyboardEvent): void => {
e.key === "Enter" ? handleClick() : null;
};
useEffect(() => {
const value = localStorage.getItem("form");
value ? JSON.parse(value) : "";
}, [setSearch]);
return (
<div>
<input
type="text"
autoComplete="off"
ref={inputRef}
placeholder="search game..."
onKeyDown={log}
/>
<button onClick={handleClick}>SEARCH</button>
</div>
);
};
I've been on this for a while now so any help is appreciated. (I am also trying to learn TS from scratch, but until I get to here...).

forwardRef with custom component and custom hook

Edit: Small changes for readability.
I'm new to react and I may be in at the deep end here but I'll go ahead anyway..
I have a Login component in which I want to give the users feedback when the input elements lose focus and/or when the user clicks submit.
I am aware that I achieve a similar bahavior with useState but for the sake of education I'm trying with useRef.
I'm getting a TypeError for undefined reading of inputRef in LoginForm.js. So inputRef is not assigned a value when validateInput is called. Can anyone help me make sense of why that is and whether there is a solution to it?
LoginForm.js:
import useInput from '../../hooks/use-input';
import Input from '../../UI/Input/Input';
const LoginForm = () => {
const { inputRef, isValid } = useInput(value =>
value.includes('#')
);
return <Input ref={inputRef} />;
};
use-input.js (custom hook):
const useInput = validateInput => {
const inputRef = useRef();
const isValid = validateInput(inputRef.current.value);
return {
inputRef,
isValid,
};
};
Input.js (custom element component):
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props.input}></input>;
});
One issue that I'm seeing is that in the Input component, you're using props.input, why?
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props}></input>;
});
You want exactly the props that you're sending to be assigned to the component.
Next up, you're doing value.includes('#'), but are you sure that value is not undefined?
const { inputRef, isValid } = useInput(value =>
value && value.includes('#')
);
This would eliminate the possibility of that error.
Solving the issue with the inputRef is undefined is not hard to fix.
Afterward, you're going to face another issue. The fact that you're using useRef (uncontrolled) will not cause a rerender, such that, if you update the input content, the isValid won't update its value.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. (React Docs)
This is a personal note, but I find uncontrolled components in general hard to maintain/scale/..., and also refs are not usually meant to do this kind of stuff. (yes, yes you have react-form-hook which provides a way of creating forms with uncontrolled components, and yes, it's performant).
In the meantime, while I'm looking into this a little more, I can provide you a solution using useState.
const useInput = (validationRule, initialValue='') => {
const [value, setValue] = useState(initialValue)
const onChange = (e) => setValue(e.target.value)
const isValid = validationRule && validationRule(value)
return {
inputProps: {
value,
onChange
},
isValid
}
}
So, right here we're having a function that has 2 parameters, validationRule and initialValue(which is optional and will default to text if nothing is provided).
We're doing the basic value / onChange stuff, and then we're returning those 2 as inputProps. Besides, we're just calling the validationRule (beforehand, we check that it exists and it's sent as parameter).
How to use:
export default function SomeForm() {
const { inputProps, isValid } = useInput((value) => value.includes('#'));
return <Input {...inputProps}/>;
}
The following part is something that I strongly discourage.
This is bad but currently, the only way of seeing it implemented with refs is using an useReducer that would force an update onChange.
Eg:
const useInput = (validationRule) => {
const [, forceUpdate] = useReducer((p) => !p, true);
const inputRef = useRef();
const onChange = () => forceUpdate();
const isValid = validationRule && validationRule(inputRef.current?.value);
return {
inputRef,
isValid,
onChange
};
};
Then, used as:
export default function SomeForm() {
const { inputRef, onChange, isValid } = useInput((value) =>
value && value.includes("#")
);
console.log(isValid);
return <Input ref={inputRef} onChange={onChange} />;
}

useState not updating[NOT AN ASYNC ISSUE]

I'm new to react hooks so I'm practicing with showing and hiding a div when checking and unckecking a checkbox input. The problem is that the state updates on the main file where I have the function that handles it but in the file where I actually have the div it does not update so it does not hide or display.
File that handles the change of the state:
import {react, useState} from "react";
export const Checker = () => {
const [checked, setChecked] = useState(true)
const clickHandler = () => {
setChecked(!checked)
console.log(checked)
}
return {checked, clickHandler, setChecked}
}
File where the checkbox is located:
import React from "react";
import { Extras, Wrapper } from "./extras.styles";
import { Checker } from "../hooks/useCheckboxes";
const Extra = () => {
const {checked, setChecked, clickHandler} = Checker()
return <>
<Extras>
<Wrapper>
<input type= 'checkbox' onClick={clickHandler} checked = {checked} onChange={e => setChecked(e.target.checked)}></input>
</Wrapper>
</Extras>
</>
}
export default Extra;
File that contains the div i want to display and hide dynamically:
import house from '../../icons/house.png'
import { Wrapper } from "./foto.styles";
import { Checker } from "../hooks/useCheckboxes";
import { Inside, Middle} from "./foto.styles";
const Home = () => {
const {checked} = Checker()
return <>
<Wrapper>
<Inside>
<Middle>
{checked && <House src={house}/>}
</Middle>
</Inside>
</Wrapper>
</>
}
export default Home;
Some issues are:
Checker looks like you want it to be a custom hook, not a React component, so it should be called useChecker or something like that, not Checker
You have both a change handler and a click handler. You should only have one. If you want the new state to come from the checkbox, you should use e.target.checked. If you want the new state to flip the old state, use the clickHandler you defined in Checker.
You only need a fragment when enclosing multiple elements. If you only have one, you don't need a fragment.
Because state setters don't update the variable immediately, your console.log(checked) won't display the new value, but the old value - if you want to log the new value when it changes, use useEffect with a dependency array of [checked] instead.
const Extra = () => {
const { checked, clickHandler } = useChecker()
return (
<Extras>
<Wrapper>
<input type='checkbox'checked={checked} onChange={clickHandler} />
</Wrapper>
</Extras>
)
}
use it like that
const {checked, clickHandler, setChecked} = Checker()
Or if you want to be able to make custom names then you need to use an array instead of an object.
the function return value.
return [checked, clickHandler, setChecked]
the function call
const [checked, setChecked, clickHandler] = Checker()
and for convention follow react hooks naming rules by renaming the function to useChecker() instead of Checker()

How do I set Quill output to be JSON/delta instead of html?

What do I need to do to set Quill's content or output to be JSON/delta instead of HTML?
I can't believe I'm asking such a simple question but I can't find the answer anywhere.
There's nothing about how to set the format in either the QuillJS doc or react-quill doc.
import React, { useState } from "react";
import ReactQuill, from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export const Comment = () => {
const [value, setValue] = useState('');
function submit(e) {
e.preventDefault();
console.log(value) # This currently returns HTML instead of JSON
}
return (
<>
<ReactQuill theme="snow" value={value} onChange={setValue}/>
<p>{value}</p>
<button onClick={submit}>Submit</button>
</>
);
}
It looks like the onChange prop is a function that has the HTML contents as the first argument, which will be the value used by setState. You'll want to define a custom function that sets value to editor.getContents(), which returns a Delta representing the current document.
import React, { useState } from "react";
import ReactQuill, from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export const Comment = () => {
const [value, setValue] = useState('');
function submit(e) {
e.preventDefault();
console.log(value);
}
// onChange expects a function with these 4 arguments
function handleChange(content, delta, source, editor) {
setValue(editor.getContents());
}
return (
<>
<ReactQuill theme="snow" value={value} onChange={handleChange}/>
<p>{value}</p>
<button onClick={submit}>Submit</button>
</>
);
}
As above denoted answer by person_v1.32.The handleChange method receives four params second param is the delta you can simply use that to get delta.
You can send it to the database or in your react state whatever suits you.
// onChange expects a function with these 4 arguments
function handleChange(content, delta, source, editor) {
setValue(delta);
}

useState hook in context resets unfocuses input box

My project takes in a display name that I want to save in a context for use by future components and when posting to the database. So, I have an onChange function that sets the name in the context, but when it does set the name, it gets rid of focus from the input box. This makes it so you can only type in the display name one letter at a time. The state is updating and there is a useEffect that adds it to local storage. I have taken that code out and it doesn't seem to affect whether or not this works.
There is more than one input box, so the auto focus property won't work. I have tried using the .focus() method, but since the Set part of useState doesn't happen right away, that hasn't worked. I tried making it a controlled input by setting the value in the onChange function with no changes to the issue. Other answers to similar questions had other issues in their code that prevented it from working.
Component:
import React, { useContext } from 'react';
import { ParticipantContext } from '../../../contexts/ParticipantContext';
const Component = () => {
const { participant, SetParticipantName } = useContext(ParticipantContext);
const DisplayNameChange = (e) => {
SetParticipantName(e.target.value);
}
return (
<div className='inputBoxParent'>
<input
type="text"
placeholder="Display Name"
className='inputBox'
onChange={DisplayNameChange}
defaultValue={participant.name || ''} />
</div>
)
}
export default Component;
Context:
import React, { createContext, useState, useEffect } from 'react';
export const ParticipantContext = createContext();
const ParticipantContextProvider = (props) => {
const [participant, SetParticipant] = useState(() => {
return GetLocalData('participant',
{
name: '',
avatar: {
name: 'square',
imgURL: 'square.png'
}
});
});
const SetParticipantName = (name) => {
SetParticipant({ ...participant, name });
}
useEffect(() => {
if (participant.name) {
localStorage.setItem('participant', JSON.stringify(participant))
}
}, [participant])
return (
<ParticipantContext.Provider value={{ participant, SetParticipant, SetParticipantName }}>
{ props.children }
</ParticipantContext.Provider>
);
}
export default ParticipantContextProvider;
Parent of Component:
import React from 'react'
import ParticipantContextProvider from './ParticipantContext';
import Component from '../components/Component';
const ParentOfComponent = () => {
return (
<ParticipantContextProvider>
<Component />
</ParticipantContextProvider>
);
}
export default ParentOfComponent;
This is my first post, so please let me know if you need additional information about the problem. Thank you in advance for any assistance you can provide.
What is most likely happening here is that the context change is triggering an unmount and remount of your input component.
A few ideas off the top of my head:
Try passing props directly through the context provider:
// this
<ParticipantContext.Provider
value={{ participant, SetParticipant, SetParticipantName }}
{...props}
/>
// instead of this
<ParticipantContext.Provider
value={{ participant, SetParticipant, SetParticipantName }}
>
{ props.children }
</ParticipantContext.Provider>
I'm not sure this will make any difference—I'd have to think about it—but it's possible that the way you have it (with { props.children } as a child of the context provider) is causing unnecessary re-renders.
If that doesn't fix it, I have a few other ideas:
Update context on blur instead of on change. This would avoid the context triggering a unmount/remount issue, but might be problematic if your field gets auto-filled by a user's browser.
Another possibility to consider would be whether you could keep it in component state until unmount, and set context via an effect cleanup:
const [name, setName] = useState('');
useEffect(() => () => SetParticipant({ ...participant, name }), [])
<input value={name} onChange={(e) => setName(e.target.value)} />
You might also consider setting up a hook that reads/writes to storage instead of using context:
const useDisplayName = () => {
const [participant, setParticipant] = useState(JSON.parse(localStorage.getItem('participant') || {}));
const updateName = newName => localStorage.setItem('participant', {...participant, name} );
return [name, updateName];
}
Then your input component (and others) could get and set the name without context:
const [name, setName] = useDisplayName();
<input value={name} onChange={(e) => setName(e.target.value)} />

Resources