Props and onChildClick not working together - javascript

I have a parent component "Item" and a child component "Order".
Item has a button that toggles whether "Order" is displayed. If book is displayed, it passes the fetched details as props to the Order component, as well as the function for toggling if its open or closed.
Before adding the props to "Order", the toggle worked perfectly. After adding the props, the prop-handling works as it should, but now the function doesn't work. What am i doing wrong?
const Item = () => {
const [item, setItem] = useState('');
const [order, setOrder] = useState('');
//Api call to get item
const orderOpenClose = () => {
setOrder(!order);
};
return (
<>
<div onClick={orderOpenClose}>
<Button text="Order"></Button>
</div>
{order ? <Order acc={item} onChildClick={orderOpenClose}/> : ""}
</>
)
}
const Order = (props, { onChildClick }) => {
const { item } = props;
return (
<>
<div onClick={onChildClick}>
x
</div>
<p>{item.title}</p>
)
}```

This (props, { onChildClick }) is just not correct syntaxis, you can either destruct props or pass them as one object, but not both, so you can do either
const Book = ({acc, onChildClick })
or
const Book = (props) => {
const { acc,onChildClick } = props;

Related

Why does my toast notification not re-render in React?

I am trying to create my own "vanilla-React" toast notification and I did manage to make it work however I cannot wrap my head around why one of the solutions that I tried is still not working.
So here we go, onFormSubmit() I want to run the code to get the notification. I excluded a bunch of the code to enhance readability:
const [notifications, setNotifications] = useState<string[]>([]);
const onFormSubmit = (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault();
const newNotifications = notifications;
newNotifications.push("success");
console.log(newNotifications);
setNotifications(newNotifications);
};
return (
<>
{notifications.map((state, index) => {
console.log(index);
return (
<ToastNotification state={state} instance={index} key={index} />
);
})}
</>
</section>
);
Inside the Toast I have the following:
const ToastNotification = ({
state,
instance,
}:
{
state: string;
instance: number;
}) => {
const [showComponent, setShowComponent] = useState<boolean>(true);
const [notificationState, setNotificationState] = useState(
notificationStates.empty
);
console.log("here");
const doNotShow = () => {
setShowComponent(false);
};
useEffect(() => {
const keys = Object.keys(notificationStates);
const index = keys.findIndex((key) => state === key);
if (index !== -1) {
const prop = keys[index] as "danger" | "success";
setNotificationState(notificationStates[prop]);
}
console.log(state);
}, [state, instance]);
return (
<div className={`notification ${!showComponent && "display-none"}`}>
<div
className={`notification-content ${notificationState.notificationClass}`}
>
<p className="notification-content_text"> {notificationState.text} </p>
<div className="notification-content_close">
<CloseIcon color={notificationState.closeColor} onClick={doNotShow} />
</div>
</div>
</div>
);
};
Now for the specific question - I cannot understand why onFormSubmit() I just get a log with the array of strings and nothing happens - it does not even run once - the props get updated with every instance and that should trigger a render, the notifications are held into a state and even more so, should update.
What is wrong with my code?

Passing a function as a prop, when calling the function it doesn't get the correct values

I have a strange issue with passing a function as a prop in my React app. Code as follows:
const NewPage = () => {
const [blocks, setBlocks] = useState([]);
const [needsShowImageModal, setNeedsShowImageModal] = useState(false);
const textButtonHandler = () => {
const key = randomInt(0, 1000000000);
const array = blocks.concat({ key, deleteButtonHandler: deleteButtonHandler });
setBlocks(array);
};
function deleteButtonHandler(blockKey) {
// Test functionality, if one text field was added arrray size should
// be 1
console.log(blocks.length);
}
return (
<div>
<ImageModal
show={needsShowImageModal}
onHide={() => setNeedsShowImageModal(false)}
insertButtonHandler={insertImageHandler}
/>
<div className="d-flex">
<NewPageSidebar
textButtonHandler={textButtonHandler}
imageButtonHandler={imageButtonHandler}
spacingButtonHandler={spacingButtonHandler}
/>
<NewPageContent blocks={blocks} />
</div>
</div>
);
};
export default NewPage;
When text button handler is called (a button press) I add a new data model to the blocks array. I have another button handler deleteButtonHandler that is passed to the NewPageContent component (inside the data model). NewPageContent:
const NewPageContent = ({ blocks }) => {
return (
<div className="new-page-content-container border mr-5 ml-5 p-3">
{blocks.map(block =>
<TextBlock
key={block.key}
blockKey={block.key}
deleteButtonHandler={block.deleteButtonHandler}
/>
)}
</div>
);
};
NewPageContent.propTypes = {
blocks: PropTypes.arrayOf(PropTypes.element)
};
export default NewPageContent;
And finally this handler is passed to TextBlock:
const TextBlock = ({ deleteButtonHandler, blockKey }) => {
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const toolbarClickHander = (buttonType, e) => {
e.preventDefault();
switch (buttonType) {
case 'delete':
// Delete button handler called here
deleteButtonHandler(blockKey);
break;
default:
break;
}
};
return(
<div className='text-block'>
<TextBlockToolbar clickHandler={toolbarClickHander} />
<Editor
editorState={editorState}
onChange={setEditorState}
/>
</div>
);
};
If I add one element to blocks via textButtonHandler the component is rendered on screen as expected. However if I tap the delete button and deleteButtonHandler is called it will log the size of the array as 0, Strangely if I add second element and then tap the delete button for that element if logs the array size as 1. It's almost as if it's taking a snapshot of the blocks state just as the new textButtonHandler is assigned to the prop and not using the actual current state of blocks. Any ideas what I might be doing wrong here? Haven't run into this issue before. Thanks
Okay. What is happening here:
You passing a function in an object. That object might have an own context, and you tryes to use that function in that object context, what confuses react. (I know that ECMAScript simple objects has no own context, but react might process that data, so might work in different way .) So, pass each function standalone prop in the child component.
Example: https://codesandbox.io/s/broken-waterfall-vgcyj?file=/src/App.js:0-1491
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [blocks, setBlocks] = useState([
{ key: Math.random(), deleteButtonHandler }
]);
const textButtonHandler = () => {
const key = Math.random();
// const array = blocks.concat({
// key,
// deleteButtonHandler: deleteButtonHandler
// });
setBlocks(prev => prev.concat({ key, deleteButtonHandler }));
};
const deleteButtonHandler = blockKey => {
// Test functionality, if one text field was added arrray size should
// be 1
console.log(blocks.length);
};
return (
<div>
<div className="d-flex">
<NewPageContent
deleteButtonHandler={deleteButtonHandler}
blocks={blocks}
/>
</div>
<button onClick={textButtonHandler}>Handler</button>
</div>
);
}
const NewPageContent = ({ blocks = [], deleteButtonHandler = () => null }) => {
return (
<div className="new-page-content-container border mr-5 ml-5 p-3">
{blocks.map(block => (
<TextBlock
key={block.key}
blockKey={block.key}
// deleteButtonHandler={block.deleteButtonHandler}
deleteButtonHandler={deleteButtonHandler}
/>
))}
</div>
);
};
const TextBlock = ({ deleteButtonHandler = () => null, blockKey = "" }) => {
return (
<div className="text-block">
{blockKey}
<span onClick={deleteButtonHandler}>X</span>
</div>
);
};
I have consoled out your old solution, to compare it.

useState variable not latest version in delete function, React

I have a list of div's that contain question fields. For every button click i add a new line of question fields and memorize in state the whole list and how many lines there are. I tried adding a delete button but when my delete functions it seems that the value from the state variable is remembered from when the line was made. How do i solve this so i can acces the full list in the HandleDelete function?
const OefeningAanvragenDagboek = () => {
const { t, i18n } = useTranslation()
const [questionCount, setQuestionCount] = useState(1)
const [questionList, setQuestionList] = useState([])
const createNewLine = () =>{
var newLine=
<div>
<Field type="text" name={"vraag" + questionCount}/>
<Field component="select" name={"antwoordMogelijkheid"+questionCount}>
<option value="">...</option>
<option value="open">{t('oefeningAanvragenDagboek.open')}</option>
<option value="schaal">{t('oefeningAanvragenDagboek.scale')}</option>
</Field>
<Field type="text" name={"type"+questionCount}/>
<button onClick={() => HandleDelete(questionCount-1)}>{t('assignmentCard.delete')}</button>
</div>
setQuestionList(questionList => [...questionList, newLine])
setQuestionCount(questionCount+1)
}
const HandleDelete = (index)=> {
console.log(questionList)
// setQuestionList(questionList.splice(index, 1))
}
return (
<div>
<button onClick={createNewLine}>{t('oefeningAanvragenDagboek.addQuestion')}</button>
{questionList}
</div>
)
}
Use functional setState as HandleDelete has closure on questionList
setQuestionList(questionList => questionList.splice(index, 1))
Both state and props received by the updater function are guaranteed to be up-to-date.
setState() in classes
You can pass event handler from container to child and then invoke event handler from client.
For example, let's say I have an app displaying list of items and each item have a delete button to remove them from the list. In this case, parent component will supply list of items and event handler to child component and then, child will be responsible for rendering and calling event handler.
Take a look at this codesandbox from which I am pasting following code:
import React, { useState } from "react";
import "./styles.css";
export function List(props) {
return (
<div>
{props.items.map((i, idx) => (
<div class="item" key={idx}>
{i} <span onClick={() => props.onDelete(idx)}>X</span>
</div>
))}
</div>
);
}
export default function App() {
const [items, setItems] = useState([
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6"
]);
const deleteItem = (index) => {
if (index >= 0 && index < items.length) {
const newItems = items.slice();
newItems.splice(index, 1);
setItems(newItems);
}
};
return (
<div className="App">
<List items={items} onDelete={deleteItem}></List>
</div>
);
}
Primarily addressing the issue on the OP's comments section, at the time of this writing which was given a bounty in addition to the question.
The SandBox with the issue: https://codesandbox.io/s/charming-architecture-9kp71?file=/src/App.js
Basically, the solution to this issue is to perform all operations on the parameter of the callback function. In the case of the sandbox issue I linked above, if you look at removeToast on the code below, the operations are being done on the list array.
Code with the issue:
export default function App() {
const [list, setList] = useState([]);
const removeToast = (id) => {
const newList = list.filter(({ toastId }) => toastId !== id);
setList([...newList]);
};
const addToast = () => {
const toastId = Math.random().toString(36).substr(2, 9);
const newList = [
...list,
{
toastId,
content: (
<>
<button onClick={() => removeToast(toastId)}>Hi there</button>
Hello {toastId}
</>
)
}
];
setList([...newList]);
};
return (
<>
<button onClick={addToast}>Show Toast</button>
<Toaster removeToast={removeToast} toastList={list} />
</>
);
}
However since removeToast has a closure on list, we need to do the filtering on the previous state which is, again, accessible via the first parameter of the callback of setState
The fix:
const removeToast = (id) => {
setList((prev) => {
return prev.filter(({ toastId }) => toastId !== id);
});
};
The Solution: https://codesandbox.io/s/suspicious-silence-r1n41?file=/src/App.js

Opening specific components through onClick event - REACT.JS

I have some data coming from Firebase and I am printing them into the app as lists. However, at first I just want to print a header for each list and when clicking on these headers their specific lists must be shown.
I am able to hide and show the lists by clicking, but this is happening for all lists and not only for the target one.
What I am doing is the classic way, to set a state as false (open) and make it toggles after a click. The component is shown if open is true and hidden if it is false. The onClick function is in Clientes, the first child.
I have a container based on class and two functional components.
To be more specific, the container just receive the data and send it to the Clientes component as an object of arrays. Each array is a list and for each one of them an Orcamentos component is created, lastly all items of each list are rendered inside of its respective Orcamentos.
How could I make just the target list opens after a click?
*Container ClientesControls:
class ClientesControls extends Component {
state = {
clientes: null,
retorno: false,
open: false
}
openOrcamentosHandler = () => {
let open = this.state.open;
this.setState({open: !open})
}
componentDidMount() {
axios.get('/clientes.json')
.then(res => {
this.setState({clientes: res.data, retorno: true})
})
.catch(error => console.log(error))
}
render() {
let inserirClientes = <div>Carregando...</div>
if (this.state.retorno) {
inserirClientes = (
Object.keys(this.state.clientes)
.map(key => <Clientes
key={Math.random()}
clienteInfo={this.state.clientes[key]}
open={this.state.open}
openHandler={this.openOrcamentosHandler}
/>)
)
}
return (
<div>
{ inserirClientes }
</div>
);
}
}
*Child Clientes:
const clientes = props => {
return (
<div>
{
Object.keys(props.clienteInfo)
.map(key => {
return (
<div key={Math.random()} onClick={props.openHandler}>
<Orcamentos orcamentosInfo={props.clienteInfo[key]} open={props.open}/>
</div>
)})
}
</div>
);
};
*Child Orcamentos:
const orcamentos = props => {
let nome = Object.keys(props.orcamentosInfo)
.map(k => props.orcamentosInfo[k].nomeCliente);
return (
<div>
<h4>{nome[0]}</h4>
{
Object.keys(props.orcamentosInfo)
.map(k => <p key={Math.random()} >{ props.open ? props.orcamentosInfo[k].data : null}</p>)
}
</div>
);
}
The child Orcamentos components need to control their own open state. The way you have structured it they are both taking the same state as a prop from the parent ClientesControls, and clicking either child component refers to the same handler which updates that state, so of course they are both being activated.
const orcamentos = props => {
const [open, setOpen] = React.useState(false);
let nome = Object.keys(props.orcamentosInfo)
.map(k => props.orcamentosInfo[k].nomeCliente);
return (
<div onClick={() => setOpen(!open)}>
<h4>{nome[0]}</h4>
{
Object.keys(props.orcamentosInfo)
.map(k => <p key={Math.random()} >{ open ? props.orcamentosInfo[k].data : null}</p>)
}
</div>
);
}

Can i set state in parent from child using useEffect hook in react

I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...
const Filter = ({ setFilter }) => {
const [cycling, setCycling] = useState(true);
const [diy, setDiy] = useState(true);
useEffect(() => {
setFilter({
cycling: cycling,
diy: diy
});
}, [cycling, diy]);
return (
<Fragment>
<Row>
<Col>
<Button block onClick={() => setCycling(!cycling)}>cycling</Button>
</Col>
<Col>
<Button block onClick={() => setdIY(!DIY)}>DIY</Button>
</Col>
</Row>
</Fragment>
);
};
In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.
const ItemDashboard = () => {
const [filter, setFilter] = useState(null);
useEffect(() => {
console.log('on mount');
}, []);
useEffect(() => {
console.log('filter');
}, [filter]);
return (
<Container>..
<Filter setFilter={setFilter} />
</Container>
);
}
I'm guessing, you're looking for the way to lift state up to common parent.
In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.
The following live-demo demonstrates the concept:
const { render } = ReactDOM,
{ useState } = React
const hobbies = ['cycling', 'DIY', 'hiking']
const ChildList = ({list}) => (
<ul>
{list.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
const ChildFilter = ({onFilter, visibleLabels}) => (
<div>
{
hobbies.map((hobby,key) => (
<label {...{key}}>{hobby}
<input
type="checkbox"
value={hobby}
checked={visibleLabels.includes(hobby)}
onChange={({target:{value,checked}}) => onFilter(value, checked)}
/>
</label>))
}
</div>
)
const Parent = () => {
const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
onChangeVisibility = (hobby,visible) => {
!visible ?
setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
setVisibleHobbies([...visibleHobbies, hobby])
}
return (
<div>
<ChildList list={visibleHobbies} />
<ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:
const NOOP = () => {};
// Filter
const Child = ({ onChange = NOOP }) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
onChange(counter);
}, [counter, onChange]);
const onClick = () => setCounter(c => c + 1);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Increase</button>
</div>
);
};
// ItemDashboard
const Parent = () => {
const [value, setState] = useState(null);
useEffect(() => {
console.log(value);
}, [value]);
return <Child onChange={setState} />;
};

Categories