Every time I add or delete a component the whole page reloads, how do I prevent it?
When I use the HandleClick or the handleRemoveItem, the page reset and its components.
export default function Home() {
const [PostArray, setPostArray] = useState<React.ReactNode[]>([]);
let component = <PostSheet />;
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
event.preventDefault();
if (PostArray.length < 8) {
setPostArray((OldArray: any) => [...OldArray, component]);
}
}
const handleRemoveItem = useCallback(
(todo: any) => {
let newPostArray = [...PostArray];
newPostArray.splice(PostArray.indexOf(todo), 1);
setPostArray(newPostArray);
},
[PostArray]
);
const MappedArray = PostArray.map((e) => (
<PostSheet key={uuid()} click={handleRemoveItem} />
));
return (
<DefaultLayout click={handleClick}>
<Wrapper>{MappedArray}</Wrapper>
</DefaultLayout>
);
}
Related
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
I'm trying to make a Checkbox component.
Here is my Checkbox.tsx.
import React from "react";
import * as S from "./style";
const Checkbox: React.FC<S.ICheckboxProps> = ({ checked, setChecked }) => {
return <S.StyledCheckbox checked={checked} onClick={setChecked} />;
};
and this is my useCheckbox.tsx,
import { useState } from "react";
export const useCheckbox = (initialState: boolean) => {
const [checked, _setChecked] = useState<boolean>(initialState);
const setCheckedToggle = () => _setChecked((prev) => !prev);
const setCheckedTrue = () => _setChecked(true);
const setCheckedFalse = () => _setChecked(false);
return { checked, setCheckedToggle, setCheckedTrue, setCheckedFalse };
};
export default Checkbox;
It works good. I can use this like
import Layout from "components/Layout";
import { useCheckbox } from "hooks/useCheckbox";
import Checkbox from "components/Checkbox";
const Home = () => {
const { checked, setCheckedToggle } = useCheckbox(false);
return (
<Layout>
<Checkbox checked={checked} setChecked={setCheckedToggle} />
</Layout>
);
};
export default Home;
But I have trouble in the List component.
List has a Checkbox component, and I have to use this List with data.
const Home = ({data}) => {
return (
<Layout>
{data.map((d) => <List />)}
</Layout>
);
};
In this case, is there a way to determine if the list is selected?
If the List has useCheckbox, the Home component doesn't know the checked state.
Should I use useCheckbox in the Home component for data.length times? I think this is not good.
Thanks for reading, and Happy new year.
If you want the checkbox state to exist at the level of Home then you'll need state in the Home component that can handle multiple items, either as an array or object.
Then where you map over data you can pass down checked and setChecked as props to List, with all the logic defined in Home using the item index (or preferably an ID if you have one) in relation to your Home state.
Here's an example of a hook you could use in Home
import { useState } from "react";
export const useCheckboxes = () => {
const [checkedIds, setCheckedIds] = useState([]);
const addToChecked = (id) => setCheckedIds((prev) => [...prev, id]);
const removeFromChecked = (id) =>
setCheckedIds((prev) => prev.filter((existingId) => existingId !== id));
const isChecked = (id) => !!checkedIds.find(id);
const toggleChecked = (id) =>
isChecked(id) ? removeFromChecked(id) : addToChecked(id);
return { isChecked, toggleChecked };
};
And you would use it like this
const Home = ({ data }) => {
const { isChecked, toggleChecked } = useCheckboxes();
return (
<Layout>
{data.map((d) => (
<List
key={d.id}
checked={isChecked(d.id)}
toggleChecked={() => toggleChecked(d.id)}
/>
))}
</Layout>
);
};
i want to return a function that uses useEffect from the usehook and i am getting error "useeffect is called in a function which is neither a react function component or custom hook.
what i am trying to do?
i have addbutton component and when user clicks add button i want to call the function requestDialog.
below is my code within addbutton file
function AddButton () {
const count = useGetCount();
const requestDialog = useRequestDialog(); //using usehook here
const on_add_click = () => {
requestDialog(count); //calling requestDialog here
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (availableSiteShares: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useRequestDialog(enabled: boolean,count: number) {
return function requestDialog() { //here is the error
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}
}, [count, trigger]);
}
How to solve the error ""useEffect is called in a function which is neither a react function component or custom hook."
i am not knowing how to use useeffect and the same time use it in the addbutton component.
could someone help me with this. thanks
useEffect method is like, useEffect(() => {}, []), But your usage in requestDialog is wrong. Try changing with following.
function requestDialog() {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}
I'm trying to use a custom react hook to create a counter for each item in a list. the problem is when I increase or decrease a counter, the value of other counters change simultaneously. basically all of the counters show the same value.
here is my custom hook:
import { useState } from "react";
export const useQuantity = (defaultQuantity) => {
const [value, setValue] = useState(defaultQuantity || 1);
const onChange = (e) => {
if (!+e.target.value >= 1) {
setValue(1);
return;
}
setValue(+e.target.value);
};
return {
value,
setValue,
onChange,
};
};
how can I change the value of a counter while it doesn't effect the other counters?
This is the component that I map through the items and for each one of them I render the QuantityInput component.
import { useQuantity } from "../Hook/useQuantity";
import { QuantityInput } from "./QuantityInput";
export const Order = () => {
const quantity = useQuantity();
return (
orders.map((order) => (
<QuantityInput quantity={quantity} />
)
)
}
and this is QuantityInput component:
export const QuantityInput = ({ quantity }) => {
const decrement = () => {
quantity.setValue(quantity.value - 1);
};
const increment = () => {
quantity.setValue(quantity.value + 1);
};
return (
<Button
onClick={decrement}
disabled={quantity.value === 1}
>
-
</Button>
<Input {...quantity} />
<Button onClick={increment}> + </Button>
);
};
Instead of useQuantity in your parent component, you should use it in QuantityInput component so each of them hold their state.
export const QuantityInput = () => {
const quantity = useQuantity(); // Use quantity here, do not need to pass from props
const decrement = () => {
quantity.setValue(quantity.value - 1);
};
const increment = () => {
quantity.setValue(quantity.value + 1);
};
return (
<Button
onClick={decrement}
disabled={quantity.value === 1}
>
-
</Button>
<Input {...quantity} />
<Button onClick={increment}> + </Button>
);
};
The hook that you implemented holds one state value and will only suffice for it.
The solution here is to implement a component that uses this hook. Once you have the component you can render as many instances of it as you want
Also you could simply implement a component instead of custom hook
import { useState } from "react";
export const QuantityInput = (props) => {
const [value, setValue] = useState(props.defaultQuantity || 1);
const onChange = (e) => {
if (!+e.target.value >= 1) {
setValue(1);
return;
}
setValue(+e.target.value);
};
return (
<div>
<div>{value}</div>
<input value={value} onChange={onChange} />
</div>
)
};
const Parent = ({arr}) => {
return arr.map(item => <QuantityInput defaultQuantity={item.defaultQuantity} />)
}
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?