I'm making a json map, and sending it to another component, to assemble several Cards at the same time:
{search.map((element, index) => {
return(
<div key={index}>
<CardItem key={index} data={element}></CardItem>
</div>
)
})}
And then I made an entry that when I put the name of the card title, it only shows the cards with those titles. Basically a search filter in React
const [value, setValue] = useState('')
const searchLowerCase = value.toLowerCase()
const search = job.filter(element => element.company.toLowerCase().includes(searchLowerCase) || element.role.toLowerCase().includes(searchLowerCase) || element.level.toLowerCase().includes(searchLowerCase) )
const handleInput = (e) => {
setValue(e.target.value)
}
The problem is that inside my json, there is an array that brings other elements, that is, I need to make another map. I'm making this other map directly in the JSX of the cards component
{data.languages.map((element, index) => {
return(
{element}
)
})}
So I need to make my input also look for these other elements that are inside the map that is being performed in the other component. For when I write the name of these elements in the input, show only the card on the screen that has these selections. Any tips would be greatly appreciated
I'm thinking of some way to add this map made in my other component, inside this map that is doing the searches, so when I type inside the input, all the elements that are written, regardless of which map it is, are shown on the screen
Related
I am new to ReactJS and pairing it with Material UI is really causing me some roadblocks. I have created a reusable search filter component for my data tables and it worked exactly the way I wanted, but now I want to add a button to clear the field and show the unfiltered results, as well as return the InputSearch component back to its default state so it will display the label inside the field again, not up in the field’s border as these Material UI TextFields do then they are focused or have a current value. This is where I am hitting my roadblock. I have tried multiple solutions I found online, like using the inputRef/useCallback method to change the values, but it didn’t seem to work…or maybe I misunderstood and did it wrong. I was also recommended to put my search values to state. As happens with state my searches are now always one render behind (I.E. , results matching ‘US’ for ‘USA’ , ‘USA’ for ‘USAF’, etc…). Then when I run the handleFilterReset function to set the filter values back to an empty string, nothing happens. I just want my search filter to work instantly (like it did before I moved the value to state [commented out]) and be able to be cleared, resetting the table back to its default display.
Can someone please help me figure this out? Suggestions are appreciated, but code snippets are much more helpful since I am really new to React and especially Material UI.
dataTable.js
const [inputValue, setInputValue] = useState('')
const [searchFn, setSearchFn,] = useState({ fn: items => { return items; } });
// Searching Data
const handleSearch = e => {
setInputValue(e.target.value) // value displayed in input field
let query = (e.target.value).toString().toLowerCase();
setSearchFn({
fn: items => {
if (query === "")
return items;
else
return items.filter(x =>
(x.tankName !== null && x.tankName.toLowerCase().includes(query)) ||
(x.dimensions !== null && x.dimensions.toLowerCase().includes(query))
)
}
})
}
// Clearing Filters
const handleFilterReset = () => {
setInputValue('');
setSearchFn({fn: items => {return items;}})
};
// Search and filter Inputs
<div>
<InputSearch
value={inputValue}
onChange={handleSearch}
/>
<Button
text="Reset"
onClick={handleFilterReset}
/>
</div>
InputSearch.js
export default function InputSearch(props) {
const { inputRef, name, value, error=null, onChange, ...other } = props;
return (
<TextField
label="Search..."
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true, helperText:error})}
>
</TextField>
)
}
You need to pass the value to InputSearch
Heres an example:
https://codesandbox.io/s/morning-brook-durbvd?file=/demo.tsx
React has a pretty good introduction on its site.
https://reactjs.org/docs/components-and-props.html
The code has been updated with a solution to this issue. I created a display value for the input that I passed to state, which was set to a blank string when the reset is pressed as well as passing an unfiltered data set.
I have a dropdown which controls state: [selected, setSelected].
This state is created into a flat array and then mapped into icons.
When the icon is selected I would like it to then remove that target specifically from the array. I assume this is just a simple (e) => onClick but am unsure how to write it.
Please see how my state is created:
const onPerksClick = (val) => {
console.log({ val });
if (!selected.includes(val)) {
setSelected((prev) => [...prev, val]);
}
};
i then map this into icons and different things.
I would like to include this so when it's clicked it will remove it from the array.
<BenefitsContainer>
{selected.map((perkTitle) => (
<SelectionBoxesChoiceOption >
{perkTitle=="Equity" &&
<BalanceIcon style={{ color: "white"}}/>
}
</SelectionBoxesChoiceOption>
))}
</BenefitsContainer>
In my React with hooks, I have a small component that renders based on a condition.
The Recipients below component works in this way, if we have more than 1 recipient then we show a dropdown list otherwise we just show the name of the recipient.
The issue is that as you see the first is a checkbox where onChange is passing the selected recipient. Having this I mind when I have only 1 recipient I need to set that recipient in the state as const [recipients, setRecipients] = useState({});
I would like to setRecipients(...) for when we have just 1 recipient but here I blocked and don't know the way of doing it and passing inside that component as you see is just a typography component. Need help to make this.
The onChange as per reference looks like this
const handleRecipientChecked = useCallback(
id => e => {
const selection = { ...recipients };
selection[id].selected = !selection[id].selected;
setRecipients(selection);
},
[recipients],
);
The component
const Recipients = () => {
return Object.keys(recipients).length > 1
? Object.keys(recipients).map(id => (
<div className={classes.dropdownContainer} key={id}>
<FormControlLabel
control={
<Checkbox
key={id}
checked={recipients[id].selected}
onChange={handleRecipientChecked(id)}
/>
}
label={`${recipients[id].name} (${roles[recipients[id].role]})`}
/>
</div>
))
: Object.keys(recipients).map(id => (
<Typography>{`${recipients[id].name} (${
roles[recipients[id].role]
})`}</Typography>
));
};
The same way you are rendering the item based on length (I.e to display checkbox or text) will be the same way you will be setting state. In your usecallback, check if the length is greater than 1 and do the normal set state for check box, while in the else block just set the state to that only item.
I need to dynamically generate multiple divs with a single input-box in it, so the user can add a number.
The user can add by clicking a button, any number of divs with input-box to put a number in it.
After the user end with the entry of the data, must click a button to process the data.
I've find out how to use React to iterate through an existing array, but not about how to iterate through a new DOM tree that was created dynamically by the user, and then generate an array with the values inside all the input-boxes.
After processing, different values will be displayed (max value, min value, average, return results from equations, etc)
Without seeing your code it's hard to help, but you probably want to use controlled inputs rather than uncontrolled ones, and so you'd have an array of the current values for the inputs as state information.
For instance, in a functional component using hooks:
const { useState } = React;
function Example() {
// The values we use on the inputs
const [values, setValues] = useState([]);
// Update the value at the given index
const updateValue = (value, index) => {
setValues(values =>
Object.assign([], values, {[index]: value})
);
};
// Add an input
const addInput = () => {
setValues(values => [...values, ""]);
};
// Get the sum (just an example; and one of the very few places I'll use `reduce`)
const sum = values.reduce((sum, value) => sum + Number(value), 0);
// Render as many inputs as we have values, along with the
// button to add an input and the sum
return (
<div>
<div>
{values.map((value, index) =>
<div key={index}>
<input type="text" value={value} onChange={evt => updateValue(evt.target.value, index)} />
</div>
)}
</div>
<div>Sum: {sum}</div>
<input type="button" value="Add Input" onClick={addInput} />
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
I think you could just create one div, and an array to store the values, then create a function that everytime the user choose to add a new value, it saves the current on that array and clean the input. So, when the user select to process the data it takes that array and do what you need.
So, I've got a bit of a doozy here. I've looked into trees and the like in react, and I'm fairly confident I can implement one, given the right data structure. The problem I'm running into is that I'm getting my data from an API that doesn't, at least natively, have the structure for a tree, so I'm trying to create that structure on the fly.
This is what the data I'm getting back from the API looks like:
const category = //could have children
{
"object_name":"B2B",
"data_provider_key":"bluekai",
"object_key": "bluekai-31",
"object_type":"category",
};
const segment = //will only be a child
{
"object_name":"B2B > Role/Title > Admin Exec",
"data_provider_key":"bluekai",
"object_key": "bluekai-1145",
"object_type":"segment",
"cpm_cost":2.500
};
And this is the logic that I'm using to try and manipulate the data from the API to add children/create parents, etc.
const asyncView = async function (segTree: string | undefined) {
const categoryDataCall = api.getBeeswaxSegmentView(categoryBody);
const segmentDataCall = api.getBeeswaxSegmentView(segmentBody);
const data = await Promise.all([categoryDataCall, segmentDataCall]);
const parent = categoryData.find( (el: any) => el.object_key === segTree);
const categories = data[0].payload;
if (categories.length >= 1) {
for (let i = 0; i < categories.length; i++) {
categories[i].children = [];
}
}
parent.children = categories.concat(data[1].payload);
setCategoryData(parent.children);
setParent(parent);
}
asyncView(e.currentTarget.dataset.segment_tree);
}
return (
<>
<div>PARENT: {parent.object_name}</div>
{categoryData.length === 0
? <div>No category data</div>
: categoryData.map((e: any) => {
if (e.object_type === 'segment') {
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
>
{`Name: ${e.object_name} (${e.object_key}, $${parseFloat(e.cpm_cost).toFixed(2)} CPM)`}
</div>
)
}
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
onClick={getCategoryAndSegmentData}
>
{`Name: ${e.data_provider_name || e.object_name}`}
</div>
)
})
}
</>
);
}
I haven't implemented the Tree part yet, but that's because I am fairly confident I'm not creating the relations between elements correctly in my logic/the logic breaks if there are multiple 'trees'/categories on a page (which there will be.)
Sorry if this is a bit much, but any help or just ideas on dynamically modifying the data from the API to fit the tree structure of child/parent relationships would be appreciated!
Edit in response to Ray Hatfield:
What's the relationship between a category and a segment?
Segments will always be children of Categories, and will never have children of their own. Categories can have other categories as children.
How do you establish which category a segment belongs to?
The object_key property from the Category object gets passed to the API call(s) (two calls are made: one for segments, and one for categories). This is the only relation between segments and categories - nothing else in the return data ties them together.
What is e?
I assume you mean in the e.currentTarget.dataset.segment_tree line.
e is the event object, which I'm using to create the queries and firing them off on click events. I'm storing the object_key in a data-attribute in the HTML, and then passing it to a handler to generate the categoryBody and segmentBody used in the asyncView() function.
For some reason I have to explicitly pass the e.currentTarget.dataset.segment_tree as an argument to the async function even though they're in the same scope, but all it's doing is allowing me to find the Category that was clicked in the existing array of data in state.
What is categoryData?
categoryData is the array of values ( that is currently in state. So, each time I hit the API I update category data to re-render everything.
Effectively, I'm finding the parent (category that was clicked) firing off the API calls to get all the subcategories/segments associated with the clicked categories object_key, and then adding a children prop to any incoming categories, and then setting the children of the last clicked element equal to the returned segments + categories, and then rendering.
I put together this working demo on jsfiddle. Here are the highlights:
The Core Idea
The core idea is a Category component that's responsible for loading and rendering its own segments and subcategories. The subcategories get rendered using the same Category component, resulting in a recursive tree structure.
The Category Component
const Category = ({item}) => {
const [data, setData] = React.useState();
const onClick = data
? () => setData(null) // discard data (collapse) on subsequent click
: () => load(item.object_key).then(setData);
return (
<div className="category">
<div
className={`category-name ${data ? 'open' : ''}`}
onClick={onClick}
>
{item.object_name}
</div>
{data && (
<ul>
{ data.map((child, i) => (
<li key={i}><Node item={child}/></li>
))}
</ul>
)}
</div>
)
}
This component takes a single item prop representing the category. The component expects item to have object_key and object_name fields, like the category object in your example.
Initially the component has no information other than what's in the item, so it renders the category's name with an onClick handler that makes API calls to fetch the category's children and then stores the result in the component's state:
const [data, setData] = React.useState();
const onClick = () => load(item.object_key).then(setData);
On the subsequent render the Category component renders its children (segments and subcategories) in addition to the category name. Subcategories are rendered using the same Category component, resulting in a recursive tree structure.
The Segment Component
const Segment = ({item: {object_name}}) => (
<div className="segment">{object_name}</div>
);
Simple component for rendering segments. Just returns the segment name here, but you could of course expand it to do whatever you need it to do.
The Node Component
const Node = ({item}) => {
const Cmp = item.object_type === 'category' ? Category : Segment;
return <Cmp item={item} />;
};
Convenience component for rendering a <Segment /> or <Category /> for the given item according to its type.
The rest of the example code is just hand waving to simulate the API calls and generate mock data.
load function
const load = async (parentKey) => {
const [categories, segments] = await Promise.all([
mockApiRequest('category'),
mockApiRequest('segment')
]);
return [
...categories,
...segments
];
}
Given a category's object_key, this makes the api calls to get the segments and subcategories, merges and returns the results as a single array.
mockApiRequest
const mockApiRequest = (type) => (
new Promise((resolve) => {
setTimeout(() => resolve(fakeData(type)), 200);
})
)
Simulates the API request. Waits 200ms before resolving with mock data.
fakeData
// generate mock response data
const fakeData = (type) => {
// copy the list of names
const n = [...names];
// plucks a random name from the list
const getName = () => (
n.splice(Math.floor(Math.random() * n.length), 1)[0]
);
// generate and return an array of data
return Array.from(
{length: Math.floor(Math.random() * 5) + 1},
(_, i) => ({
...samples[type],
object_name: getName()
})
)
};
Generates mock category or segment data by copying the sample and choosing a random name.