React update state of component after search through object - javascript

I have an object that is outputted from the reactIcons npm package https://www.npmjs.com/package/react-icons I am importing everything from one of the folders with import * as ReactIcons from 'react-icons/fa' I am using sanity studio, I created a text input that takes the value searched and runs a search function that runs a includes on the values from the object I grabbed from the reactIcons fa folder and log's the vales that match. Now I want to take those values and update a react component with useState() Howerver I get the following error Uncaught ReferenceError: setIcon is not defined and SetIcon is the setter function from useState array. Here is my code so far
import React, { useState } from 'react';
import PropTypes from 'prop-types'
import FormField from 'part:#sanity/components/formfields/default'
import PatchEvent, {set, unset} from 'part:#sanity/form-builder/patch-event'
import * as ReactIcons from 'react-icons/fa'
console.log(ReactIcons);
const createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(String(value)))
const Example = value => {
const [icon, setIcon] = useState();
return (
<div>{icon}</div>
);
}
const search = value => {
console.log(value);
Object.keys(ReactIcons).map(go => {
if (go.toLowerCase().includes(value) === true){
console.log(go);
setIcon(go);
}
})
}
class IconPickerCustom extends React.Component{
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired
};
render = () =>{
const {type, value, onChange} = this.props
return (
<>
<FormField label={type.title} description={type.description}>
<input
type="text"
value={value === undefined ? '' : value}
onChange={event => onChange(createPatchFrom(event.target.value))}
ref={element => this._inputElement = element}
/>
</FormField>
<input
type="text"
onChange={event => search(event.target.value)}
/>
{Object.values(ReactIcons).map(X =>{
return (
<>
<X/>
</>
);
})}
{console.log(ReactIcons.Fa500Px)}
<ReactIcons.Fa500Px/>
<Example/>
</>
)
}
}
export default IconPickerCustom;

React has two types of components, class components and function components (formerly stateless components). Hooks are used in function components when you realize you need state inside a function component and prefer not to convert it to a class component.
The useState() Hook allows us to add state in a function component.
Class Component
//initialize state
constructor(props) {
super(props);
this.state = {foo: bar};
}
//set state
this.setState({foo: baz});
//read state
this.state.foo;
Function Component
(with useState() Hook)
//initialize state
const [icon, setIcon] = useState("myInitialValue");
//set state
setIcon("myNewValue");
//read state
{icon}
That being said, you already have a class component so no need to use hooks here.
class IconPickerCustom extends React.Component{
constructor(props) {
super(props);
this.state = { icon: "nothing" };
}
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired
};
const createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(String(value)));
const search = value => {
Object.keys(ReactIcons).map(go =>
({ go.toLowerCase().includes(value) ?
this.setState({icon:go}) : null;})
}
render = () =>{
const {type, value, onChange} = this.props
return (
<>
<FormField label={type.title} description={type.description}>
<input
type="text"
value={value === undefined ? '' : value}
onChange={event => onChange(createPatchFrom(event.target.value))}
ref={element => this._inputElement = element}
/>
</FormField>
<input
type="text"
onChange={event => search(event.target.value)}
/>
{Object.values(ReactIcons).map(X =>{
return (
<>
<X/>
</>
);
})}
{console.log(ReactIcons.Fa500Px)}
<ReactIcons.Fa500Px/>
<Example/>
</>
)
}
}
export default IconPickerCustom;
You are going to want something along those lines. Let me know if you have any questions.

Related

Manually updating Form.Item value

I need to update the value of Form.Item manually. I have a custom component, which returns selected value, and want to pass this value to the Form for validation and so I can send it.
Is this possible with antd?
Here's the simplified code:
import { Form } from "antd";
import { FC, ReactElement, useEffect, useState } from "react";
const Child: FC<{
returnValue: (value: any) => void;
}> = ({ returnValue }): ReactElement => {
return <input onChange={(e) => returnValue(e.currentTarget.value)} />;
};
export default function App() {
const { useForm } = Form;
const [form] = useForm();
const [value, setValue] = useState<string>("");
const setNewValue = (newVal: string) => setValue(newVal);
useEffect(() => {
console.log(value);
}, [value]);
return (
<div className="App">
test
<Form form={form}>
<Form.Item
//value={value} I'm looking form smth like this
>
<Child returnValue={setNewValue} />
</Form.Item>
</Form>
</div>
);
}
And here is the code sandbox.
Using <Input> from antd will not work in my case. This is a simplified problem. I have a way more complex component, which behaves in a similar way. The returnValue is how I managed to pull the value out of the component.
For a class based component, this is how you would define a form
class CustomForm extends React.Component {
formRef = React.createRef();
constructor()
render(){
return(
<Form
ref={this.formRef}
name="customForm"
>
<Form.Item label="Email" name="email">
<Input />
</Form.Item>
</Form>
)}
}
and this is how you set form.items value
componentDidUpdate(){
this.formRef.current.setFieldsValue({
email: this.props.customerData.map((d) => d.email),
});
}
you can convert the logic for the functional component.

Order of hooks error when rendering different components

React throws the following error when I am trying to render different components
Warning: React has detected a change in the order of Hooks called by GenericDialog. This will lead to bugs and errors if not fixed.
Previous render
Next render
useRef
useRef
useState
useState
useState
useState
useState
useState
useState
useState
useState
useState
useContext
useState
I do agree this would be inappropriate when I would be rendering the same component each time but with different order of hooks. What I am trying to achieve is render a different component each time so it is quite obvious the order of hooks won't be identical.
I have created this GenericDialog component which renders a multistep dialog.
import React, { useRef, useState, useEffect } from 'react';
import { DialogFooterNavigation } from './DialogFooterNavigation';
import { Dialog } from '../../../../Dialog';
import { Subheader } from '../../../../Subheader';
import { Loading } from '../../../../Loading';
export interface FooterConfiguration {
onContinue?: () => Promise<boolean | void>;
isContinueDisabled?: boolean;
continueLabel?: string;
isBackHidden?: boolean;
isCancelHidden?: boolean;
}
export interface HeaderConfiguration {
subheader?: string;
}
export interface DialogStepProps {
setHeaderConfiguration: (config: HeaderConfiguration) => void;
setFooterConfiguration: (config: FooterConfiguration) => void;
}
export type DialogStep = (props: DialogStepProps) => JSX.Element;
interface GenericDialogProps {
isShown: boolean;
hideDialog: () => void;
steps: DialogStep[];
header: string;
}
export const GenericDialog = ({
isShown,
hideDialog,
steps,
header,
}: GenericDialogProps) => {
const buttonRef = useRef(null);
const [step, setStep] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [headerConfiguration, setHeaderConfiguration] = useState<HeaderConfiguration | undefined>(
undefined,
);
const [footerConfiguration, setFooterConfiguration] = useState<FooterConfiguration | undefined>(
undefined,
);
const [loadingMessage, setLoadingMessage] = useState<string>('');
const dialogBody = steps[step]({
setHeaderConfiguration,
setFooterConfiguration,
});
const nextStep = () => {
if (step < steps.length - 1) {
setStep(step + 1);
}
};
const prevStep = () => step > 0 && setStep(step -1);
const isBackPossible = step > 0;
const onBack = () => (isBackPossible || footerConfiguration?.isBackHidden ? undefined : prevStep);
const onContinue = async () => {
setIsLoading(true);
const result = await footerConfiguration?.onContinue?.call(undefined);
setIsLoading(false);
if (result === false) {
return;
}
nextStep();
};
return (
<Dialog isShown={isShown} onHide={hideDialog}>
<div>
{header}
{headerConfiguration?.subheader && (
<Subheader>{headerConfiguration.subheader}</Subheader>
)}
</div>
{isLoading && loadingMessage ? <Loading msg={loadingMessage} /> : dialogBody}
{!isLoading && (
<DialogFooterNavigation
onBack={isBackPossible ? onBack : undefined}
onContinue={onContinue}
isContinueDisabled={footerConfiguration?.isContinueDisabled}
/>
)}
</Dialog>
);
};
const FirstStep = (props: DialogStepProps) => {
// Here I need useContext
const { id, name } = useCustomerContext();
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !id || !name,
})
}, [id, name]);
return (
<>
<div>ID: {id}</div>
<div>Name: {name}</div>
</>
);
};
const SecondStep = (props: DialogStepProps) => {
// Here I don't need useContext but I do need useState
const [inputValue, setInputValue] = useState({});
useEffect(() => {
props.setFooterConfiguration({
isContinueDisabled: !inputValue,
});
}, [inputValue]);
return <input value={inputValue} onChange={(event) => setInputValue(event.target.value)} />;
}
const MyDialogExample = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const steps: DialogStep[] = [
FirstStep,
SecondStep,
];
return (
<>
<button onClick={() => setIsDialogOpen(true)}>Open Dialog</button>
<GenericDialog
isShown={isDialogOpen}
hideDialog={() => setIsDialogOpen(false)}
steps={steps}
header="Dialog example"
/>
</>
);
};
The problem is here:
const dialogBody = steps[step]({
setHeaderConfiguration,
setFooterConfiguration,
});
Try changing it to something like this:
const DialogBody = steps[step];
And then, in your return statement:
{isLoading && loadingMessage ? <Loading msg={loadingMessage} /> : <DialogBody setHeaderConfiguration={setHeaderConfiguration} setFooterConfiguration={setFooterConfiguration} />}
Please note that it can be done differently, like:
const DialogBody = steps[step];
const dialogBody = <DialogBody setHeaderConfiguration={setHeaderConfiguration} setFooterConfiguration={setFooterConfiguration} />;
And keeping your return statement unaltered.
Explanation
Your code isn't entirely wrong though. When working with functional components, there is a subtle difference between an actual component, a hook and a simple function that returns an instantiated component based on some logic. The problem is that you are mixing those three.
You can't manually instantiate a component by calling its corresponding function (just like you can't instantiate a class component by using the new operator). Either you use JSX (like <DialogBody />) or directly use React inner methods (Like React.createElement()). Both alternatives are different from just doing dialogBody(). For example, if you see the compiled JSX code you will note that <DialogBody /> compiles to code that uses React.createElement() and the latter returns a real React element instance containing many special properties and methods.
dialogBody() would work if its only goal was to return an instantiated element (Using one of the methods above) based on some logic. This implies not using any hook along with some other constraints.
Instead, your dialogBody 'function' contains hooks and it acts as a custom hook itself. This is why React complains about hooks execution order. You are executing hooks conditionally.

How to set value through patch event to any field from custom input in sanity.io?

I want to make patch event from custom component and set a value to another field in document, but couldn’t find documentation about patch events.
there are only example without field specification:
PatchEvent.from(set(value))
Does anybody knows how to specify field name?
This opportunity contains in documentation, but without examples
https://www.sanity.io/docs/custom-input-widgets#patch-format-0b8645cc9559
I couldn't get PatchEvent.from to work at all for other fields inside a custom input component but useDocumentOperation combined with withDocument from part:#sanity/form-builder
This is a rough working example using a custom component:
import React from "react";
import FormField from "part:#sanity/components/formfields/default";
import { withDocument } from "part:#sanity/form-builder";
import { useDocumentOperation } from "#sanity/react-hooks";
import PatchEvent, { set, unset } from "part:#sanity/form-builder/patch-event";
// tried .from(value, ["slug"]) and a million variations to upate the slug but to no avail
const createPatchFrom = (value) => {
return PatchEvent.from(value === "" ? unset() : set(value));
};
const ref = React.createRef();
const RefInput = React.forwardRef((props, ref) => {
const { onChange, document } = props;
// drafts. cause an error so remove
const {patch} = useDocumentOperation(
document._id.replace("drafts.", ""), document._type)
const setValue = (value) => {
patch.execute([{set: {slug: value.toLowerCase().replace(/\s+/g, "-")}}])
onChange(createPatchFrom(value));
// OR call patch this way
patch.execute([{set: {title: value}}])
};
return (
<input
value={document.title}
ref={ref}
onChange={(e) => setValue(e.target.value)}
/>
);
});
class CustomInput extends React.Component {
// this._input is called in HOC
focus = () => {
ref.current.focus();
};
render() {
const { title } = this.props.type;
return (
<FormField label={title}>
<RefInput ref={ref} {...this.props} />
</FormField>
);
}
}
export default withDocument(CustomInput);

React: passing state value as parameter to method

I want to make my class more reusable. I have huge form to handle, and I don't want write single method for each input. How to pass state value as parameter to method?
I was trying:
state = {
subtitle: ""
};
inputHandler = (e, param) => this.setState({ [param]: e.target.value });
render() {
return (
<>
{this.state.title}
<input
type="text"
value={this.state.subtitle}
onChange={e => this.inputHandler(this.state.subtitle)}
/>
</>
);
}
and different similar combinations of this solution.
Demo: https://codesandbox.io/s/kw6pnxwv0v
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
subtitle: ""
};
onChange = (e) => {
const target = e.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<>
{this.state.title}
<input
type="text"
name="subtitle"
value={this.state.subtitle}
onChange={this.onChange}
/>
{this.state.subtitle}
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Let me know if this helps :)

React, looping through object, displaying list, and grabbing that object when selected

Edit:
I have made it work by getting the target.value from the list, passing that to firstComp.js. Where the onChange method is below, but is this the best practice?
onchange = (e) =>{
let name= e.target.value
let currentName= this.state.foodData.map((item)=>{
if(name==item.name){
console.log(item)
}
})
}
Im doing stephen griders course on react, and trying to implement what he teaches on a personal project, but I cant wrap my head around it.
I have a list, that is looping through an array of objects. When I pick something from that list, I want it to update the state with that object.
The Layout..
DropDownList.js = return a drop down list with all the ingredient names in foodData
DropDownItem = loops through the foodData, returning an option value for each one.
foodData.js = db that looks something like this..
let foodData=[
{"name":"MCT Oil", "brand":"Keppi Keto", "servings":1}
{"name":"Chunky Peanut Butter", "brand":"Skippy"}
]
firstComp.js
import React, { Component} from 'react'
import ReactDOM from 'react-dom'
import foodData from './food/foodData.js'
import DropDownList from './DropDownList.js'
class Layout extends Component {
constructor () {
super()
this.state = {
foodData:foodData,
selectedFood:''
}
this.onchange =this.onchange.bind(this)
}
onchange = (e) =>{
console.log(e.target.value)
}
render () {
return (<div className='home'>
<DropDownList foodData={this.state.foodData} onchange={this.onchange} />
</div>)
}
}
const app = document.getElementById('app')
ReactDOM.render(<Layout />, app)
DropDownList.js
import React from 'react'
import DropDownItem from './DropDownItem.js'
const DropDownList = (props) =>{
****let textInput = React.createRef();**** //REFS
const ingredientItems = props.foodData.map((ingredient, i)=>{
return<DropDownItem key={i} ingredient={ingredient} **ref={textInput}**//REFS />
})
return(
<select value={ingredientItems.name} onChange ={ (e) => props.onchange(e)} >
{ingredientItems}
</select>
)
}
export default DropDownList;
DropDownItem.js
import React from 'react'
const DropDownItem = ({ingredient}) =>{
const itemName = ingredient.name
return(<option value={itemName}>{itemName}</option> )
}
export default DropDownItem;
You don't need to use refs here. You can use some kind of handleChange function in your DropdownList component and send back the value to parent component.
const DropDownList = (props) => {
const handleChange = e => props.onchange( e.target.value );
const ingredientItems = props.foodData.map((ingredient) =>
<DropDownItem key={ingredient.name} ingredient={ingredient} /> );
return (
<select onChange={handleChange}>
{ingredientItems}
</select>
)
}
and in the parent component:
class Layout extends Component {
state = {
foodData,
selectedFood: foodData[0]
};
onchange = name =>
this.setState({
selectedFood: foodData.filter(food => food.name === name)[0]
});
render() {
return (<div className='home'>
<DropDownList foodData={this.state.foodData} onchange={this.onchange} />
</div>)
}
}
or without struggling with values and filter, we can use e.target.options.selectedIndex as OP finds out him/herself :) This is a cleaner way and works as this (only related parts) :
DropdownList.js
const handleChange = e => props.onchange( e.target.options.selectedIndex);
Layout.js
onchange = index => this.setState({ selectedFood: foodData[index] });

Categories