In this app I have a search engine that is supposed to look inside the file tree for desired content. The purpose is, when searching for some content and clicking the Search button, the file tree will filter and show what you are looking for.
For now, I have a console.log() (check onSubmitSearch(e) inside <SearchEngine/>) telling me what content I am asking to search when I click the Search button. The only thing missing is the search actually looking inside the file tree. How do I do that?
Please check the working snippet attached.
Thank you!
/**** TEXT BOX COMPONENT ****/
class TextBox extends React.Component {
constructor(props) {
super(props);
this.state = { content: "Select A Node To See Its Data Structure Here..." };
this.changeContent = this.changeContent.bind(this);
}
changeContent(newContent) {
this.setState({ content: newContent });
}
componentWillReceiveProps(nextProps) {
this.setState({
content: nextProps.content
});
}
render() {
return (
<div className="padd_top">
<div className="content_box">
{this.state.content}
</div>
</div>
);
}
}
/**** SEARCH COMPONENT ****/
class SearchEngine extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.onInputChange = this.onInputChange.bind(this);
this.onSubmitSearch = this.onSubmitSearch.bind(this);
}
onInputChange(e) {
const content = e.target.value;
this.setState({value: content});
console.log(content);
}
onSubmitSearch(e) { // CONSOLE LOG IS HERE
e.preventDefault();
console.log('A node was submitted: ' + this.state.value);
}
render() {
return (
<div>
<form onSubmit={this.onSubmitSearch}>
<input
className="form-control"
value={this.state.value}
type="text"
onChange={this.onInputChange}
/>
<p>{this.state.value}</p>
<SearchButton />
</form>
</div>
);
}
}
/**** SEARCH BUTTON ****/
class SearchButton extends React.Component {
render() {
return (
<button
type="submit"
value="submit"
bsStyle="danger"> Search
</button>
);
}
}
/**** FILE TREE COMPONENT ****/
let data = [
{
type: "directory",
name: ".",
contents: [
{
type: "directory",
name: "./bin",
contents: [{ type: "file", name: "./bin/greet" }]
},
{
type: "directory",
name: "./lib",
contents: [{ type: "file", name: "./lib/greeting.rb" }]
},
{
type: "directory",
name: "./spec",
contents: [
{ type: "file", name: "./spec/01_greeting_spec.rb" },
{ type: "file", name: "./spec/02_cli_spec.rb" },
{ type: "file", name: "./spec/spec_helper.rb" }
]
},
{ type: "file", name: "./CONTRIBUTING.md" },
{ type: "file", name: "./Gemfile" },
{ type: "file", name: "./Gemfile.lock" },
{ type: "file", name: "./LICENSE.md" },
{ type: "file", name: "./README.md" }
]
}
];
// Icon file image for 'FileTree'
const FileIcon = () => {
return (
<div className="svg-icon">
<svg
id="icon-file-text2"
className="icon"
viewBox="0 0 32 32"
fill="currentColor"
width="1em"
height="1em"
>
<path d="M28.681 7.159c-0.694-0.947-1.662-2.053-2.724-3.116s-2.169-2.030-3.116-2.724c-1.612-1.182-2.393-1.319-2.841-1.319h-15.5c-1.378 0-2.5 1.121-2.5 2.5v27c0 1.378 1.122 2.5 2.5 2.5h23c1.378 0 2.5-1.122 2.5-2.5v-19.5c0-0.448-0.137-1.23-1.319-2.841zM24.543 5.457c0.959 0.959 1.712 1.825 2.268 2.543h-4.811v-4.811c0.718 0.556 1.584 1.309 2.543 2.268zM28 29.5c0 0.271-0.229 0.5-0.5 0.5h-23c-0.271 0-0.5-0.229-0.5-0.5v-27c0-0.271 0.229-0.5 0.5-0.5 0 0 15.499-0 15.5 0v7c0 0.552 0.448 1 1 1h7v19.5z" />
<path d="M23 26h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
<path d="M23 22h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
<path d="M23 18h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
</svg>
</div>
);
};
// Icon folder image for 'FileTree'
const FolderIcon = () => {
return (
<div className="svg-icon">
<svg
id="icon-folder"
className="icon"
viewBox="0 0 32 32"
fill="currentColor"
height="1em"
width="1em"
>
<path d="M14 4l4 4h14v22h-32v-26z" />
</svg>
</div>
);
};
// Icon arrow image for 'FileTree'
const TriangleDown = () => {
return (
<div className="svg-icon">
<svg
id="svg__icon--triangle-down"
viewBox="0 0 9 4.5"
fill="currentColor"
height="1em"
width="1em"
>
<path d="M0,0,4.5,4.5,9,0Z" />
</svg>
</div>
);
};
// Filters file 'name' and adds '/'
const formatName = name => {
return name.substr(name.lastIndexOf("/") + 1);
};
// Dummy data set
var root = data[0];
// Construction of FileTree
class FileTree extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNode: null
};
this.setActiveNode = this.setActiveNode.bind(this);
}
setActiveNode(name) {
this.setState({ activeNode: name });
this.props.liftStateUp(name);
}
componentWillReceiveProps({ searchTerm }) {
this.setState({ searchTerm });
}
render() {
return (
<div className="padd_top">
{renderTree(
this.props.root || root,
this.setActiveNode,
this.state.activeNode,
null,
this.state.searchTerm
)}
</div>
);
}
}
/**** DIRECTORY ****/
class Directory extends React.Component {
constructor(props) {
super(props);
this.state = { expanded: true };
this.toggleDirectory = this.toggleDirectory.bind(this);
}
toggleDirectory() {
this.setState({ expanded: !this.state.expanded });
}
hasMatchingNodes() {
const searchTerm = this.props.searchTerm.toLowerCase();
const matchNode = node =>
node.contents
? node.contents.filter(matchNode).length !== 0
: node.name.toLowerCase().indexOf(searchTerm) !== -1;
return matchNode(this.props.node);
}
render() {
let node = this.props.node;
const rotate = this.state;
if (this.props.searchTerm && !this.hasMatchingNodes()) return null;
return (
<div className="directory-container">
<div className="directory">
<div
className={`directory__toggle ${
this.state.expanded ? "expanded" : ""
}`}
>
<div onClick={this.toggleDirectory}>
<TriangleDown onClick={() => this.setState({ rotate: true })}
className={rotate ? "rotate" : ""} />
</div>
</div>
<div className="directory__icon" onClick={this.toggleDirectory}>
<FolderIcon />
</div>
<div className="directory__name" onClick={this.toggleDirectory}>
<div>{formatName(node.name)}</div>
</div>
</div>
{this.state.expanded
? node.contents.map((content, index) =>
renderTree(
content,
this.props.setActiveNode,
this.props.activeNode,
index,
this.props.searchTerm
)
)
: ""}
</div>
);
}
}
// Set class Active to selected file
const File = ({ name, setActiveNode, activeNode, searchTerm }) => {
if (searchTerm && name.toLowerCase().indexOf(searchTerm.toLowerCase()) < 0)
return null;
let isActive = activeNode === name;
let className = isActive ? "active" : "";
return (
<div className={className + " file"} onClick={() => setActiveNode(name)}>
<div className="file__icon">
<FileIcon />
</div>
<div className="file__name">{formatName(name)}</div>
{isActive && <div className="file__options">...</div>}
</div>
);
};
var renderTree = (node, setActiveNode, activeNode, index, searchTerm) => {
if (node.type === "file") {
return (
<File
key={index}
name={node.name}
setActiveNode={setActiveNode}
activeNode={activeNode}
searchTerm={searchTerm}
/>
);
} else if (node.type === "directory") {
return (
<Directory
key={index}
node={node}
setActiveNode={setActiveNode}
activeNode={activeNode}
searchTerm={searchTerm}
/>
);
} else {
return null;
}
};
/**** APP ****/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNode: ""
};
this.onChange = this.onChange.bind(this);
}
liftStateUp = (data) => {
this.setState({ activeNode: data });
};
onChange(data) {
this.setState({ searchTerm: data });
}
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine className="form-control" onChange={this.onChange} />
</div>
<div className="col-md-6">
<FileTree
liftStateUp={this.liftStateUp}
searchTerm={this.state.searchTerm}
/>
</div>
<div className="col-md-6">
<TextBox content={this.state.activeNode}/>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
* {
font-family: Helvetica;
color: #333333 !important;
}
/** DIRECTORY CSS **/
.directory {
padding-left: 10px;
padding-top: 1px;
padding-bottom: 1px;
display: flex;
flex-direction: row;
align-items: center;
}
.directory__toggle {
padding-left: 10px;
transform: rotate(-90deg)
}
.directory__icon {
padding-left: 10px;
}
.directory__icon {
padding-left: 10px;
}
.directory__name {
padding-left: 10px;
}
.directory-container {
padding-left: 10px;
}
/** FILE CSS **/
.file {
padding-left: 50px;
padding-top: 1px;
padding-bottom: 1px;
display: flex;
}
.file__icon {
padding-left: 10px;
}
.file__name {
padding-left: 10px;
}
.file__options {
align-self: flex-end;
}
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.svg-icon {
width: 1em;
height: 1em;
}
.expanded {
transform: rotate(0deg)
}
/** CONTENT BOX **/
.padd_top {
padding-top: 20px;
}
.btn-danger {
color: #fff !important;
}
.content_box {
font-size: 12px;
white-space: pre-wrap;
border: solid 1px black;
padding: 20px;
color: #9da5ab;
min-height: 250px;
width: 100%;
}
.text_color {
color: #21252b !important;
}
/** arrow animation **/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
Your problem is data propagation. You gave data to FileTree Component and your SearchEngine is on the same level as the FileTree Component, and it cannot access the data which has to be filtered. I lifted up data to App Component - parent of SearchEngine and FileTree Component and propagated data to FileTree. Instead of propagating data to SearchEngine - I lifted onSubmitSearch event handler to App Component and propagated it to SearchEngine, because even if I gave data to SearcEngine I couldn't update it on FileTree Component (because of unidirectional data flow).
// Dummy data set
var root = data[0];
/**** APP ****/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNode: "",
root: root
};
this.onChange = this.onChange.bind(this);
}
liftStateUp = data => {
this.setState({ activeNode: data });
};
onSubmitSearch = (e, search) => {
let tree = JSON.stringify(root); // always search full data tree
tree = JSON.parse(tree); // JSON.stringify and then JSON.parse are
if (!search || search === "") {
// if search is undefined, null or empty, set root to full data tree
this.setState({ root: tree }); // state.root is filtered tree passed to the FileTree component
return;
}
/*uncoment if you need to filter already filtered tree*/
// tree = JSON.stringify(this.state.root);
// tree = JSON.parse(tree);
/**/
// else filter tree
this.setState({
root: this.filterTree(tree, search.toLowerCase())
});
};
filterTree = (data, search) => {
let children = data.contents;
if (!children || !children.length) {
if (!data.name.toLowerCase().includes(search)) {
data.remove = true;
}
} else {
for (let i = children.length - 1; i >= 0; i--) {
this.filterTree(children[i], search);
if (children[i].remove) {
children.splice(i, 1);
}
}
if (!children.length) {
data.remove = true;
}
}
return data;
};
onChange(data) {
this.setState({ searchTerm: data });
}
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine
className="form-control"
onChange={this.onChange}
onSubmitSearch={this.onSubmitSearch}
/>
</div>
<div className="col-md-6">
<FileTree
root={this.state.root}
liftStateUp={this.liftStateUp}
searchTerm={this.state.searchTerm}
/>
</div>
<div className="col-md-6">
<TextBox content={this.state.activeNode} />
</div>
</div>
);
}
}
Note that App now has onSubmitSearch function which is then propagated to SearchEngine where it is called with the search input value:
/**** SEARCH COMPONENT ****/
class SearchEngine extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.onInputChange = this.onInputChange.bind(this);
}
onInputChange(e) {
const content = e.target.value;
this.setState({ value: content });
}
render() {
const { onSubmitSearch } = this.props;
const { value } = this.state;
return (
<div>
<form onSubmit={e => onSubmitSearch(e, value)}>
<input
className="form-control"
value={this.state.value}
type="text"
onChange={this.onInputChange}
/>
<p>{this.state.value}</p>
<SearchButton />
</form>
</div>
);
}
}
And the FileTree Component now gets filtered data (by search input value / search engine) and takes care about rendering FileTree Component only.
Take a look at the working example of filtering tree structure with submit button here: https://codesandbox.io/s/3rnvv0kln6
Related
I want to know it's normal to reuse react key from component to component. In Row component i got key From column component and reuse it for mapping Row childrens
const Table = props => {
const { data, children, showHeader } = props;
return (
<div className="table">
{showHeader && <TableHead children={children} />}
{data.map(data => {
return <Row key={data.id} data={data} children={children} />;
})}
</div>
);
};
const TableHead = props => {
const { children } = props;
return (
<div className="table-row">
{children.map(col => {
const { title, field, width } = col.props;
return (
<div className="table-col" key={field}>
<div
className="table-cell table-cell-head"
style={{
width: width
}}
>
{title}
</div>
</div>
);
})}
</div>
);
};
//HERE I GET key from Column component and reuse it for rows (number of columns === number of rows)
const Row = props => {
const { data, children } = props;
return (
<div className="table-row">
{children.map(col => {
let { field, ...rest } = col.props;
const { key } = col;
return (
<div className="table-col" key={key}>
{React.cloneElement(col, {
...rest,
field,
data
})}
</div>
);
})}
</div>
);
};
const Column = props => {
const { field, data, width } = props;
return (
<div
style={{
width: width
}}
className="table-cell"
key={data.id}
>
{data[field]}
</div>
);
};
const HeadRow = props => {
const { children } = props;
return (
<div className="table-row">
{children.map(col => {
const { title, field, width } = col.props;
return (
<div className="table-col" key={field}>
<div
className="table-cell table-cell-head"
style={{
width: width
}}
>
{title}
</div>
</div>
);
})}
</div>
);
};
const initData = [
{
id: 1,
name: "Ivan",
age: "My age is 27",
enabled: true,
myListValue: [
{
myName: "Duncan",
myDescription: "Immortal!",
myGroup: "Immortal",
createDate: "2019-08-12T05:21:28Z"
}
],
lastChanged: new Date(),
sortable: true
},
{
id: 2,
name: "Vitaly",
age: `My age is 25\nMy age is 25\nMy age is 25\n`,
lastChanged: new Date(),
enabled: true,
sortable: true
},
{
id: 3,
name: "Sergey",
age: "My age is 29",
enabled: true,
myListValue: [
{
myName: "Duncan",
myDescription: "Immortal!",
myGroup: "Immortal",
createDate: "2019-08-12T05:21:28Z"
},
{
myName: "Connor",
myDescription: "Immortal2!",
myGroup: "MacLeods",
createDate: "2019-08-12T05:21:28Z"
},
{
myName: "John Snow",
myDescription: "(he knows nothing)",
myGroup: "WhiteWalkers",
createDate: "2019-08-12T05:21:28Z"
},
{
myName: "Jamie Lannister",
myDescription: "Lannisters always pay their debts",
myGroup: "Red castle",
createDate: "2019-08-12T05:21:28Z"
}
],
lastChanged: new Date()
}
];
ReactDOM.render(
<Table data={initData} showHeader={true} sortableConfig={{}}>
<Column key="0" field="name" width={150} title="Name" sortable="true" />
<Column key="1" field="age" width={150} title="AGe" sortable="true" />
</Table>, document.getElementById('root'))
.App {
font-family: sans-serif;
text-align: center;
display: flex;
flex-wrap: wrap;
}
.table {
border: 1px solid;
border-bottom: none;
}
.table-row {
width: 100%;
border-bottom: 1px solid;
display: flex;
}
.table-col {
border-right: 1px solid;
}
.table-col:last-child {
border: none;
}
.table-cell {
white-space: pre-line;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
box-sizing: border-box;
}
.table-cell-head {
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Keys help React identify which items have changed, are added, or are
removed.
Keys should be given to the elements inside the array to give them a stable identity, and they only make sense in the context of the surrounding array, so it doesn't matter if two isolated lists have elements with equal keys as long as they are not equal inside the same list (repeated ids). There is no problem with the following code
{
arr.map((item,index) =>{
//defining key only once per `arr` iteration
const key = uniqueId(index)
return(
<div key={key}>
{
arr2.map((item, index) => <span key={key} />)
}
</div>
)
})
}
Note that the key is only relevant within a particular parent React element. React won’t try to “match up” elements with the same keys between different parents. (React doesn’t have idiomatic support for moving a host instance between different parents without re-creating it.) Font
See the docs here
The tutorial avoids using nested components doing this:
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
Suppose one wanted to nest the cells inside a row like this:
function Square(props) {
return (
<div onClick={props.onClick} className="square">
[{props.row}.{props.cell}]
</div>
);
}
function Row(props) {
return <div className="row">{props.children}</div>;
}
function Cells(props) {
let squares = [];
for (let i = 0; i < COLUMNS; i++) {
squares.push(<Square row={props.row} cell={i} onClick={props.onClick} />);
}
return squares;
}
Then Inside Board class how one would create a callback with both row and column? I'm able to pass row, but I can't see how to pass the column click as this is actually inside Cells. The state is lifted up in Board.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
board: [
[{ value: 1, visible: 0 }, { value: 2, visible: 0 }],
[{ value: 2, visible: 0 }, { value: 1, visible: 0 }]
],
player_turn: 0, //0,1,2,3
player1: 0,
player2: 0
};
}
handleClick(e) {
alert(e.row + "." + e.cell);
}
renderSquares() {
const rows = [];
for (let i = 0; i < ROWS; ++i) {
let event = { row: i, cell: -1 };
rows.push(
<Row>
<Cells
row={i}
values={this.state.board[i]}
onClick={() => this.handleClick(event)}
event={event}
/>
</Row>
);
}
return rows;
}
render() {
return <div>{this.renderSquares()}</div>;
}
}
Just pass down column and handleClick! Check out the code below. And I don't really see what the Cells component is doing, so didn't keep that, but you could achieve the same thing even if you kept Cells. Now Row has a bunch of Squares as children which makes sense to me :)
function Square({ row, column, onClick, cell }) {
return (
<div className="square" onClick={() => onClick({ row, column, ...cell })}>
[{row}.{column}]
</div>
);
}
function Row({ children }) {
return <div className="row">{children}</div>;
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
board: [
[{ value: 1, visible: 0 }, { value: 2, visible: 0 }],
[{ value: 2, visible: 0 }, { value: 1, visible: 0 }]
],
playerTurn: 0, //0,1,2,3
player1: 0,
player2: 0
};
}
handleClick({ row, column, value, visible }) {
let { playerTurn } = this.state;
playerTurn++;
this.setState({ playerTurn });
alert(`${row}.${column} - value: ${value}, visible: ${visible} - player turn: ${playerTurn}`);
}
renderSquares() {
return this.state.board.map((cells, row) => (
<Row key={row}>
{cells.map((cell, column) => (
<Square
cell={cell}
key={column}
row={row}
column={column}
onClick={this.handleClick.bind(this)}
/>
))}
</Row>
));
}
render() {
return <div>{this.renderSquares()}</div>;
}
}
ReactDOM.render(<Board />, document.getElementById("app"));
.row {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></app>
I have an array of objects and when the user inputs a zipcode and click I want to loop through the array and if the users zipcode matches a zip code in the array output results if the zipcode dose not match output another result
I have attempted to use map and forEach on the array and each allow me to find the zipcode and provide out put Im running into trouble when the zipcodes do not match
class PricingTool extends Component {
state = {
zipCode: "",
finalZip: "",
routine: "",
rush: "",
sameDay: "",
city: "",
match: false
};
handleKeypress = e => {
const { zipCode } = this.state;
if (e.which === 13 && zipCode.length === 5) {
console.log("enterrerre");
this.zipcodeHandler();
}
};
zipcodeHandler = () => {
const { zipCode } = this.state;
this.setState({
finalZip: zipCode,
});
};
changeHandler = e => {
e.preventDefault();
if (e.target.value.length <= 5 && !isNaN(e.target.value)) {
this.setState({
[e.target.name]: e.target.value
});
}
};
render() {
const { finalZip, zipCode, match } = this.state;
let searchResult;
if(finalZip){
searchResult = zipCodes.map(cur => {
if (finalZip && finalZip == cur.zip) {
return (
<div className="pricing__searchResult">
<h1 className="pricing__searchResult-1">
We do serve in {cur.city}, Indiana
</h1>
<div className="pricing__searchResult-2">Same-Day</div>
<div className="pricing__searchResult-3">{cur.fees.sameDay}</div>
<div className="pricing__searchResult-4">Rush</div>
<div className="pricing__searchResult-5">{cur.fees.rush}</div>
<div className="pricing__searchResult-6">Routine</div>
<div className="pricing__searchResult-7">{cur.fees.routine}</div>
<div className="pricing__searchResult-8">
Please call us or email info#ccprocess.com to order
</div>
</div>
);
}
});
}
I would like it to find the user inputed zip code if it is in the data array and if it is not then render another message
Instead of using a Array map method, which would map each value of the array to something else (in your case, it would only map the zipcode found), you can (and should) use a better method for the job. The find method will find the first item that meet your criteria and return it, in your case, it could be finalZip && (finalZip == cur.zip). If no item is found for the expression given, undefined is returned.
render() {
const { finalZip, zipCode, match } = this.state;
let searchResult;
if(finalZip){
searchResult = zipCodes.find(cur => finalZip && (finalZip == cur.zip));
if(searchResult) {
// do something for when the zip code is found
}
else {
// do something when no zip code is found
}
}
}
Array find method documentation: MDN
Being not sure what all components you used or are currently in your application, I created a sample app which kind of simulates your requirement and possibly delivers the correct output.
So here, I had no idea of where the input will be taken from the user and how your onKeyPress listener would work, so I created another component that renders your data based on the input and check if that zipcode exist or not.
Like shown below, your ZipCode related information will be render by another component ZipTool and your input box is handled by PricingTool
Also, here's the jsfiddle if you want to play around with the code - https://jsfiddle.net/hf9Ly6o7/3/
I hope you find this useful.
class ZipTool extends React.Component {
constructor(props) {
super(props);
}
render() {
let { data } = this.props;
return (
<div>
<h1 className="pricing__searchResult-1">We do serve in { data.city }, Indiana</h1>
<div className="pricing__searchResult-2">Same-Day</div>
<div className="pricing__searchResult-3">{ data.fees.sameDay }</div>
<div className="pricing__searchResult-4">Rush</div>
<div className="pricing__searchResult-5">{ data.fees.rush }</div>
<div className="pricing__searchResult-6">Routine</div>
<div className="pricing__searchResult-7">{ data.fees.routine }</div>
</div>
);
}
}
class PricingTool extends React.Component {
constructor(props) {
super(props)
this.state = {
zipCodes: [
{
'zipcode': '12345',
'city': 'abc',
'fees': {
'sameDay': '43',
'rush': '90',
'routine': '20'
}
},
{
'zipcode': '54321',
'city': 'xyz',
'fees': {
'sameDay': '25',
'rush': '35',
'routine': '10'
}
}
],
zipCode: "",
finalZip: "",
routine: "",
rush: "",
sameDay: "",
city: "",
match: false
}
}
changeHandler(e) {
this.setState({
zipCode: e.target.value,
});
for (let code of this.state.zipCodes) {
if( code.zipcode === e.target.value ){
this.setState({match: true, finalZip: code})
break;
}
else {
this.setState({match: false, finalZip: null})
}
}
};
render() {
return (
<div>
<input type="text" onChange={this.changeHandler.bind(this)} onKeyPress={this.handleKeyPress} name={this.state.zipCode}></input>
<div className="pricing__searchResult">
{ this.state.finalZip ? <ZipTool data={this.state.finalZip} /> : <div>Not Found</div> }
<div className="pricing__searchResult-8">
Please call us or email info#ccprocess.com to order
</div>
</div>
</div>
)
}
}
ReactDOM.render(<PricingTool />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
What I want to do is to change the border on the input to red if the input value doesn't match any movie in the API call.
The user types in the input field and the call to the API shows the matching result.
If we don't have any result I would like the border on the input to be red.
But I can't see how I should make that happen.
The component Input is at the end of the code snippet.
CSS
.input-style {
padding: 7px;
border-radius: 5px;
border: 1px solid #cccccc;
font-family: Courier New, Courier, monospace;
transition: background-color 0.3s ease-in-out;
outline: none;
}
.input-style:focus {
border: 1px solid turquoise;
}
APP.js
class App extends Component {
constructor() {
super();
this.state = {
value: '',
items: [],
isLoading: false,
searchResult: null,
error: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// To handle search
handleChange(e) {
this.setState({ value: e.target.value });
}
handleSubmit(e) {
let searchResult = [];
for (var i = 0; i < this.state.items.length; i++) {
if (
this.state.items[i].name
.toLowerCase()
.indexOf(this.state.value.toLowerCase()) !== -1
) {
searchResult.push(this.state.items[i]);
} else {
console.log('No matches on your search, try again');
}
}
e.preventDefault();
// If we have something in the object searchResult
if (searchResult.length > 0) {
this.setState({
error: false,
value: '',
searchResult: searchResult,
});
} else {
this.setState({
error: true,
value: '',
searchResult: [],
});
}
}
// call to the API
componentDidMount() {
this.setState({ isLoading: !this.state.isLoading });
fetch('https://api.tvmaze.com/shows')
.then(response => response.json())
.then(data => {
this.setState({
items: data,
error: false,
});
this.setState({ isLoading: !this.state.isLoading });
})
.catch(console.error);
}
render() {
return (
<div className="App">
<Header />
<Loader isLoading={this.state.isLoading} />
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
/>
{this.state.error ? (
<p className="errorMsg">No match on the search, try again!</p>
) : null}
<Search search={this.state.searchResult} />
</div>
);
}
}
export default App;
Input.js
function Input(props) {
return (
<div>
<form onSubmit={props.handleSubmit}>
<input
type="text"
className="input-style"
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
<button id="bold" className="button-style" type="submit">
<i className="fa fa-search" />
</button>
</form>
</div>
);
}
export default Input;
You can pass the error into the Input component from the App
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
error={this.state.error)
/>
and in your Input component:
<input
type="text"
className={props.error ? 'error-input-style' : 'input-style'}
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
alternative you can also set an inline styling for the error condition:
<input
type="text"
className="input-style"
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
style={{ border: props.error ? '1px solid red' : '' }}
/>
You can easily do this. Have a flag, say resultFound, in the state of App.js, with an initial value of false. Then, in the function where you make the API call, update this resultFound depending on whether any result was obtained.
And, in the render(), before returning, assign inputClassName dynamically based on the this.state.resultFound, like so,
let inputClassName = '';
if (this.state.resultFound === false) {
inputClassName = 'input-style-error'; // new CSS class for errors
} else {
inputClassName = 'input-style';
}
Then, you can pass the inputClassName as a prop to Input and use it as <input>'s className, like so,
// in your App.js render() method's return
// ... existing components
<Input customStyle={inputClassName} ... />
// ...
<!-- in your Input.js render() method -->
<input type="text" className={props.customStyle} ... />
Whenever the API call happens, your state will change causing a re-render of the DOM (render() is called). During each call, we dynamically set the inputClassName based on the state's resultFound. And, accordingly, the right className will be applied to the <input>.
I will give bad names for classes and variables, just to make it super clear. You should use more generic ones.
The trick here is to give your Input a dynamic class via props, and if that expression turns true and the class is appended to the element, you can style it with css.
__CSS__
.input-style {
padding: 7px;
border-radius: 5px;
border: 1px solid #cccccc;
font-family: Courier New, Courier, monospace;
transition: background-color 0.3s ease-in-out;
outline: none;
}
.input-style:focus {
border: 1px solid turquoise;
}
.input-style.red-border {
border: 1px solid red;
}
__APP.js__
class App extends Component {
constructor() {
super();
this.state = {
value: '',
items: [],
isLoading: false,
searchResult: null,
error: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// To handle search
handleChange(e) {
this.setState({ value: e.target.value });
}
handleSubmit(e) {
let searchResult = [];
for (var i = 0; i < this.state.items.length; i++) {
if (
this.state.items[i].name
.toLowerCase()
.indexOf(this.state.value.toLowerCase()) !== -1
) {
searchResult.push(this.state.items[i]);
} else {
console.log('No matches on your search, try again');
}
}
e.preventDefault();
// If we have something in the object searchResult
if (searchResult.length > 0) {
this.setState({
error: false,
value: '',
searchResult: searchResult,
});
} else {
this.setState({
error: true,
value: '',
searchResult: [],
});
}
}
// call to the API
componentDidMount() {
this.setState({ isLoading: !this.state.isLoading });
fetch('https://api.tvmaze.com/shows')
.then(response => response.json())
.then(data => {
this.setState({
items: data,
error: false,
});
this.setState({ isLoading: !this.state.isLoading });
})
.catch(console.error);
}
render() {
return (
<div className="App">
<Header />
<Loader isLoading={this.state.isLoading} />
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
showRedBorder={this.state.error === true} // or what ever your logic
/>
{this.state.error ? (
<p className="errorMsg">No match on the search, try again!</p>
) : null}
<Search search={this.state.searchResult} />
</div>
);
}
}
export default App;
__Input.js__
function Input(props) {
return (
<div>
<form onSubmit={props.handleSubmit}>
<input
type="text"
className={`input-style${props.showRedBorder ? ' red-border' : ''}`}
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
<button id="bold" className="button-style" type="submit">
<i className="fa fa-search" />
</button>
</form>
</div>
);
}
export default Input;
I'm trying to use data from an API (https://messi.hyyravintolat.fi/publicapi/restaurant/11/) in my React project. I was able to render each "date" from the API, but how do I render each "name" for each "date" in with this kind of .json (the ones immediately inside the "data" arrays in the API)? {item.data[name]} doesn't seem to be the way to do this. Here is the component class I'm using to get and render the data:
import React from 'react';
/*eslint-env jquery*/
class TestMenu extends React.Component {
constructor(props) {
super(props);
this.state = { food: [] };
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://messi.hyyravintolat.fi/publicapi/restaurant/11/')
.then(({ data }) => this.setState({ food: data }));
}
render() {
const foods = this.state.food.map((item, i) => (
<div>
<h1>{item.date}</h1>
<p>{item.data[name]}</p>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{foods}</div>
</div>
);
}
}
export default TestMenu;
After looking at the datasource, the JSON is in the following shape:
[
{
"date": some date,
"data": [
{
"name": some name,
...
},
{
"name": some other name,
...
}
...
]
},
{
"date": some other date,
"data": [ ... ]
},
...
]
So there are several names for a single date. You could render this like the following:
<h1>{item.date}</h1>
<ul>
{item.data.map((d, idx) => {
return <li key={idx}>{d.name}</li>
)}
</ul>
Note that I also use the indices when mapping the data in order to provide a unique key to React for each <li> element.
That's because you need a second loop as data (the nested one) is an array and not an object.
A simple example of looping with your data structure:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list: []
};
}
componentDidMount() {
axios
.get("https://messi.hyyravintolat.fi/publicapi/restaurant/11/")
.then(({ data }) => {
this.setState({
list: data.data
});
});
}
render() {
const { list } = this.state;
return (
<div>
<ul>
{
list && list.map((obj) => {
return (
<li className="item">
<div className="date">
<span>Date: </span> <span>{obj.date}</span>
</div>
<div className="names">
{
obj.data.map((obj2) => {
return (
<div className="name">
<span>Name: </span> <span>{obj2.name}</span>
</div>
)
})
}
</div>
</li>
)
})
}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.item {
list-style: none;
border: 1px solid #ccc;
padding: 10px;
}
.date {
font-weight: bold;
}
.names {
text-indent: 15px;
}
.name{
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>