React: How to change props values - javascript

In my code, I replace these values
const [items, setItem] = useState<string[]>([]);
const [value, setValue] = useState('')
const [error, setValue]= useState('')
to this
type Props = {
items?: string[],
value?: string,
error?: string
}
and then change the following setItem, setValue, setValue which causes the following error
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
type Props = {
items?: string[],
value?: string,
error?: string
}
export const TagActions = (props:Props) => {
const { items, value, error } = props;
// const [items, setItem] = useState<string[]>([]);
// const [value, setValue] = useState('')
// const [error, setError]= useState('')
const divRef = useRef<HTMLDivElement>(null)
const handleDelete = (item:any) => {
console.log("handleDelete", item)
const result = items?.filter(i => i !== item)
setItem(result)
};
const handleItemEdit = (item:any) =>{
console.log("handleItemEdit", item)
const result = items?.filter(i => i !== item)
items = result // setItem(result)
value = item // setValue(item)
console.log("value", value)
};
const handleKeyDown = (evt:any) => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var test = value?.trim();
if (test && isValid(test)) {
items?.push(test)
setValue("")
}
}
};
const isValid = (email:any)=> {
let error = null;
if (isInList(email)) {
error = `${email} has already been added.`;
}
if (!isEmail(email)) {
error = `${email} is not a valid email address.`;
}
if (error) {
setError(error);
return false;
}
return true;
}
const isInList = (email:any)=> {
return items?.includes(email);
}
const isEmail = (email:any)=> {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
}
const handleChange = (evt:any) => {
setValue(evt.target.value)
// setError("")
};
const handlePaste = (evt:any) => {
evt.preventDefault();
var paste = evt.clipboardData.getData("text");
var emails = paste.match(/[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/g);
if (emails) {
var toBeAdded = emails.filter((email:any) => !isInList(email));
setItem(toBeAdded)
}
};
return (
<>
<div>
<TextField id="outlined-basic" variant="outlined"
InputProps={{
startAdornment: items?.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
onDelete={() => handleDelete(item)}
onClick={() => handleItemEdit(item)}
/>
)),
}}
ref={divRef}
value={value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => handleChange(e)}
onPaste={(e) => handlePaste(e)}
/>
</div>
{error && <p className="error">{error}</p>}
</>
);
}
I am a beginner in react typescript, so I don't know how to fix this, Please give me a solution to fix this problem

While changing const to let may fix immediate errors in the console, I doubt this will give you the behaviour that you desire.
The main issue here is that you are mutating the value of props, which in general you should never do. Props are used to pass stateful data down from a parent to a child component. If you wish to update the state of this data from the child component, you should pass an update function using props as well. Below gives an example of what I mean by this by implementing the delete item function (no typescript, but hopefully it gets the idea across):
const ParentComponent = () => {
const [items, setItems] = useState(["item1", "item2", "item3"])
const deleteItem = (itemToDelete) => {
//here we use the functional update form of setState which is good practise when the new state depends on the old state
setItems((items) => items.filter((item) => item!==itemToDelete))
}
return <ChildComponent items={items}, onDeleteItem={deleteItem} />
}
const ChildComponent = ({items, onDeleteItem}) => {
//e.g. to delete item2 call
onDeleteItem("item2")
}
This is one of the more confusing patterns in React, but it is very important to get your head around. Only the component where state is declared should actually be updating that state - as it is the only place where you have access to the setState function.

Related

How can I make a todo list using functional components?

I'm making it so that every component is one element (button, the whole list, a single element...) I'm having trouble figuring out how to make my list print below the form. Tasks are shown in console.log() but I can't seem to get the right data transferred.
Thanks in advance for any help
This is items.jsx code
import React, { useState} from 'react'
import './todo.css'
import List from './list'
import Button from './button';
function Items () {
const [tasks, setTasks] = useState([]);
const [value, setvalue] = useState("");
/* const onChange = (e) => {
setvalue(e.target.value)
// console.log('type')
} */
const onAddTask = (e) =>{
e.preventDefault();
console.log('submit')
const obj = {
name: value ,
id: Date.now(),
};
if (value !== "") {
setTasks(tasks.concat(obj));
setvalue("")
console.log(obj)
}
};
return(
<div className="form">
<header>Your todo list</header>
<input
placeholder="type your task"
value={value}
onChange={(e) => setvalue(e.target.value)}/>
<input type="date" placeholder='Set your date!'/>
<button onClick={onAddTask}>Submit task</button>
<List data = {List}/>
</div>
)
}
export default Items
This is list.jsx code
import React , { useState } from "react";
import "./Items"
import Button from "./button"
const List = (tasks) => {
return(
<div>
{tasks.map}
</div>
)
console.log(task.map)
}
export default List
step 1
Here's a fully functioning demo to get you started -
function Todo() {
const [items, setItems] = React.useState([])
const [value, setValue] = React.useState("")
const addItem = event =>
setItems([...items, { id: Date.now(), value, done: false }])
return <div>
<List items={items} />
<input value={value} onChange={e => setValue(e.target.value)} />
<button type="button" onClick={addItem}>Add</button>
</div>
}
function List({ items = [] }) {
return <ul>
{items.map(item =>
<ListItem key={item.id} item={item} />
)}
</ul>
}
function ListItem({ item = {} }) {
return <li>{item.value}</li>
}
ReactDOM.render(<Todo />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
start with good state
Note using an Array to store the items is somewhat inefficient for the kinds of operations you will need to perform. Consider using a Map instead. Run the demo again and click on a list item to toggle its state -
const update = (m, key, func) =>
new Map(m).set(key, func(m.get(key)))
function Todo() {
const [items, setItems] = React.useState(new Map)
const [value, setValue] = React.useState("")
const addItem = event => {
const id = Date.now()
setItems(update(items, id, _ => ({ id, value, done: false })))
}
const toggleItem = id => event =>
setItems(update(items, id, item => ({ ...item, done: !item.done })))
return <div>
<List items={items} onClick={toggleItem} />
<input value={value} onChange={e => setValue(e.target.value)} />
<button type="button" onClick={addItem}>Add</button>
</div>
}
function List({ items = new Map, onClick }) {
return <ul>
{Array.from(items.values(), item =>
<ListItem key={item.id} item={item} onClick={onClick(item.id)} />
)}
</ul>
}
function ListItem({ item = {}, onClick }) {
return <li onClick={onClick}>
{ item.done
? <s>{item.value}</s>
: item.value
}
</li>
}
ReactDOM.render(<Todo />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
do more with less
Functional programming goes a long way in React. Using a curried update function we can take advantage of React's functional updates -
const update = (key, func) => m => // <-
new Map(m).set(key, func(m.get(key)))
function Todo() {
// ...
const addItem = event => {
const id = Date.now()
setItems(update(id, _ => ({ id, value, done: false }))) // <-
}
const toggleItem = id => event =>
setItems(update(id, item => ({ ...item, done: !item.done }))) // <-
// ...
}
but don't stop there
Avoid creating the todo item data by hand { id: ..., value: ..., done: ... }. Instead let's make an immutable TodoItem class to represent our data. A class also gives us an appropriate container for functions that would operate on our new data type -
class TodoItem {
constructor(id = 0, value = "", done = false) {
this.id = id
this.value = value
this.done = done
}
toggle() {
return new TodoItem(id, value, !this.done) // <- *new* data
}
}
Now our Todo component is unmistakable with its intentions -
function Todo() {
// ...
const [items, setItems] = useState(new Map)
const addItem = event => {
const id = Date.now()
setItems(update(id, _ => new TodoItem(id, value))) // <- new TodoItem
}
const toggleItem = id => event =>
setItems(update(id, item => item.toggle())) // <- item.toggle
// ...
}

Update a component after useState value updates

Having a monaco-editor inside a React component:
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
The defaultValue, the default code inside of the editor, is sent via props to the component:
const MyComponent = ({
originalCode
}: MyComponentProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
When the user edits the code, onChange={onChangeCode} is called:
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
My question is, how to reset the code to the original one when the user clicks on Cancel?
Initially it was like:
const handleCancel = () => {
onChangeCode(defaultValue);
};
but it didn't work, probably because useState is asynchronous, any ideas how to fix this?
Here is the whole component for more context:
import Editor from '#monaco-editor/react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, HeaderWithButtons } from '../shared/ui-components';
import { ICalculationEngine } from '../../../lib/constants/types';
import { usePostScript } from '../../../lib/hooks/use-post-script';
import { scriptPayload } from '../../../mocks/scriptPayload';
import { editorDefaultValue } from '../../../utils/utils';
export interface ScriptDefinitionProps {
realInputDetails: Array<ICalculationEngine['RealInputDetails']>;
realOutputDetails: ICalculationEngine['RealInputDetails'];
originalCode: string;
scriptLibId: string;
data: ICalculationEngine['ScriptPayload'];
}
const ScriptDefinition = ({
realInputDetails,
realOutputDetails,
originalCode
}: ScriptDefinitionProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
const [code, setCode] = useState(defaultValue);
const { handleSubmit } = useForm({});
const { mutate: postScript } = usePostScript();
const handleSubmitClick = handleSubmit(() => {
postScript(scriptPayload);
});
const handleCancel = () => {
onChangeCode(defaultValue);
};
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
useEffect(() => {
setDefaultValue(editorDefaultValue(realInputDetails, realOutputDetails));
}, [realInputDetails, realOutputDetails, originalCode]);
return (
<div>
<HeaderWithButtons>
<div>
<Button title='cancel' onClick={handleCancel} />
<Button title='save' onClick={handleSubmitClick} />
</div>
</HeaderWithButtons>
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
</div>
);
};
export default ScriptDefinition;
If you need the ability to change the value externally, you'll need to use the Editor as a controlled component by passing the value prop (sandbox):
For example:
const defaultValue = "// let's write some broken code 😈";
function App() {
const [value, setValue] = useState(defaultValue);
const handleCancel = () => {
setValue(defaultValue);
};
return (
<>
<button title="cancel" onClick={handleCancel}>
Cancel
</button>
<Editor
value={value}
onChange={setValue}
height="90vh"
defaultLanguage="javascript"
/>
</>
);
}

Get previous props value with React Hooks

I am using usePreviousValue custom hook to get previous props value from my component:
const usePreviousValue = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const MyComponent = ({ count }) => {
const prevCount = usePreviousValue(count)
return (<div> {count} | {prevCount}</div>)
}
But in this case, in prevCount I always have only the first count prop value when a component was rendered, and the next updated prop value is never assigned to it. Are there any ways to properly compare nextProp and prevProp with functional React components?
Your code sample seems to be working just fine. How exactly are you using the component? Try to run the snippet below:
const { useEffect, useRef, useState } = React;
const usePreviousValue = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const MyComponent = ({ count }) => {
const prevCount = usePreviousValue(count);
return (<div> {count} | {prevCount}</div>);
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<MyComponent count={count} />
<button
onClick={() => setCount((prevCount) => prevCount + 1)}
>
Count++
</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
As previously answered, the easiest way to do it is using a custom hook:
import isEqual from "lodash/isEqual";
import { useEffect, useRef } from "react";
const useComponentDidUpdate = (callback, data, checkIfIsEqual) => {
const prevData = useRef(data);
useEffect(() => {
const isTheSame = checkIfIsEqual ? isEqual(data, prevData) : undefined;
callback(prevData.current, isTheSame);
prevData.current = data;
}, [data]);
return null;
};
export default useComponentDidUpdate;
Then in your component:
const Component = ({age})=>{
const [state, setState] = useState({name: 'John', age})
useComponentDidUpdate(prevStateAndProps=>{
if(prevStateAndProps.age !== age || prevStateAndProps.state.name !== state.name){
// do something
}
}, {state, age})
...
}

Passing data to sibling components with react hooks?

I want to pass a variable username from sibling1 component to sibling2 component and display it there.
Sibling1 component:
const sibling1 = ({ usernameData }) => {
// I want to pass the username value I get from input to sibling2 component
const [username, setUsername] = useState("");
const handleChange = event => {
setUsername(event.target.value);
};
return (
<Form.Input
icon='user'
iconPosition='left'
label='Username'
onChange={handleChange}
/>
<Button content='Login' onClick={handleClick} />
)
}
export default sibling1;
Sibling2 component:
export default function sibling2() {
return (
<h1> Here is where i want to display it </h1>
)
}
You will need to handle your userName in the parent of your siblings. then you can just pass setUsername to your sibling1, and userName to your sibling2. When sibling1 use setUsername, it will update your parent state and re-render your sibling2 (Because the prop is edited).
Here what it looks like :
const App = () => {
const [username, setUsername] = useState('Default username');
return (
<>
<Sibling1 setUsername={setUsername} />
<Sibling2 username={username} />
</>
)
}
const Sibling2 = ({username}) => {
return <h1> Helo {username}</h1>;
}
const Sibling1 = ({setUsername}) => {
return <button onClick={setUsername}>Set username</button>;
}
In parent of these two components create a context where you will store a value and value setter (the best would be from useState). So, it will look like this:
export const Context = React.createContext({ value: null, setValue: () => {} });
export const ParentComponent = () => {
const [value, setValue] = useState(null);
return (
<Context.Provider value={{value, setValue}}>
<Sibling1 />
<Sibling2 />
</Context.Provider>
);
Then in siblings you are using it like this:
const Sibling1 = () => {
const {setValue} = useContext(Context);
const handleChange = event => {
setValue(event.target.value);
};
// rest of code here
}
const Sibling2 = () => {
const {value} = useContext(Context);
return <h1>{value}</h1>;
}
best way: React Context + hooks
you can use React Context. take a look at this example:
https://codesandbox.io/s/react-context-api-example-0ghhy

How to prevent first rerendering Formik

I started using the <AutoSave/> component created by Jared Palmer:
const AutoSave = ({debounceMs}) => {
const formik = useFormikContext()
const debouncedSubmit = useCallback(
debounce(formik.submitForm, debounceMs),
[formik.submitForm, debounceMs]
)
useEffect(() => debouncedSubmit, [debouncedSubmit, formik.values])
return <>{!!formik.isSubmitting && 'saving...'}</>
}
The main problem is when I enter the page, <AutoSave/> submits the form once the page is mounted, how to prevent this behavior?
Example:
<Formik onSubmit={values => callMyApi(values)} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
Well, I didn't get a normal idea. Decided to use flag with hook usePrevious:
import {useRef} from 'react'
const usePrevious = value => {
const ref = useRef()
const prev = ref.current
ref.current = value
return prev
}
Now it looks like:
const MyForm = () => {
const [shouldUpdate, setShouldUpdate] = useState(false)
const previousShouldUpdate = usePrevious(shouldUpdate)
useEffect(() => {
setShouldUpdate(true)
return () => {setShouldUpdate(false)}
}, [])
<Formik onSubmit={values => {
if (previousShouldUpdate) {
return callMyApi(values)
}
}} initialValues={{bla: 'bla-bla'}}>
{() => (
//...My beautiful field components...
<AutoSave debounceMs={300}/>
)}
</Formik>
}
Any ideas to make it better?

Categories

Resources