Change the input initial value of controlled component in React - html

I have this input component
const FOO = props => {
const [inputValue, setInputValue] = useState(
props.editState ? props.initialValue : ""
);
const setSearchQuery = (value) => {
setInputValue(value);
props.onSearch(value);
};
return (
<input
placeholder="Select ..."
onChange={(e) => {
setSearchQuery(e.target.value);
}}
value={inputValue}
/>
)}
I'm using it like this
const BAR = props => {
const [fetchedData, setfetchedData] = useState({
value : "" // to get rid of can't change controlled component from undefined error
});
const params = useParams();
//request here to get fetchedData
return(
<FOO
onSearch={(value) => {
searchSomethingHandler(value);
}}
initialValue={
params.ID
? fetchedData.value
: ""
}
editState={params.ID ? true : false}
/>
)}
I need to set the initial value of the fetched data into the input so the user could see the old value and edit it, if I pass the data (fetchedData) as props it works perfectly,
but if I get the data through API it wont set the value cause it's empty at the first render,
how can I solve this please?

You'll probably want to make use of the useEffect hook to run code when a value updates.
In FOO:
const FOO = props => {
// ...
useEffect(() => {
// This hook runs when props.initialValue changes
setInputValue(props.initialValue);
}, [props.initialValue]);
// ...
};
You can leave BAR the same as before, I think. Though, I would put the request to the API inside a useEffect hook with an empty dependency array so you're not querying it on every render.

Related

React: useState() onChange: Doesn't log the first letter when I type the first letter in the input field. How can I set state asynchronously?

Let's leave the filteredBookings for now. My problem is that if I use e.target.value directly instead of assigning it to a state, it works perfectly (So I dont need this solution). As soon as I set the search term to be a state, then it doesn't take listen to my first click. In case I need to search for 'banana', it will start printing the whole word after 'a' and not after 'b'. And the first log in the console is not undefined, but just empty.
The reason is probably because the setting has to be done asynchroniously. But how? :/
const [searchWord, setSearchWord] = useState("");
const [filteredBookings, setfilteredBookings] = useState([]);
const handleFilteredItems = ({ target }) => {
setSearchWord(target.value);
console.log(searchWord);
const filteredBookings = bookings.filter((booking) => {
return booking.customerFirstName
.toLowerCase()
.includes(searchWord.toLowerCase());
});
// setfilteredBookings(filteredBookings);
};
First and second lines in the console:
The state will be set at the end of the current function. So your includes is still using the previous state. You could do like this rather:
const handleFilteredItems = ({ target }) => {
const { value } = target
setSearchWord(value);
const filteredBookings = bookings.filter((booking) => {
return booking.customerFirstName
.toLowerCase()
.includes(value.toLowerCase());
});
setfilteredBookings(filteredBookings);
};
EDIT Based on your comments.
I think the problem is the way you approach the hook. You should get the filteredBookings outside of the handleFilteredItems, as such:
const handleFilteredItems = ({ target }) => {
const { value } = target
setSearchWord(value);
};
const filteredBookings = bookings.filter((booking) => {
return booking.customerFirstName
.toLowerCase()
.includes(searchWord.toLowerCase());
});
and remove this line:
const [filteredBookings, setfilteredBookings] = useState([]);
This will update the filteredBookings value at every state change.

How create a new input field row by clicking a button reactjs

I am building an ecommerce app and I want to collect the users different phone numbers and address.
I want to create a new field where the user types new phone number and address
I tried using state to accomplish the task but I am geting error
TypeError: contactInfo.phoneInputs is undefined
const RegisterModal = ({openRegisterModal, setOpenRegisterModal}) => {
const [contactInfo, setContactInfo] = useState({
phoneInputValue : {},
phoneInputs: [],
addressInputValue : {},
addressInputs: [],
})
console.log(contactInfo)
const addContact = (e) => {
e.preventDefault()
const contactsphoneInfo = "phoneNumber";
const contactsAddressInfo = "address";
let phoneInputBox =
<Input name={contactsphoneInfo} star="false" label="PhoneNumber" type="text" className="col-md-6" />
let addressInputBox =
<Input name={contactsAddressInfo} star="false" label="address" type="text" className="col-md-6" />
setContactInfo(contactInfo.phoneInputs.push({phoneInputBox}))
console.log(contactInfo)
}
return (
<div>
{
contactInfo.phoneInputs.map(input => input)
}
button onClick={addContact}>Add</button>
</div>
)
}
export default RegisterModal
How do I fix this error
link to codesandbox
https://codesandbox.io/s/distracted-morse-s6zn0
your setstate is a bit fishy, I believe
setContactInfo({...contactInfo, phoneInputs: [...contactInfo.phoneInputs,phoneInputBox ]});
This should work . but i recommend you try for more clean code .
and console.log(contactInfo) before render if you check itll be undefined if you want to check the inital value still use useEffect then log it .

Mapping from JSON Get Request. Undefined

I am trying to connect to the USDA Food Central database using an API.
let uri = encodeURI(`https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${MY_API_KEY}&query=${search}`)
I want to use the API to map certain fields.
class AddFoodItemList extends Component {
static contextType = AddFoodContext;
render() {
const listItems = this.context.FoodSearch.map((foods) =>
<FoodItem
key={foods.brandOwner}
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
);
return (
<div id="AddFoodItemList">
{listItems}
</div>
);
}
}
export default AddFoodItemList;
The returned JSON is this screenshot attached:
Returned JSON
I am getting an error, TypeError: Cannot read property 'map' of undefined.
Why do you think this is the case? Any sort of help or suggestions are appreciated!
You are attempting to access a property FoodSearch on the value of your AddFoodContext provider. The error tells you that this property is undefined. If the object in your screenshot is the value of your context then you want to access the property foods instead. This is an array whose elements are objects with properties brandOwner and fdcId.
On your first render this data might now be loaded yet, so you should default to an empty array if foods is undefined.
It's honestly been a long time since I've used contexts in class components the way that you are doing it. The style of code is very dated. How about using the useContext hook to access the value?
const AddFoodItemList = () => {
const contextValue = useContext(AddFoodContext);
console.log(contextValue);
const listItems = (contextValue.foods || []).map((foods) => (
<FoodItem
key={foods.fdcId} // brandOwner isn't unique
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
));
return <div id="AddFoodItemList">{listItems}</div>;
};
Here's a complete code to play with - Code Sandbox Link
const MY_API_KEY = "DEMO_KEY"; // can replace with your actual key
const getUri = (search) => `https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${MY_API_KEY}&query=${encodeURIComponent(search)}`;
const AddFoodContext = createContext({});
const FoodItem = ({ brandOwner, fdcId }) => {
return (
<div>
<span>{fdcId}</span> - <span>{brandOwner}</span>
</div>
);
};
const AddFoodItemList = () => {
const contextValue = useContext(AddFoodContext);
console.log(contextValue);
const listItems = (contextValue.foods || []).map((foods) => (
<FoodItem
key={foods.fdcId} // brandOwner isn't unique
brandOwner={foods.brandOwner}
fdcId={foods.fdcId}
/>
));
return <div id="AddFoodItemList">{listItems}</div>;
};
export default () => {
const [data, setData] = useState({});
useEffect(() => {
fetch(getUri("cheese"))
.then((res) => res.json())
.then(setData)
.catch(console.error);
}, []);
return (
<AddFoodContext.Provider value={data}>
<AddFoodItemList />
</AddFoodContext.Provider>
);
};

How to access ag-Grid API in React function component (useState hook)?

What is the best way of accessing ag-Grid API inside of React function component?
I have to use some of the methods from API (getSelectedNodes, setColumnDefs etc.) so I save a reference to the API (using useState hook) in onGridReady event handler:
onGridReady={params => {
setGridApi(params.api);
}}
and then I can call the API like this: gridApi.getSelectedNodes()
I haven't noticed any problems with this approach, but I'm wondering if there's more idiomatic way?
Stack:
ag-grid-community & ag-grid-react 22.1.1
react 16.12.0
We find the most idiomatic way to use a ref. As the api is not a state of our component. It is actually possible to simply do:
<AgGridReact ref={grid}/>
and then use it with
grid.current.api
Here an example:
import React, { useRef } from 'react'
import { AgGridReact } from 'ag-grid-react'
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact'
const ShopList = () => {
const grid = useRef<AgGridReactType>(null)
...
return (
<AgGridReact ref={grid} columnDefs={columnDefs} rowData={shops} />
)
}
The good thing here is, that you will have access to the gridApi but als to to the columnApi. Simply like this:
// rendering menu to show/hide columns:
{columnDefs.map(columnDef =>
<>
<input
type='checkbox'
checked={
grid.current
? grid.current.columnApi.getColumn(columnDef.field).isVisible()
: !(columnDef as { hide: boolean }).hide
}
onChange={() => {
if (grid.current?.api) {
const col = grid.current.columnApi.getColumn(columnDef.field)
grid.current.columnApi.setColumnVisible(columnDef.field, !col.isVisible())
grid.current.api.sizeColumnsToFit()
setForceUpdate(x => ++x)
}
}}
/>
<span>{columnDef.headerName}</span>
</>
)}
Well I am doing it in my project. You can use useRef hook to store gridApi.
const gridApi = useRef();
const onGridReady = params => {
gridApi.current = params.api; // <== this is how you save it
const datasource = getServerDataSource(
gridApi.current,
{
size: AppConstants.PAGE_SIZE,
url: baseUrl,
defaultFilter: props.defaultFilter
}
);
gridApi.current.setServerSideDatasource(datasource); // <== this is how you use it
};
I'm running into the same issue but here is a workaround that at least can get you the selected rows. Essentially what I'm doing is sending the api from the agGrid callbacks to another function. Specifically I use OnSelectionChanged callback to grab the current row node. Example below:
const onSelectionChanged = params => {
setDetails(params.api.getSelectedRows());
};
return (<AgGridReact
columnDefs={agData.columnDefs}
rowSelection={'single'}
enableCellTextSelection={true}
defaultColDef={{
resizable: true,
}}
rowHeight={50}
rowData={agData.rowData}
onCellFocused={function(params) {
if (params.rowIndex != null) {
let nNode = params.api.getDisplayedRowAtIndex(params.rowIndex);
nNode.setSelected(true, true);
}
}}
onSelectionChanged={function(params) {
onSelectionChanged(params);
params.api.sizeColumnsToFit();
}}
onGridReady={function(params) {
let gridApi = params.api;
gridApi.sizeColumnsToFit();
}}
deltaRowDataMode={true}
getRowNodeId={function(data) {
return data.id;
}}
/>);

Trying to use local-storage in react app, however not working in between pages

I'm trying to save an array to local-storage in my react app, so that if the user goes to another page in the app, or closes the app and reopens it, the value stays the same.
In my index.js (simplified code):
import ls from 'local-storage';
function HomeIndex() {
const [testString, setTestString] = useState(ls('localStorageText') || '');
if(condition){
const array = [1,2,3];
const saveArray = {key: array};
localStorage.setItem('key1', JSON.stringify(saveArray));
const restoreValue = localStorage.getItem('key1');
setTestString(JSON.parse(restoreValue).key);
}
return (
<div className="col-12">
{testString}
</div>
);
}
When I press the button, and the condition is met, the testString value displays 123 as it should. And it holds the value. However it does not work when I try and add my own array.
const array = reversedHistoryText;
const saveArray = {key: array};
localStorage.setItem('key1', JSON.stringify(saveArray));
const restoreValue = localStorage.getItem('key1');
setTestString(JSON.parse(restoreValue).key);
It doesn't display anything the first time the button is clicked, then gives error on the 2nd time:
Error: Minified React error #31;
When I do this test:
setTestString(JSON.stringify(reversedHistoryText));
The result is []
You need to set your testString to the localStorage value.
import ls from "local-storage";
import React, { useEffect, useState } from "react";
function MyComponent() {
const [testArray, setTestArray] = useState([]);
useEffect(() => {
setTestArray(ls("testArray") || []);
}, []);
function handleClick(e) {
ls("testArray", [
{ id: 1, name: "this" },
{ id: 2, name: "thing" },
{ id: 3, name: "is" },
{ id: 4, name: "cool" }
]);
setTestArray(ls("testArray"));
}
return (
<div className="col-12">
<ul>
{testArray.map(obj => (
<p key={obj.id}>{obj.name}</p>
))}
</ul>
<button onClick={handleClick}>Set The State</button>
</div>
);
}
export default MyComponent;
You don't need to use third party for localStorage.
Just use localStorage without importing anything.
To save,
localStorage.setItem('key', 'value');
To get value from localStorage,
localStorage.getItem('key') // value
To remove value,
localStorage.removeItem('key')
Use
if(condition) ls('localStorageText', "TEST");
setTestString(ls('localStorageText')|| ' '); }
Instead
if(condition){ ls('localStorageText', "TEST");
setTestString(ls('localStorageText')); }
Because when you go back to the index page a new instance of this component is rendered and i think the condition in the if statement is false, so the code don't change the setstate value...
To set use
localStorage.setItem('itemName', JSON.stringify(arrayName));
To get use
whatEver = jQuery.parseJSON(localStorage.getItem('itemName'));
Local storage stores strings