How to prevent first rerendering Formik - javascript

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?

Related

React: How to change props values

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.

React: Map over array and dynamically render a component in a specific div

Probably I'm missing something really simple, but:
I have an array:
const [weight, setWeight] = useState([]);
And I want to map over it to dynamically render a component:
const renderWeight = () => {
weight.map((e) => {
<Weight />;
});
};
I want this to happen when I click on a submit button, and I want the components to render in a specific div.
I can't find a way to do this.
Thank you!
You can use a show variable as toggler to show/hide the weight component like this
import React, { useState } from "react";
const App = () => {
const [weight, setWeight] = useState([1, 2, 3]);
const [show, setShow] = useState(false);
return (
<div className="container">
<div className="weight">{show && weight.map((e) => <Weight />)}</div>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
</div>
);
};
export default App;
I thing you need to return the mapped array
const renderWeight = () => {
return weight.map((e) => {
<Weight />;
});
};
note: Make sure to assign a key to each component rendered
You have to return it.
const [show ,setShow] = useState(false)
const renderWeight = () => {
return weight.map((e) => {
return <Weight />;
});
};
return (
<>
<button onClick={() => setShow(!show)}>Click to show/hide</button>
{
show?
renderWeight():''
}
</>
);

How do I only re-render the 'Like' button in React JS?

const SiteDetails = ({site, user}) => {
const dispatch = useDispatch()
const updateSite = async (siteId, siteObject) => {
try {
const updatedSite = await siteService.update(siteId, siteObject)
dispatch(toggleStatus(siteId, updatedSite))
// dispatch(setNotification(`One like added to ${updatedSite.title}`))
} catch (exception) {
console.log(exception)
dispatch(setNotification("Could not update site"))
}
}
return (
<div className='site-details'>
<Link href={site.url}>{site.url}</Link>
<h2>
<LikeButton user={user} site={site} updateSite={updateSite} />
<VisitButton user={user} site={site} updateSite={updateSite} />
</h2>
<p>{site.description}</p>
<img src={site.imageUrl} alt={"Image could not be loaded"} />
</div>
)
}
const LikeButton = ({user, site, updateSite}) => {
const dispatch = useDispatch()
const likedList = site?.userLiked.find(n => n?.username === user.username)
useEffect(() => {
if (!likedList) {
const newUser = { username: user.username, liked: false }
const updatedArray = site.userLiked.concat(newUser)
const updatedSite = {...site, userLiked: updatedArray}
updateSite(site.id, updatedSite)
}
},[])
const liked = likedList?.liked
const handleLike = async () => {
const indexCurr = site.userLiked.indexOf(likedList)
//updatedSite is the parent site. This will have its liked status toggled
//actually updatedUserLiked can simply use username: user.username and liked: true
const updatedUserLiked = { username: likedList?.username, liked: !likedList.liked}
site.userLiked[indexCurr] = updatedUserLiked
const updatedSite = {...site, userLiked: site.userLiked}
updateSite(site.id, updatedSite)
//childSite is the spawned from the parent,
//it will contain a parent, which is the updatedSite
const childSite = {...site, parent: updatedSite, opcode: 100}
const newSite = await siteService.create(childSite)
// dispatch(createSite(newSite))
dispatch(initializeUsers())
dispatch(initSites())
}
return (
<Button
size='small' variant='contained'
color={liked ? 'secondary' : 'primary'}
onClick={!liked ? handleLike : null} className='site-like'>{liked ? 'Already Liked' : "like"}
</Button>
)
}
Expected Behaviour
Actual Behaviour
Hi everyone, my goal is to make it such that when the 'Like' button is clicked, the appearance of the button will change but the rest of the page does not refresh. However, if you look at the actual behaviour, after the button is clicked, the page is re-rendered and is restored to its default state. I have made my own component which upon clicking will display further details below.
I have tried looking at React.memo and useRef, none of which worked for me. Help on this would be very much appreciated. Thanks!
Have tried using the 'useState' hook?
import {useState} from 'react'
function LikeButton() {
//set default value to false
const [liked, setLiked] = useState(false);
const handleClick = () => {setLiked(!liked)}
return(
<Button color={liked? 'secondary' : 'primary'} onClick={() => handleClick()}
/>
);
}
export default LikeButton;
this should only re-render the button
check this out as well:
ReactJS - Does render get called any time "setState" is called?

React Component calling method to a different component

I have a page with the following structure
const Upload = (props) => {
return (
<BaseLayout>
<ToolbarSelection />
<Box>
<FileDropArea />
</Box>
</BaseLayout>
)
}
I have a method which works in the component <FileDropArea />
This is the method used as example
const allSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
In React how can i call this method allSelection from the <ToolbarSelection /> component, where i have my simple button like <Button>All Selection</Button>
You need to use React Context like this:
//create a fileContext.js
const fileContext = React.createContext();
const useFileContext = () => React.useContext(fileContext);
const FileContextProvider = ({ children }) => {
const [files, setFiles] = useState([]);
const allSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
// if you have other methods which may change the files add them here
return (
<fileContext.Provider
value={{
files,
setFiles,
allSelection,
}}
>
{children}
</fileContext.Provider>
);
};
use fileContextProvider in your upload file
const Upload = (props) => {
return (
<FileContextProvider>
<BaseLayout>
<ToolbarSelection />
<Box>
<FileDropArea />
</Box>
</BaseLayout>
</FileContextProvider>
);
};
use it, for example in ToolbarSelection like this:
const ToolbarSelection = () => {
const {files, allSelection} = useFileContext();
// do other stuff
}
React Hooks
I assume you are looking to make the allSelection function reusable. Hooks are a great way to make logic reusable across components.
Create a custom hook useAllSelection. Note that hooks should have a use prefix.
const useAllSelection = (files) => {
const [files, setFiles] = useState([]);
const handleAllSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
return { handleAllSelection };
};
const ToolbarSelection = () => {
// import the hook and use
const { handleAllSelection } = useAllSelection();
return (
<button onClick={handleAllSelection}>All Selection</button>
)
}
ReactJS allows to perform this scenario in a different way. Let me explain it: if you press a button in the ToolbarSelection, pass the value of the new state of that button to FileDropArea as props. Then, in the FileDropArea render, call the method or not depending on the value of that property
const Upload = (props) => {
return (
<BaseLayout>
<ToolbarSelection
onSelectionClick={(value) => setSelected(value)}
/>
<Box>
<FileDropArea
selected = { /* state of a button in the Toolbar */}
/>
</Box>
</BaseLayout>
)
}
Note how the callback in the Toolbar changes the state, and how this new state is passed to FileDropArea as property

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

Categories

Resources