React - combine functions - javascript

How could I combine those two functions (handleSelectAll and handleSelectNone)? Toggle (on/off) wouldn't work here as in most cases some options would be 'checked' so you won't know whether to toggle it ALL or NONE so there need to be 2 separate buttons (at least that's what I think). What I was thinking was that the function can be shared
const handleSelectAll = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: true
}
}))
}
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: false
}
}))
}
and then the buttons in a component:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={props.handleSelectAll}>
Select All
</button>
<button onClick={props.handleSelectNone}>
Select None
</button>
</div>
);
};
Would there be a way of just defining one function for both buttons?

I usually do like this, keep things simple and legible, nothing too fancy:
const handleSelect = (selected = false) => {
setCategories((oldCats) =>
oldCats.map((category) => {
return {
...category,
selected,
}
})
)
}
const handleSelectAll = () => {
return handleSelect(true)
}
const handleSelectNone = () => {
return handleSelect()
}
(the render part continues as is)
Doing like so avoids you creating that extra template function passing an argument and creating a new function on every render

Yes. You can wrap the call for props.handleSelectAll and props.handleSelectNone with a function and pass the new value as argument:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={()=>props.handleSelect(true)}>
Select All
</button>
<button onClick={()=>props.handleSelect(false)}>
Select None
</button>
</div>
);
};
[ ()=>props.handleSelect(true) is an arrow function that calls handleSelect on click ]
And the new function will be:
const handleSelect= (newValue) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: newValue
}
}))
}

You can make it one function using a parameter, such as
const handleChangeAll = (selected) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: selected
}
}))
}
Then you can call this function with a parameter in each button like this:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={() => props.handleChangeAll(true)}>
Select All
</button>
<button onClick={() => props.handleChangeAll(false)}>
Select None
</button>
</div>
);
};

selected: !category.selected
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: !category.selected
}
}))
}

Related

How do I use Async with array.map when rendering in ReactJS?

How would I go about running this async with ReactJS?
{array.map((content, index) => {
const var = await asyncFunction(param)
return(
<div key={index} className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
)
})}
You have to separate the async processing from the rendering (as of React 17, at least), and you have to use the promise directly, but can be done in a function, stored in a state variable, then rendered. That function can be called when the array changes using useEffect.
Example:
function MyComponent(array) {
const [vars, setVars] = useState([]);
function handleArrayChanged(newArray) {
// note: no error handling, do this yourself as appropriate.
Promise.all(array.map(var => asyncFunction(var)))
.then((newVars) => setVars(newVars))
.catch(error => { /* handle error */ });
}
useEffect(() => {
handleArrayChanged(array);
}, [array]);
return (
<>
{vars.map((var, index) => {
return(
<div key={index} className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
)
})}
</>
}
Depending on exactly what asyncFunction does, you may want to break that async check out into its own component, as well, but that's subjective based on what asyncFunction does.
function MyComponent({element}) {
const [var, setVar] = useState(undefined)
function handleElementChanged(newElement) {
asyncFunction(newElement)
.then(newVar => setVar(newVar))
.catch(error => { /* handle error */ });
}
useEffect(() => {
handleElementChanged(element);
}, [element]);
return (
<div className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
);
}
function MyList({array}) {
return (
<>
{array.map(element, index) => (
<MyComponent
key={index}
element={element}
/>
)}
</>
);
}

Should I use the Redux store to pass data from child to parent?

I am building a to-do/notes app in order to learn the basics of Redux, using React hooks and Typescript.
A note is composed of an ID and a value. The user can add, delete or edit a note.
The add / delete mechanics work fine. But the edit one is trickier for me, as I'm questionning how it should be implemented.
I think my reducer's code is fine. The problem lies between my component (Note.tsx) and its parent one (App.tsx).
When i'm logging the value, I can see that the new updated/edited value of the note is not sent to the reducer. As a result, my note is not edited with the new value.
I've tried "cloning" the redux store and making my changes here, but it seems tedious and unnatural to me. Should I just call the edit method from my Note.tsx component ?
Is there a clean / conventional way to do this ?
Here is my code :
App.tsx
function App() {
const notes = useSelector<NotesStates, NotesStates['notes']>(((state) => state.notes));
const dispatch = useDispatch();
const onAddNote = (note: string) => {
dispatch(addNote(note));
};
const onDeleteNote = (note: NoteType) => {
dispatch(deleteNote(note));
};
const onEditNote = (note: NoteType) => {
dispatch(updateNote(note));
};
return (
<div className="home">
<NewNoteInput addNote={onAddNote} />
<hr />
<ul className="notes">
{notes.map((note) => (
<Note
updateNote={() => onEditNote(note)}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
))}
</ul>
</div>
);
}
Note.tsx
interface NoteProps {
deleteNote(): void
updateNote(noteValue: string | number): void
note: NoteType
}
const Note: React.FC<NoteProps> = ({ deleteNote, updateNote, note: { id, value } }) => {
const [isEditing, setIsEditing] = useState(false);
const [newNoteValue, setNewNoteValue] = useState(value);
const onDeleteNote = () => {
deleteNote();
};
const onUpdateNote = () => {
updateNote(newNoteValue);
setIsEditing(false);
};
const handleOnDoubleClick = () => {
setIsEditing(true);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
onBlur={onUpdateNote}
/>
);
};
return (
<li className="note" key={id}>
<span className="note__title">
Note n°
{id}
</span>
<div className="note__body" onDoubleClick={handleOnDoubleClick}>
{renderBody()}
</div>
<button type="button" onClick={onDeleteNote}>Delete</button>
</li>
);
};
export default Note;
and the notesReducer.tsx
export interface NotesStates {
notes: Note[]
}
export interface Note {
id: number
value: string
}
const initialState = {
notes: [],
};
let noteID = 0;
export const notesReducer = (state: NotesStates = initialState, action: NoteAction): NotesStates => {
switch (action.type) {
case 'ADD_NOTE': {
noteID += 1;
return {
...state,
notes: [...state.notes, {
id: noteID,
value: action.payload,
}],
};
}
case 'UPDATE_NOTE': {
return {
...state,
notes: state.notes.map((note) => {
if (note.id === action.payload.id) {
return {
...note,
value: action.payload.value,
};
}
return note;
}),
};
}
case 'DELETE_NOTE': {
return {
...state,
notes: [...state.notes
.filter((note) => note.id !== action.payload.id)],
};
}
default:
return state;
}
};
Thanks to #secan in the comments I made this work, plus some changes.
In App.tsx :
<Note
updateNote={onEditNote}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
In Note.tsx :
interface NoteProps {
deleteNote(): void
updateNote(newNote: NoteType): void // updated the signature
note: NoteType
}
// Now passing entire object instead of just the value
const onUpdateNote = (newNote: NoteType) => {
updateNote(newNote);
setIsEditing(false);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
// modifying current note with updated value
onBlur={() => onUpdateNote({ id, value: newNoteValue })}
/>
);
};

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

How to re-render a callback function in ReactJS?

I'm making a filter function by checkbox. I made a reproduce like below. I changed values in array but checkbox checked status not change, what I missed? I think I must re-render list but it's also refill checked array to initial state. What should I do? Thanks!
import * as React from "react";
import "./styles.css";
import { Checkbox } from "antd";
const arr = [
{
name: "haha"
},
{
name: "haha2"
},
{
name: "hahaha"
}
];
const App = () => {
let [viewAll, setViewAll] = React.useState(true);
let checked = new Array(3).fill(true);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "white" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
haha
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
checked = checked.map(() => viewAll);
};
const handleFilter = (value, index) => {
setViewAll(false);
checked.map((_value, _index) => {
if (_index === index) {
return (checked[_index] = !checked[_index]);
} else {
return checked[_index];
}
});
console.log(checked);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>Check all</Checkbox>
{list}
</div>
);
};
export default App;
Here is codesanboxlink
You should create checked state. Check the code below.
let [viewAll, setViewAll] = React.useState(true);
let [checked, setChecked] = React.useState([true, true, true]);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "black" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
{value.name}
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
setChecked(() => checked.map(item => !viewAll));
};
const handleFilter = (value, index) => {
setViewAll(false);
setChecked(() =>
checked.map((_value, _index) => {
if (_index === index) return !checked[_index];
return checked[_index];
})
);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>
{checked}
</Checkbox>
{list}
</div>
);
codesandbox demo
You have to define checked array as a state value.
Right now your code is not firing render function because checked array is not props but also not state.

To do list using react-sortable-hoc

I'm using sortable drag and drop which works fine. The problem is that I'd like users to be able to remove items. The SortableItem component isn't accessible as it came with the code, so I can't pass an event handler that takes index as an argument. Here's what I have so far:
const SortableItem = SortableElement(
({value}) =>
<ul>{value}</ul>
);
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>
);
});
export class BlocksContainer extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
};
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
addBlock = (block) =>{
let arr = [...this.state.items, block];
this.setState({items: arr})
}
removeBlock = (index) => {
let remove = [...this.state.items];
remove.filter(block => block === index);
this.setState({items:remove})
}
render() {
return (<div><div onChange={console.log(this.state)} className="sortableContainer"><SortableList items={this.state.items} onSortEnd={this.onSortEnd} /></div>
<h2>Blocks</h2>
<button onClick={() => this.addBlock(<BannerImage remove={this.removeBlock} />)}>Banner Image</button>
<button onClick={() => this.addBlock(<ShortTextCentred remove={this.removeBlock}/>)}>Short Text Centred</button>
<h2>Layouts</h2>
<hello />
</div>
)
}
}
Since you dont have control over the events of the SortableItem component, you can wrap that component in a component you do have control over.
For example, if i wanted to add a click handler to the SortableItem, i would add it instead to the div wrapper:
const SortableList = SortableContainer(({ items }) => {
return (
<ul>
{items.map((value, index) => (
<div onClick={this.someEventHandler}>
<SortableItem key={`item-${index}`} index={index} value={value} />
</div>
))}
</ul>
);
});

Categories

Resources