This issue seems common enough, but I cannot quite make the connection for my scenario.
I have a table in React that has the ability to search, and I have an Actions column that allows for updating and deleting data. They work functionally with a Spring Boot API and a SQL datbase, but my problem consists in refreshing the view after the deletion happens.
I'm working off of a precedent from here: https://codesandbox.io/s/62brk?file=/src/useTableSearch.js:0-1515
This is my code:
Columns.js:
export const userColumns = [
{
title: "First Name",
dataIndex: "firstName",
key: "firstName"
},
{
title: "LastName",
dataIndex: "lastName",
key: "lastName"
},
{
title: "Pronoun(s)",
dataIndex: "pronoun",
key: "pronoun"
},
{
title: "Date of Birth",
// dataIndex: "birth",
key: "birth",
render: record => {
return Object.values(record.birth.slice(0, 10))
}
},
{
title: "Address",
key: "address",
render: record => {
return Object.values(record.address)
.filter(val => typeof val !== "object")
.join("");
}
},
{
title: "City",
dataIndex: "city",
key: "city"
},
{
title: "Province",
dataIndex: "province",
key: "province"
},
{
title: "Postal Code",
dataIndex: "postalCode",
key: "postalCode"
},
{
title: "Phone",
dataIndex: "phone",
key: "phone"
},
{
title: "Email",
dataIndex: "email",
key: "email"
},
{
title: "Contact Preference",
dataIndex: "contactPref",
key: "contactPref"
},
{
title: "Daytime Preference",
dataIndex: "daytimePref",
key: "daytimePref"
},
{
title: "Actions",
dataIndex: "actions",
key: "actions",
}
];
UseTableSearch.js:
// reference from: https://codesandbox.io/s/62brk?file=/src/useTableSearch.js:0-1515
import { useState, useEffect } from "react";
export const useTableSearch = ({ searchVal, retrieve }) => {
const [filteredData, setFilteredData] = useState([]);
const [origData, setOrigData] = useState([]);
const [searchIndex, setSearchIndex] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
const crawl = (user, allValues) => {
if (!allValues) allValues = [];
for (var key in user) {
if (typeof user[key] === "object") crawl(user[key], allValues);
else allValues.push(user[key] + " ");
}
return allValues;
};
const fetchData = async () => {
const { data: users } = await retrieve();
setOrigData(users);
setFilteredData(users);
const searchInd = users.map(user => {
const allValues = crawl(user);
return { allValues: allValues.toString() };
});
setSearchIndex(searchInd);
if (users) setLoading(false);
};
fetchData();
}, [retrieve]);
useEffect(() => {
if (searchVal) {
const reqData = searchIndex.map((user, index) => {
if (user.allValues.toLowerCase().indexOf(searchVal.toLowerCase()) >= 0)
return origData[index];
return null;
});
setFilteredData(
reqData.filter(user => {
if (user) return true;
return false;
})
);
} else setFilteredData(origData);
}, [searchVal, origData, searchIndex]);
return { filteredData, loading };
};
showUsers.js:
// reference from: https://codesandbox.io/s/62brk?file=/src/useTableSearch.js:0-1515
import React, { useState } from "react";
import { Link} from "react-router-dom";
import { Table, Input } from "antd";
import axios from "axios";
import { userColumns} from './Columns'
import { useTableSearch } from "./UseTableSearch";
import '../styling/ShowUsers.css';
import userService from '../services/UserServices';
const { Search } = Input;
// const deleteUser = (id) => {
// userService.deleteUser(id)
//axios.delete(`http://localhost:8080/clients/${index}`)
// .then(res => {
// const users = this.state.users.filter(item=> item.id !== id);
// this.setState({ users });
});
}
// const [users, setUsers] = useState([]); //{ data }
// const [user, setUser] = useState('');
const showUsers = async () => {
const { data } = await axios.get(
"http://localhost:8080/clients/"
) return {data}
}
// .then((res) => {
// setUsers({users: res.data});
// })
// const rows = users.map((item, index) => {
// return (
// <tr>
// <td>
// <Link to={"/editUser/" + user.id} className="btn btn-outline-success my-2 // text-center mr-2">Update User</Link>
// <button className="btn btn-outline-primary text-center mr-2" onClick={() // => {deleteUser(user.id); }} />
// </td>
// </tr>
//
)
// })
// }
export default function App() {
// const [rows, setRows] = useState(dbValues);
// const deleteRow = (number) => {
// let copy = [...rows]
// copy = copy.filter((item, index) => number != index)
// setRows(copy);
// }
// const [users, setUsers] = useState([]);
const [searchVal, setSearchVal] = useState(null);
const { filteredData, loading } = useTableSearch({
searchVal,
retrieve: showUsers
});
return (
<>
<Search id="searchBox"
onChange={e => setSearchVal(e.target.value)}
placeholder="Search"
enterButton
style={{ position: "sticky", top: "0", left: "0" }}
/>
<br /> <br />
<Table
rowKey="name"
dataSource={filteredData}
columns={userColumns}
loading={loading}
pagination={false}
/>
</>
);
}
The approaches I've tried include putting in a render component inside the Actions column in Columns.js. Then I stuck the deleteUser function atop the Columns array, and it works with an Axios request. This approach worked almost entirely, save for the requirement to do a manual refresh on the page to reflect the changed data in the database.
return (
<td>
<Link to={"/editUser/" + user.id} className="btn btn-outline-success my-2 text-center mr-2">Update User</Link>
<button className="btn btn-outline-primary text-center mr-2" onClick={() => {deleteUser(user.id); //RefreshComponent && <RefreshComponent />}}>Delete User</button>
</td>
)
And delete component:
const deleteUser = (id) => {
userService.deleteUser(index)
axios.delete(`http://localhost:8080/clients/${index}`)
}
The limitation with this approach is that I cannot carry out a comparison against the remaining content, which is otherwise a common approach for the response after the deletion:
deleteUser(index) {
console.log(index);
userService.deleteUser(index)
axios.delete(`http://localhost:8080/clients/${index}`)
.then(res => {
const users = this.state.users.filter(item=> item.id !== index);
this.setState({ users });
})
}
Other things I looked at, include this example, which does what I'm looking for, but I can't translate its approach to my code (plus, my code is using Ant Design).
The commented out lines in showUsers.js reflect an attempt to add the buttons separately, outside of the Columns file in a separate return component, but that also went nowhere.
Finally, the use of a useState hook, which would generally work in this case, is not permitted to be used in the Columns file, either outside of it, or within a function within the event trigger for the Delete button, inside the return component for the column.
I've gone through the implementation of a separate Delete Component or a Refresh Component that I can import into Columns (e.g. RefreshComponent && <RefreshComponent />) and invoke once a deletion happens. Likewise, I've tried to use useNavigate as a way to invoke another get request and refresh the view, but that also was not possible.
Finally, I've looked at things on SO for a solution, such as this one, and they use a class-based approach, whereas I am generally working with hooks, and I also cannot find a decisive precedent for an example that's built around a search function and a separate Columns import.
Overall, my mind is looping on itself on this problem, and I can't exactly figure out how to implement the view refresh after the delete request passes.
Thank you in advance!
Let me share an idea. One of the ideas behind React is the ability to make UI (from larger UI's to single components) to auto-render or re-render (whatever you prefer) based on changes to a registered state.
So, what you have to achive as a developer is try to register that user data set as state to that component which is in charge of rendering the table.
You can achieve this with a hook useState() in case you are using a funcional approach or this.setState() if you are using a class' approach. Or updating the props passed to this component from a higher component (usually a parent).
So your challenge is to be able to update this state for example, when a user is deleted. So if you manage to update the registered dataset once it changes, then the React component, in this case, the table will re-render.
Related
having an issue, when the when nav to the comp the items state is empty, if I edit the code and page refreshes its shows up and if I add the state to the useEffect "[itemCollectionRef, items]" it's an inf loop but the data is their anyone have a better idea or way to fetch the data for display from firestore.
import React, { useState, useEffect } from "react";
import { Grid, Box, Button, Space } from "#mantine/core";
import { ItemBadge } from "../../components/NFAItemBadge";
import { useNavigate } from "react-router-dom";
import { db, auth } from "../../firebase";
import { getFirestore, query, getDocs, collection, where, addDoc } from "firebase/firestore";
import { useAuthState } from "react-firebase-hooks/auth";
const ItemTrack = () => {
const [user, loading, error] = useAuthState(auth);
const navigate = useNavigate();
const [items, setItems] = useState([]);
const itemCollectionRef = collection(db, "items");
useEffect(() => {
//if(!user) return navigate('/');
//if(loading) return;
const q = query(itemCollectionRef, where("uid", "==", user.uid));
const getItems = async () => {
const data = await getDocs(q);
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
console.log("Fetched Items: ", items);
};
getItems();
}, []);
if (loading) {
return (
<div>
<p>Initialising User....</p>
</div>
);
}
if (error) {
return (
<div>
<p>Error: {error}</p>
</div>
);
}
if (user) {
return (
<Box sx={{ maxWidth: 1000 }} mx="auto">
</Box>
);
} else {
return navigate("/");
}
};
export default ItemTrack;
It will depend how you will render the data from the useEffect. setState does not make changes directly to the state object. It just creates queues for React core to update the state object of a React component. If you add the state to the useEffect, it compares the two objects, and since they have a different reference, it once again fetches the items and sets the new items object to the state. The state updates then triggers a re-render in the component. And on, and on, and on...
As I stated above, it will depend on how you want to show your data. If you just want to log your data into your console then you must use a temporary variable rather than using setState:
useEffect(() => {
const newItems = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }))
console.log(newItems)
// setItems(newItems)
}, [])
You could also use multiple useEffect to get the updated state object:
useEffect(() => {
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })))
}, [])
useEffect(() => { console.log(items) }, [items])
If you now want to render it to the component then you have to call the state in the component and map the data into it. Take a look at the sample code below:
useEffect(() => {
const q = query(itemCollectionRef, where("uid", "==", user.uid));
const getItems = async () => {
const data = await getDocs(q);
setItems(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
getItems();
}, []);
return (
<div>
<p>SomeData: <p/>
{items.map((item) => (
<p key={item.id}>{item.fieldname}</p>
))}
</div>
);
I have a dropdown menu showing states and counties. I want the county one to be dependent on the state one.
I am using react, javascript, prisma to access the database.
I made it work separated, so I can get the states to show and the counties, but I don't know how to make them dependent.
What I think I need is a way to change my function that bring the county data. I can group by the state that was selected. So what I need is after getting the state that was selected to send that to my "byCounty" function. Is that possible?
menu.js
export default function DropDownMenu(props){
if(!props.states) return
return(
<table>
<body>
<select onChange={(e) => { console.log(e.target.value) }}>
{props.states.map(states=>
<option>{states.state}</option>
)}
</select>
<select >
{props.byCounty.map(byCounty=>
<option>{byCounty.county}</option>
)}
</select>
</body>
</table>
)
}
functions.js
const states = await prisma.county.groupBy({
by:["state"],
where: {
date: dateTime,
},
_sum:{
cases:true,
},
});
const byCounty = await prisma.county.groupBy({
by:["county"],
where: {
date: dateTime,
state: 'THIS SHOULD BE THE STATE NAME SELECTED BY USER'
},
_sum:{
cases:true,
},
});
const result =JSON.stringify(
{states:states, byCounty:byCounty},
(key, value) => (typeof value === 'bigint' ? parseInt(value) : value) // return everything else unchanged
)
res.json(result);
index.js
<div className={styles.table_container}>
<h2>Teste</h2>
<DropDownMenu states={myData?myData.states:[]} byCounty={myData?myData.byCounty:[]}></DropDownMenu>
</div>
What I have:
Here's a self-contained example demonstrating how to "fetch" options from a mock API (async function), and use the results to render a top level list of options, using the selected one to do the same for a dependent list of options. The code is commented, and I can explain further if anything is unclear.
For simplicity, the example doesn't use states and counties, but the dependency relationship is the same.
TS Playground
body { font-family: sans-serif; }
.select-container { display: flex; gap: 1rem; }
select { font-size: 1rem; padding: 0.25rem; }
<div id="root"></div><script src="https://unpkg.com/react#18.1.0/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#18.1.0/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.10/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import * as ReactDOM from 'react-dom/client';
// import {
// type Dispatch,
// type ReactElement,
// type SetStateAction,
// useEffect,
// useRef,
// useState,
// } from 'react';
// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {
useEffect,
useRef,
useState,
} = React;
// The next section is just a mock API for getting dependent options (like your States/Counties example):
async function getOptionsApi (level: 1): Promise<string[]>;
async function getOptionsApi (
level: 2,
level1Option: string,
): Promise<string[]>;
async function getOptionsApi (
level: 1 | 2,
level1Option?: string,
) {
const OPTIONS: Record<string, string[]> = {
colors: ['red', 'green', 'blue'],
numbers: ['one', 'two', 'three'],
sizes: ['small', 'medium', 'large'],
};
if (level === 1) return Object.keys(OPTIONS);
else if (level1Option) {
const values = OPTIONS[level1Option];
if (!values) throw new Error('Invalid level 1 option');
return values;
}
throw new Error('Invalid level 1 option');
}
// This section includes the React components:
type SelectInputProps = {
options: string[];
selectedOption: string;
setSelectedOption: Dispatch<SetStateAction<string>>;
};
function SelectInput (props: SelectInputProps): ReactElement {
return (
<select
onChange={(ev) => props.setSelectedOption(ev.target.value)}
value={props.selectedOption}
>
{props.options.map((value, index) => (
<option key={`${index}.${value}`} {...{value}}>{value}</option>
))}
</select>
);
}
function App (): ReactElement {
// Use a ref to track whether or not it's the initial render
const isFirstRenderRef = useRef(true);
// State for storing the top level array of options
const [optionsLvl1, setOptionsLvl1] = useState<string[]>([]);
const [selectedLvl1, setSelectedLvl1] = useState('');
// State for storing the options that depend on the selected value from the level 1 options
const [optionsLvl2, setOptionsLvl2] = useState<string[]>([]);
const [selectedLvl2, setSelectedLvl2] = useState('');
// On the first render only, get the top level options from the "API"
// and set the selected value to the first one in the list
useEffect(() => {
const setOptions = async () => {
const opts = await getOptionsApi(1);
setOptionsLvl1(opts);
setSelectedLvl1(opts[0]!);
};
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
setOptions();
}
}, []);
// (Except for the initial render) every time the top level option changes,
// get the dependent options from the "API" and set
// the selected dependent value to the first one in the list
useEffect(() => {
const setOptions = async () => {
const opts = await getOptionsApi(2, selectedLvl1);
setOptionsLvl2(opts);
setSelectedLvl2(opts[0]!);
};
if (isFirstRenderRef.current) return;
setOptions();
}, [selectedLvl1]);
return (
<div>
<h1>Dependent select options</h1>
<div className="select-container">
<SelectInput
options={optionsLvl1}
selectedOption={selectedLvl1}
setSelectedOption={setSelectedLvl1}
/>
<SelectInput
options={optionsLvl2}
selectedOption={selectedLvl2}
setSelectedOption={setSelectedLvl2}
/>
</div>
</div>
);
}
const reactRoot = ReactDOM.createRoot(document.getElementById('root')!)
reactRoot.render(<App />);
</script>
You could use custom hooks to do this.
The key is that in your code the second dropdown should watch the changes in the date of the first dropdown & react to these changes. In React you do this by using useEffect() (most of the times):
useEffect(() => {
reactingToChanges()
}, [watchedVariable])
In the snippet,
The "states" API is querying a real source of data
I mocked the counties API (I couldn't find a free/freesource solution)
I added a simple cache mechanism for the counties, so the API doesn't get queried if the data has already been downloaded
// THE IMPORTANT PART IS IN A COMMENT TOWARDS THE BOTTOM
const { useEffect, useState } = React;
const useFetchStates = () => {
const [states, setStates] = useState([]);
const fetchStates = () => {
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
const urlencoded = new URLSearchParams();
urlencoded.append("iso2", "US");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow"
};
fetch(
"https://countriesnow.space/api/v0.1/countries/states",
requestOptions
)
.then((response) => response.json())
.then(({ data: { states } }) => setStates(states))
.catch((error) => console.log("error", error));
};
if (!states.length) {
fetchStates();
}
return {
states
};
};
const useFetchCounties = () => {
const [countiesByState, setCountiesByState] = useState({});
const [counties, setCounties] = useState([]);
const fetchCounties = (state) => {
if (state in countiesByState) {
setCounties(countiesByState[state]);
} else if (state) {
fetch("https://jsonplaceholder.typicode.com/todos")
.then((response) => response.json())
.then((json) => {
const mappedCounties = json.map(({ id, title }) => ({
id: `${state}-${id}`,
title: `${state} - ${title}`
}));
setCounties(mappedCounties);
setCountiesByState((prevState) => ({
...prevState,
[state]: mappedCounties
}));
});
} else {
setCounties([]);
}
};
return {
counties,
fetchCounties
};
};
const Selector = ({ options = [], onChange, dataType }) => {
return (
<select onChange={(e) => onChange(e.target.value)} defaultValue={"DEFAULT"}>
<option disabled value="DEFAULT">
SELECT {dataType}
</option>
{options.map(({ name, val }) => (
<option key={val} value={val}>
{name}
</option>
))}
</select>
);
};
const App = () => {
const { states = [] } = useFetchStates();
const [selectedState, setSelectedState] = useState("");
const { counties, fetchCounties } = useFetchCounties();
const [selectedCounty, setSelectedCounty] = useState("");
// here's the heart of this process, the useEffect():
// when the selectedState variable changes, the
// component fetches the counties (based on currently
// selected state) and resets the currently selected
// county (as we do not know that at this time)
useEffect(() => {
fetchCounties(selectedState);
setSelectedCounty("");
}, [selectedState]);
const handleSelectState = (val) => setSelectedState(val);
const handleSelectCounty = (val) => setSelectedCounty(val);
return (
<div>
<Selector
options={states.map(({ name, state_code }) => ({
name,
val: state_code
}))}
onChange={handleSelectState}
dataType={"STATE"}
/>
<br />
<Selector
options={counties.map(({ id, title }) => ({
name: title,
val: id
}))}
onChange={handleSelectCounty}
dataType={"COUNTY"}
/>
<br />
Selected state: {selectedState}
<br />
Selected county: {selectedCounty}
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The way you asked the question leads to different interpretations of your problem, both #muka.gergely's and #jsejcksn's answers are very good solutions but it's much more from what you really asked for. As you only want to get the value from selected state and fetch the counties from your backend, you can do the following:
functions.js
// change to a function that gets a state as parameter
const byCounty = async (selectedState) => {
return await prisma.county.groupBy({
by:["county"],
where: {
date: dateTime,
// use the received parameter here to fetch the counties
state: selectedState
},
_sum:{
cases:true,
},
})
};
menu.js
export default function DropDownMenu(props){
if(!props.states) return
return(
<table>
<body>
<select
// use the byCounty function with the selected value to fetch the counties
onChange={ async (e) => {
await byCounty(e.target.value)
}}
>
{props.states.map(states=>
<option>{states.state}</option>
)}
</select>
<select >
{props.byCounty.map(byCounty=>
<option>{byCounty.county}</option>
)}
</select>
</body>
</table>
)
}
And that's all, if you want to make the option county and state working together you can use the idea behind the other answers as well. Hope I helped you!
This is exactly what I'm wanting to do. . . For example, First drop down list would list all the State Names, then I click on that state, and it would generate a text file. The text file would be county name placefiles for all the counties for that state.
I am making a form that uses many fields to post the data into a database.
I have over 80 fields like "title, body HTML, price, compare price, vendor, weights", etc.
and my code is very repetitive, is there a way to make my code shorter? I shaved a lot of my code because it's over 600 lines of code and would be too confusing to post the whole thing
I made 2 separate functions handleChange and selectHandler as little helpers to get the value of the dropdowns datalist inputs to be stored into state... the values have to be stored in separate states as I need each one to do an axios call to store its specific fields into the right data field.
import React, { useState } from "react";
function handleChange(e, setter) {
return setter({ value: e.target.value });
}
function selectHandler(setter) {
return (
<>
<input
list="headers"
placeholder="Select one"
onChange={(e) => handleChange(e, setter)}
/>
{/* headers comes from mapped api in another file */}
<datalist id="headers">{headers}</datalist>
</>
);
}
function PushToDB() {
const [showExtraImageInputs, setShowExtraImageInputs] = useState(false);
const [titleHeader, setTitleHeader] = useState();
const [handleHeader, setHandleHeader] = useState();
const [descriptionHtmlHeader, setDescriptionHtmlHeader] = useState();
const [image1Header, setImage1Header] = useState();
const [image2Header, setImage2Header] = useState();
const [altText1, setAltText1] = useState();
const [altText2, setAltText2] = useState();
return (
<>
<form onSubmit={(e) => e.preventDefault()}>
// each label uses the helpers to get the dropdown values and store it in state
<label>Title: {selectHandler(setTitleHeader)}</label>
<label>Body html: {selectHandler(setDescriptionHtmlHeader)}</label>
<label>Handle: {selectHandler(setHandleHeader)}</label>
<label>Image: {selectHandler(setImage1Header)}</label>
<label>Img alt text: {selectHandler(setAltText1)}</label>
{/* ADD MORE IMAGES */}
{showExtraImageInputs && (
<>
<div>Image 2: {selectHandler(setImage2Header)}</div>
<div>Img alt text 2: {selectHandler(setAltText2)}</div>
</>
)}
</form>
</>
);
}
export default PushToDB;
this is how the axios data looks like. as you can see I need each value from state. and again, its over 80 fields.
useEffect(() => {
if (pushState && apiData) {
let productValues = apiData.data.data;
productValues.map((e) => {
let url = `url`;
return axios
.get(url)
.then((res) => {
if (res) {
// if the data is already in db, do not push
if (res.data.products.length === 0)
// if there is no data then push data
return setProductData({
variables: {
// values from state
title: e[titleHeader?.value],
descriptionHtml: e[descriptionHtmlHeader?.value],
handle: e[handleHeader?.value],
img1: e[image1Header?.value] ?? "",
alt1: e[altText1?.value],
img2 : e[image2Header?.value] ?? '',
alt2: e[altText2?.value],
img3: e[image3Header?.value] ?? '',
// and so on
},
});
}
// this is the logger of whats being pushed into the database
})
.then((res) => {
if (res)
return axios.post("http://localhost:4000/api/v1/Extradb", {
data: {
title: res?.data?.productCreate?.product?.title,
handle: res?.data?.productCreate?.product?.handle,
item_id: res?.data?.productCreate?.product?.id,
},
});
});
});
}
}, []);
came out with a solution... I just needed to make an object
function App() {
const [userInputs, setUserInputs] = useState({})
function handleChange(e) {
const { value, name } = e.target
setUserInputs(prevState => ({
...prevState,
[name]: value
}))
}
function handleInputNaming(name) {
let capitilizedWord = name.charAt(0).toUpperCase() + name.slice(1);
return (<input placeholder={capitilizedWord} name={name} value={userInputs[name]} onChange={handleChange} />)
}
return (
<div className="App">
{handleInputNaming('title')}
{handleInputNaming('handle')}
{handleInputNaming('image')}
</div>
);
}
export default App;
I have a problem with handleChange inputs in connection with Redux, it almost works, but not quite, because when I change state, in one field there is a change of state guess what I would like to achieve, but when I change in another field this state disappears from the first one and saves in the second one (on the picture below I showed what I mean). I have two such moderately convincing ideas, that is, in the reducer in the line "value: action.payload.num_building || '', and in the place of '' do ' ', so that there is always something there and then when I do the post I do the trim, so that there is no space, the post will fly as an empty value, or instead of '' do the state in the same place. num_building.value in one field and state.test.value in the other, but here the problem is that if I type something and then delete the whole input, there will always be the first letter of the text we typed and I don't know how to delete it :(
Slicer.js
import { createSlice } from '#reduxjs/toolkit';
export const numberBuildingSlice = createSlice({
name: 'number_building',
initialState: {
num_building: { key: 'num_building', value: '' },
test: { key: 'test', value: '' },
},
reducers: {
inputChange: (state, action) => {
return {
...state,
num_building: {
...state.num_building,
value: action.payload.num_building || state.num_building.value,
},
test: {
...state.test,
value: action.payload.test || ' ',
},
};
},
},
});
export const { inputChange } = numberBuildingSlice.actions;
export const numberBuildingState = (state) => state.number_building;
export default numberBuildingSlice.reducer;
Component
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import axios from 'axios';
import {
numberBuildingState,
inputChange,
} from '../redux/slices/numberBuildingSlice';
import LabelInput from '../reusable-components/LabelInput';
const URL = 'http://localhost:8000/numbers-buildings/';
function AddNumberBuilding() {
const numberBuilding = useSelector(numberBuildingState);
const dispatch = useDispatch();
const { value } = numberBuilding.num_building;
const handleChange = async (event) => {
const response = await dispatch(
inputChange({ [event.target.name]: event.target.value }),
);
console.log(response);
};
function postNumberBuilding() {
const jsonData = { num_building: value };
axios
.post(URL, jsonData)
.then((res) => {
console.log(res);
})
.catch((error) => console.error(error.response));
}
return (
<div>
<input
name="num_building"
onChange={handleChange}
{...numberBuilding.num_building}
/>
<input name="test" onChange={handleChange} {...numberBuilding.test} />
<input type="submit" onClick={postNumberBuilding} value="Add New!" />
</div>
);
}
export default AddNumberBuilding;
You are updating both the values in reducer, even though you are just sending one variable to be updated in dispatched function.
The solution is to use dynamic key accessor
reducers: {
inputChange: (state, action) => {
const key = Object.keys(action.payload)[0];
return {
...state,
[key ]: {
...state[action.payload[key]],
value: action.payload[key ] || state.num_building.value,
},
};
},
},
I need to make a request to the server each time user types in the Multiselect input to retrieve data based on the search query.
If i set the list with fixed values it works fine and filter the list.
import MultiSelect from "react-multi-select-component";
...
const options = [
{ label: "Grapes 🍇", value: "grapes" },
{ label: "Mango 🥭", value: "mango" },
{ label: "Strawberry 🍓", value: "strawberry" },
];
const [selected, setSelected] = useState([]);
<MultiSelect
options={options}
value={selected}
onChange={setSelected}
labelledBy={"Select"}
/>
I tried to use filterOptions props on the MultiSelect. The problem is that when i press on the MultiSelect and start typing in the input it keeps making calls to the sever until I clear the value of the MultiSelect input. it stopped.
const [invoices,set_invoices] = useState([]);
const [selected, setSelected] = useState([]);
function test(options, filter) {
if (!filter) {
return options;
}
var data={'invoice_number':'22'}
axios.post('http://localhost:4000/get_invoice_by_number',data).then(
response => {
// The for loop below is to make the invoices objects like
// {label:'',value:''}
var temp_invoices=[];
for(var i =0;i<response.data.length;i++){
temp_invoices.push({
label:response.data[i].invoice_number,
value:response.data[i].invoice_number
})
}
// JSON.stringify(temp_invoices)
set_invoices(temp_invoices);
},error =>{
Swal.fire({
title: 'Error!',
text: 'Please Contact your software developer',
icon: 'error',
confirmButtonText: 'OK'
})
}
)
return options;
}
<MultiSelect
options={invoices}
value={selected}
labelledBy={"Select"}
onChange={setSelected}
filterOptions={test}
/>
You could make use of filterOptions props MultiSelect component.
The functions passed in the props will be triggered when user types in the Multiselect input.
But the onChange will be triggered only on selection of options.
import React, { useState } from "react";
import MultiSelect from "react-multi-select-component";
const Example: React.FC = () => {
const options = [];
const [selected, setSelected] = useState([]);
const handleChange = e => {
// Triggered for option select
}
function filterOptions(options, filter) {
// Triggered for filter input changes
}
return (
<div>
<pre>{JSON.stringify(selected)}</pre>
<MultiSelect
options={options}
value={selected}
onChange={handleChange}
labelledBy={"Select"}
filterOptions={filterOptions}
/>
</div>
);
};
export default Example;