React: Pass function with a given argument to child - javascript

My goal is to create a function in a parent component and pass it to the child component, so I can update state from child to parent.
However, I would like to determine one argument in the parent component already.
Is there any way of doing so? See my example below.
Let´s assume I have the following code
const Parent = ( props ) => {
const [counter, setCounter] = useState(0);
function updateFunc(type, id) {
let obj = {type : "", id : id}
if (type == "red"){
obj.type = 'RED_FLAG';
} else {
obj.type = 'ELSE_FLAG';
}
return obj;
}
return (
<>
// HERE I WOULD LIKE TO PASS ALSO A "RED" ARGUMENT -> IS THIS POSSIBLE?
<Child update = {update}
</>
)
}

You can do that by making an additional function:
<Child update={(id) => {
return updateFunc("red", id);
}}/>

There is a technique called currying. For example, you could have a function that takes the type as an argument and returns a function that takes the id as an argument, which finally returns the object.
const Parent = (props) => {
const [counter, setCounter] = useState(0);
function updateFunc(type) {
return (id) => {
let obj = { type: "", id: id }
if (type == "red") {
obj.type = 'RED_FLAG';
} else {
obj.type = 'ELSE_FLAG';
}
return obj;
}
}
return (
<>
<Child update={update("red")}
</>
)
}

Related

How to map an array of objects in React useReducer function

I have an array of objects in my React state. I want to be able to map through them, find the one I need to update and update its value field. The body of my request being sent to the server should look like:
{ name: "nameOfInput", value:"theUserSetValue" type: "typeOfInput" }
What I thought would be simple is causing me some heartache. My reducer function calls, and I hit the "I AM RUNNING" log where it then jumps over my map and simply returns my state (which is empty). Please note that I NEVER see the "I SHOULD RETURN SOMETHING BUT I DONT" log.
NOTE: I have learned that I could be simply handingling this with useState
function Form(props) {
const title = props.title;
const paragraph = props.paragraph;
const formBlocks = props.blocks.formBlocks
const submitEndpoint = props.blocks.submitEndpoint || "";
const action = props.blocks.action || "POST";
const formReducer = (state, e) => {
console.log("I AM RUNNING")
state.map((obj) => {
console.log("I SHOULD RETURN SOMETHING BUT I DONT")
if (obj.name === e.target.name) {
console.log("OBJ EXISTS", obj)
return {...obj, [e.target.name]:obj.value}
} else {
console.log("NO MATCH", obj)
return obj
}
});
return state
}
const [formData, setFormData] = useReducer(formReducer, []);
const [isSubmitting, setIsSubmitting] = useState(false);
=====================================================================
Where I am calling my reducer from:
<div className="form-block-wrapper">
{formBlocks.map((block, i) => {
return <FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={setFormData}
/>
})}
</div>
Issues
When using the useReducer hook you should dispatch actions to effect changes to the state. The reducer function should handle the different cases. From what I see of the code snippet it's not clear if you even need to use the useReducer hook.
When mapping an array not only do you need to return a value for each iterated element, but you also need to return the new array.
Solution
Using useReducer
const formReducer = (state, action) => {
switch(action.type) {
case "UPDATE":
const { name, value } = action.payload;
return state.map((obj) => obj.name === name
? { ...obj, [name]: value }
: obj
);
default:
return state;
}
};
...
const [formData, dispatch] = useReducer(formReducer, []);
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={e => dispatch({
type: "UPDATE",
payload: {...e.target}
})}
/>
);
})}
Using useState
const [formData, setFormData] = useState([]);
...
const changeHandler = e => {
const { name, value } = e.target;
setFormData(data => data.map(obj => obj.name === name
? { ...obj, [name]: value }
: obj
));
};
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={changeHandler}
/>
);
})}
I have come to understand my problem much better now and I'll update my question to reflect this.
As the user interacted with an input I needed to figure out if they had interacted with it before
If they did interact with it before, I needed to find that interaction in the state[] and update the value as required
If they didn't I needed to add an entirely new object to my forms state[]
I wrote two new functions, an AddObjectToArray function and an UpdateObjectInArray function to serve these purposes.
const handleFormInputChange = (e) => {
const { name, value, type } = e.target;
const addObjectToArray = (obj) => {
console.log("OBJECT TO BE ADDED TO ARRAY:", obj)
setFormData(currentArray => ([...currentArray, obj]))
}
const updateObjectInArray = () => {
const updatedObject = formData.map(obj => {
if (obj.name === name) {
//If the name matches, Update the value of the input
return ({...obj, value:value})
}
else {
//if no match just return the object as is
return obj
}
})
setFormData(updatedObject)
}
//Check if the user has already interacted with this input
if (formData.find(input => input.name === name)) {
updateObjectInArray()
}
else {
addObjectToArray({name, value, type})
}
}
I could get more complicated with this now and begin to write custom hooks that take a setState function as a callback and the data to be handled.

React: props are undefined, even though they should be

my props in the children class is supposed to be an array of Event objects.
I am checking beforehand, if the array is empty in App.js like this:
function App() {
class Event {
constructor(id, title, date){
this.id = id;
this.title = title;
this.date = date;
}
}
const [events, setEvents] = useState([])
const [ids, setIds] = useState([])
const [safedIds, setSafedIds] = ([])
const [eventsPrep, setEventsPrep] = useState([Event])
useEffect(() => {
fetch('https://someAPI.com')
.then(response => response.json())
.then(
res => {setEvents(res);
console.log(res);
})
.catch(err => console.log(err))
.then(handleIncomingData())
//.then(console.log("was here"))
}, [])
function handleIncomingData () {
if(events.length > 0) {
events.forEach(event => {
ids.push(event["_id"]);
let date = new Date(event["date"]);
eventsPrep.push(new Event(event["_id"], event["title"], date.toDateString()))
})
}
}
return (
<>
<Navbar/>
{eventsPrep.length > 0 ? <Home events={eventsPrep}/> : <></>}
</>
);
}
export default App;
but whenever I try to reach the props in the child component it is considered undefined.
My child component:
import React from 'react'
import SingleEvent from '../../event/SingleEvent'
export const Home = (props) => {
console.log(props.events)
return (
<>
{props?.events
? props.events.forEach((event) => {
console.log('was here 2');
return <SingleEvent title={event.title} start={event.date} />;
})
: 'no upcomming events'}
</>
);
}
Even if I only pass a string down, it is still undefined.
Thanks for help!
In your useEffect() you update events via setEvents(res) and call handleIncomingData() after that.
In handleIncomingData() you use events, but it will still hold the value from previous render / from the initialization as setEvents(res) will not change events immidiately. You can console.log(events) inside that function to investigate this.
Instead you can pass res into handleIncomingData() and use it instead of events inside that function. So in you useEffect you would have:
.then(response => response.json())
.then(
res => {
setEvents(res);
handleIncomingData(res);
})
In addition in handleIncomingData() use setEventsPrep(...) instead of eventsPrep.push(), as mentioned in the comment.
You are returning 2 return statments in the component.
Only need to return 1 return statement in component
export const Home = (props) => {
return (
<>
{props?.events && Object.values(props.events).length > 0
? props.events.forEach((event) => {
console.log('was here 2');
return <SingleEvent title={event.title} start={event.date} />;
})
: 'no upcomming events'}
</>
);
};
Also to check whether eventsPrep has a length > 0 then try it this way if eventsPrep is an object
eventsPrep && Object.values(eventsPrep).length > 0

React state doesn't refresh value

I'm now learning React and I have a problem with re-rendering component.
App.js code:
function App() {
const [expenses, setExpenses] = useState(INITIAL_EXPENSES);
const addNewExpenseHandler = (expense) => {
setExpenses((prevState) => {
return [expense, ...prevState];
}, changeYearHandler(filteredYear));
};
const filterExpenses = (expenses, year) => {
const newFilteredExpenses = expenses.filter((expense) => {
if (String(expense.date.getFullYear()) === year) {
return expense;
}
});
return newFilteredExpenses;
};
const [filteredYear, setFilteredYear] = useState('2019');
const [filteredExpenses, setFilteredExpenses] = useState(
filterExpenses(expenses, filteredYear)
);
const changeYearHandler = (value) => {
setFilteredYear(
value,
setFilteredExpenses(() => {
const newValue = filterExpenses(expenses, value);
return newValue;
})
);
};
return (
<>
<NewExpense onAddNewExpense={addNewExpenseHandler} />
<ExpenseFilter expenses={expenses} />
<ShowExpenses
onChangeYear={changeYearHandler}
data={filteredExpenses}
/>
</>
);
}
export default App;
The problem is that filteredExpenses isn't up-to-date. It's always retarded and it's the previous state. I was trying to call a changeYearHandler in callback of setExpenses and setFilteredExpense inside setFilteredYear but it's still doesn't work and I don't know why.
It feels like you're using a roundabout way to filter your expenses. What about just creating a memoized version of a filteredExpenses directly, using useMemo()?
const filteredExpenses = useMemo(() => {
return expenses.filter((expense) => {
if (String(expense.date.getFullYear()) === filteredYear) {
return expense;
}
});
}, [expenses, filteredYear]);
The dependency array will ensure that whenever expenses or filteredYear changes, then filteredExpenses will recompute and return a new filtered array (that is subsequently cached).

How to remove a div on component unmount using react?

I want to remove a div element on component unmount using react.
I create a div with id portal in usecallback method. I want to remove it on component unmount how can I do it.
below is my code,
function Dialog () {
const [portal, setPortal] = React.useState<HTMLDivElement | null>(
(document.getElementById('portal') as HTMLDivElement) || null
);
const createPortalIfNotExists = React.useCallback(() => {
if (portal === null) {
const el = document.createElement('div');
el.id = 'portal';
document.body.appendChild(el);
setPortal(document.getElementById(
'portal'
) as HTMLDivElement);
}
}, [portal]);
createPortalIfNotExists();
if (portal === null) {
return null;
}
return ReactDOM.createPortal(
<>
<div>
{children}
</div>
</>,
portal
);
}
I have two questions here, can useEffect be instead of usecallback in this case. and how to remove the div with id portal on component unmount.
Could someone help me with this?
By using the React.useEffect internal return method, you can do it. for example:
function Dialog () {
const [portal, setPortal] = React.useState<HTMLDivElement | null>(
(document.getElementById('portal') as HTMLDivElement) || null
);
const createPortalIfNotExists = React.useCallback(() => {
if (portal === null) {
const el = document.createElement('div');
el.id = 'portal';
document.body.appendChild(el);
setPortal(document.getElementById(
'portal'
) as HTMLDivElement);
}
}, [portal]);
React.useEffect(() => {
createPortalIfNotExists();
return () => {
const portalElement = portal || document.getElementById('portal')
portal.remove();
}
}, [])
if (portal === null) {
return null;
}
return ReactDOM.createPortal(
<>
<div>
{children}
</div>
</>,
portal
);
``

Child component props are empty though they're passed from a parent component

I try to get the value of openMenu from Parent to Child function but the props.value is empty in Child function and I don´t understand why.
function Child({ routes }, props) {
return (
<div>{props.value}</div> /*This is empty*/
)
}
function Parent() {
const [isOpen, setOpen] = useState({
isOpen: false
});
const handleClick = e => {
e.preventDefault();
setOpen(isOpen => !isOpen);
if(isOpen === true) {
const openMenu = 'open';
return <Child value={openMenu}/>;
}
else {
const openMenu = 'close';
return <Child value={openMenu} />
}
};
}
I want to get the value of openMenu, either open or close, to the Child component.
Props is the first argument in a function component.
function Child(props) {
return (
<div>{props.value}</div>
)
}
If you are trying destructure routes from props you can use the rest pattern (...) to get all other props.
function Child({ routes, ...props }) {
return (
<div>{props.value}</div>
)
}
maybe you should have some insight with sending props and destructuring them,
// suppose props = { val1, val2, val3 }
const Parent = (props) => <Child {...props} />
// in such case you can destructure the values in the paranthesis itself,
const child = ({ val1, val2, val3 }) => {
<div>
<div>{val1 && val1}</div>
<div>{val2 && val2}</div>
<div>{val3 && val3}</div>
<div>
}
// which is same as,
const child = (props) => {
const { val1, val2, val3 } = props
//rest of the code..
}
also you can send multiple props as,
class Parent extends Component {
// ..
function1 = () => {..}
function2 = () => {..}
render () {
//suppose this.props = { match, dispatch, list, ..}
const value1 = ..
const value2 = ..
return <Child {...{ function1, function2, value1, value2}} {...this.props} />
}
and desctructure them in child as,
const Child = ({
function1,
function2,
value1,
value2,
match,
dispatch,
list,
}) => {
return (
<div> .. </div>
}
now you can see how handy it is, and you won't stumble again..
Note: .. (double dots are fill in the blanks, you know)

Categories