React state not updating props (using next.js) - reactjs

Trying to update the state of a react-date-range object (next.js) in a component with useState / setState. Not sure what I'm doing wrong, but I keep getting an error that dateRange.startDate is undefined.
import {useState, React} from 'react'
import { DefinedRange } from 'react-date-range'
import PropTypes from 'prop-types'
export const DataCard = ({...props}) => {
let [dateRange, setDateRange] = useState([{
startDate: props.dateRange.startDate,
endDate: props.dateRange.endDate,
key: 'selection'
}])
const updateDateRange = (dateRange) => {
props.dateRange = {startDate: dateRange.startDate, endDate: dateRange.endDate}
setDateRange([props.dateRange.startDate, props.dateRange.endDate])
}
return (
<div>
<div>
<button>
<DefinedRange onChange={item=> {updateDateRange([item.selection])}} ranges={dateRange}/>
</button>
</div>
</div>
)}
DataCard.propTypes = {
header: PropTypes.string,
dateRangeComponent: PropTypes.element,
dateRange: PropTypes.object
}
DataCard.defaultProps = {
header: "Graph Name",
dateRange: {startDate: new Date(), endDate: new Date()}
}

Your dispatch code is a bit problematic.
const updateDateRange = (dateRange) => {
console.log(dataRange)
props.dateRange = {startDate: dateRange.startDate, endDate: dateRange.endDate}
setDateRange([props.dateRange.startDate, props.dateRange.endDate])
}
I suspect two things therefore i suggest to add a console.log.
Possibility 1
Maybe your input isn't an array, instead it's a e from event handler. So first need to confirm that by printing it onto screen.
Possibility 2
When you change something, you'd like to take changes from your input, not prop. So you need to think of why you are using props in the dispatch. Props are coming from parent, not children. Use local variable instead.
Bonus
Normally we don't render stuff inside a <button> element. It might be irrelevant to your problem, but might complicate your other parts of work. Use <div> instead.

Your initial status is an array of 1 object but when you setDateRange, you provide an array of two objects
Your init data:
[{
startDate: props.dateRange.startDate,
endDate: props.dateRange.endDate,
key: 'selection'
}] // an array of a object
setDateRange([props.dateRange.startDate, props.dateRange.endDate]) // setDateRange([a, b]), array of two objects
It should be:
setDateRange((prevState) => ({ startDate: props.startDate, endDate: props.endDate, key: prevState.key }));

Related

React context not updated in props.children

I have a requirement where I need to show a date range picker and the selected date range should be made available to all the components. I'm trying to use React Context API to get this to work.
Using React.useContext() I am able to see the updated value in all components except whatever is rendered in {props.children}
Below is the approach I've tried out.
Context.tsx
export const DateRangeContext = React.createContext<DateRangeContextType>({
dateRange: {
startDate: new Date(),
endDate: new Date(),
},
setDateRange: (dateRange: DateRange) => {}
});
DateRangePicker.tsx
...
const { dateRange, setDateRange } = React.useContext(DateRangeContext);
...
const applyDateRange = (newDateRange: DateRange) => {
const newDateRange = {
startDate: newDateRange.startDate,
endDate: newDateRange.endDate,
};
setDateRange(newDateRange);
};
PageLayout.tsx
...
<DateRangeContext.Provider value={{dateRange, setDateRange}}>
<Header/>
{props.children}
<Footer/>
</DateRangeContext.Provider>
Dashboard.tsx
...
const { dateRange, setDateRange } = React.useContext(DateRangeContext);
...
<PageLayout>
<Card>Card content goes here</Card>
</PageLayout>
...
In PageLayout.tsx, if I directly use <Dashboard/> in place of {props.children} I see the updated context value in Dashboard.tsx.
But when Dashboard component is passed to {props.children}, the context value doesn't get updated and I always see the initialized value.
Not sure why but it worked after moving the provider to the PageLayout component's parent.
MainLayout.tsx
...
const [dateRange, setDateRange] = React.useState<DateRange>();
...
<DateRangeContext.Provider value={{dateRange, setDateRange}}>
<PageLayout/>
</DateRangeContext.Provider>

react-hook-form v7 `watch` type in typescript?

I am confused about how to declare a type for watch as a prop. Here is an example:
// pages/FormContainer.tsx
import { useForm, useWatch } from "react-hook-form";
import FormView from "./FormView";
interface FormInputs {
firstName: string;
lastName: string;
age: number;
}
const FormContainer = () => {
const { control, register } = useForm<FormInputs>({
defaultValues: {
firstName: "Jon",
lastName: "Doe",
age: 24
}
});
const formData = useWatch({ control });
return <FormView register={register} formData={formData} />;
};
export default FormContainer;
// pages/FormView.tsx
import { UseFormReturn } from "react-hook-form";
interface Props {
register: UseFormReturn["register"];
formData: UseFormReturn["watch"];
}
const FormView = ({ formData }: Props) => {
return (
<div>
<h1>
My Name: {formData.firstName} {formData.lastName}
</h1>
<h2>I am {formData.age} years old</h2>
</div>
);
};
export default FormView;
The thing is typescript shows me an error on this formData={formData} prop
Here I provided a sandbox to be clear on what I mean
UseFormReturn["watch"] will give you back the type of the watch function itself - that is, the type of this function:
// `watch` here is of type `UseFormReturn["watch"]
const { control, register, watch } = useForm<FormInputs>({
// ...
});
For reference, the return type of this watch function is UnpackNestedValues<TFormValues> .
But - you're not using that function, you're using useWatch which returns a subtly different UnpackNestedValue<DeepPartialSkipArrayKey<TFieldValues>>. So, you could change your form type to that:
import { UnpackNestedValue, UseFormReturn, DeepPartialSkipArrayKey } from "react-hook-form";
import { FormInputs } from "./FormContainer";
interface Props {
register: UseFormReturn["register"];
formData: UnpackNestedValue<DeepPartialSkipArrayKey<FormInputs>>
}
Alternatively, since your form object (at least in this example) is very "simple" (it's just key/value pairs effectively), you could use the simpler type declaration of Partial<FormInputs> which is functionally equivalent in this case.
Here's an updated example for you.
I found 2 things that will improve and fix your problem.
Use the method watch returned from useForm instead of useWatch for getting full form data. You can use useWatch for individual form input changes without impacting the root component's render.
// Empty watch function will return the full form data.
const formData = watch()
watch returns the full form data, so the prop in the FormView component will be FormInputs.
interface Props {
register: UseFormReturn["register"];
formData: FormInputs;
}
This should fix your TS errors.

No using class Component , only function in React

Hello I am just learning react and I am looking at tutorials but in the version that was installed, react is no longer using the classes, it only appears functions and I would like to continue that way if possible, but I have a problem with where to change the name in this part with a click but it does not allow me to access persons(const), how could I do it?
import Person from './Person/Person'
function App() {
const persons = {
persons: [
{ name: 'Jose', age: 32},
{ name: 'Gabriel', age: 2}
]
}
const switchNameHandler = () => {
persons({
persons: [
{ name: 'Jose Fernando', age: 32},
{ name: 'Gabriel', age: 2}
]
})
}
return (
<div className="App">
<h1>Hi, I'm a React App!</h1>
<button onClick={switchNameHandler}> Switch Name</button>
<Person name={persons.persons[0].name} age={persons.persons[0].age}/>
<Person name={persons.persons[1].name} age={persons.persons[1].age}> I like play</Person>
</div>
);
}
export default App;
How could I fix the switchNameHandler part?
I know that if I use classes I can access this.setPersons, but is there any way to access without using classes?
You need to use the useState hook. All hooks return two things in a de-structured array (1) the state value and (2) the function to set that state value. and the value you put in the useState() function is the initial value.
For example:
const [name, setName] = useState("Ben");
Here the initial value of name is "Ben". If I wanted to change that value, I could use the setName function and do something like this setName("Jerry");. Now the value of name is "Jerry".
The biggest difference between the setter (in this case setName) and this.setState (in a class component), is that this.setState remembers and spreads the previous state automatically for you if you don't explicitly define it. With hooks, you have to do that your self:
For example:
const [person, setPerson] = useState({ name: "", id: "", age: "" });
If I have this state, and want to edit just the name of the person, I have to remember to spread the previous state object every time I update state, by using a callback function - where the parameter in the callback is the previous state:
// just updating name
setPerson(prevPerson => ({ ...prevPerson, name: "Bob" }));
Here, the only thing that changes was the "name" value and everything else stayed the same:
Result: { name: "Bob", id: "", age: ""}
Check out the react documentation for more tips and examples: https://reactjs.org/docs/hooks-state.html

React Table 7 - How to change the state of an individual button created dynamically by the React-Table API

I am using React Table 7 to create a table with the first cell of each row being a ChecOut/CheckIn Button. My goal is to create a tool where people can CheckOut and CheckIn equipment. My React Table CodeSandbox
The idea is that when a user clicks the button, it will change from "CheckOut" to "CheckIn" and vise versa. It will also change the color of the row when Checked out. I was able to accomplish this with JQuery (code is in the sandbox under the App() functional component) but want to avoid that and am sure it's easily doable in react.
My issue is changing the state of an individual button and which functional component to define it in. The <Button> elements are created dynamically by the React-Table, defined in the columns object under the App functional component.
{
Header: "CheckIn/Out",
Cell: ({ row }) => (
<Button
key={row.id}
id={row.id}
onClick={e => checkClick(e)}
value={checkButton.value}
>
{checkButton.value}
</Button>
)
}
I tried passing a function to onChnage attribute on the <Button> element. In that function I updated the state of the <Button> to change the string value from "CheckOut" to "CheckIn" but that does not work. At first, that changed the name (state) of All the <Button> elements. Not sure if the state will live in the App() component or the TableRe() functional component. Or do I need to use state at all?
I basically built this on top of the editable table code available on the React-Table website React-Table Editable Data CodeSandbox , examples section React-Table Examples .
Any help is much appreciated!
data.js
import React from "react";
// export default random => {
// let arr = [];
// for (let i = 0; i < random; i++) {
// arr.push({
// first: chance.name(),
// last: chance.last(),
// birthday: chance.birthday({ string: true }),
// zip: chance.zip()
// });
// }
// return arr;
// };
const temp_data = [
{
firstName: "johny",
lastName: "Bravo",
age: "23",
visits: "45",
progress: "complete",
status: "all done"
},
{
firstName: "johny",
lastName: "Bravo",
age: "23",
visits: "45",
progress: "complete",
status: "all done"
}
];
const ArrayData = () => {
const data = temp_data.map(item => {
return {
firstName: item.firstName,
lastName: item.lastName,
age: item.age,
visits: item.visits,
progress: item.progress,
status: item.status
};
});
return data;
};
// Do not think there is any need to memoize this data since the headers
// function TempData() {
// const data = React.useMemo(ArrayData, []);
// return data;
// }
export default ArrayData;

How to write Test cases for useEffect Hook in React using Jest & Enzyme?

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const InputComponent = ({ item, data }) => {
const [value, setValue] = useState('');
// Binding values for Edit based on unique Key
useEffect(() => {
if (data && data[item.field] && data[item.field] !== 'undefined') {
setValue(data[item.field]);
}
}, [data,item.field]);
//On change setting state
const setState = e => {
setValue(e.target.value);
};
return (
<div className='Input-Containter' data-test='InputBox-tbl-evt'>
<input
data-test='input-ele'
type='text'
value={value}
onChange={e => setState(e)}
/>
</div>
);
};
InputComponent.propTypes = {
item: PropTypes.object,
data: PropTypes.object
};
InputComponent.defaultProps = {
data: {
id: '1',
name: 'd'
},
item:{
field: 'id',
}
};
export default InputComponent;
Can someone help me How to test for setValue() in useEffect
-> Updated complete code for this Component
-> Component will take some data for binding values into input element & in the same
component we can edit values in it as-well.
First, let's take closer look onto useEffect section. What does it mean?
if any of prop is changed
and combination of new values are given some meaningful value(not undefined)
we initialize input's value based on those props even if we have to override custom value
How to test that? Changing prop(s) and validating input's value.
Based on that we may have up to 3(changed only first prop, only second or both) * 2 (if result is undefined or not) * 2 (if there has been already custom value provided and stored in useState or not) = 12. But I believe exhaustive testing is not a good way. So I'd put most checks in single test case:
it('re-init value for nested input after props changes', () => {
const wrapper = mount(<InputComponent />);
function getInput(wrapper) {
return wrapper.find("input").prop("value");
}
expect(getInput(wrapper).props("value")).toEqual("1"); // based on default props
getInput(wrapper).props().onChange({ target: {value: "initial"} }); // imitating manual change
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({data: {a: "some-a", b: "undefined"} });
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: { field: "c" } }); // data[item.field] is undefined
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: {field: "b" } }); // data[item.field] is "undefined"
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: {field: "a" } }); // data[item.field] is meaningful
expect(getInput(wrapper).props("value")).toEqual("some-a");
});
As for getValue helper - it's needed cause we cannot just memoize input element itself like:
const wrapper = mount(...);
const input = wrapper.find("input");
...
expect(input.prop("value")).toEqual();
...
expect(input.prop("value")).toEqual();
Details can be found in Enzyme's docs. Or just know we need to re-run find after any update.
Also beware Enzyme's setProps does not replace current props but update them by merging(as well as React's setState does with state).

Resources