This may be a quick fix but I have been racking my brain for the past little while, and could really use another set of eyes to take a look.
Basically I am trying to render an array full of generated JSX elements. I fell like I have done this a million times, but it does not seem to work here.
Heres the code:
import React, { Fragment } from 'react'
import css from './Search.scss';
import Header from '../SectionHeader/Header';
import SearchItem from '../SearchItem/SearchItem';
const Search = (props) => {
const { coinObject, coinKeys } = props;
let searchResults = []; // Array in question
const findResults = (searchText) => {
searchResults = []; // Reset the array to blank for each new character typed in input
for(let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]]; // App specific logic, not important, or the problem here
if(FullName.toLowerCase().includes(searchText) && (searchResults.length < 5)) {
console.log(FullName, searchText); // Prints the correct Full name based off of the searched text
searchResults.push(<SearchItem key={i} searchText={FullName} />);
}
}
console.log(searchResults); // Prints the updated array with all react elements
}
return (
<Fragment>
<Header title='Find Coins!' />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input onChange={input => findResults(input.target.value)} className={css.searchInput} type='text' placeholder='Start Typing a Coin'/>
</div>
{ searchResults }
</div>
</Fragment>
);
}
export default Search;
And the SearchItem Component, which is super simple:
import React from 'react'
import css from './SearchItem.scss';
const SearchItem = (props) => {
return (
<div className={css.searchItem}>
{props.searchText}
</div>
)
}
export default SearchItem;
For a little bit of context, this component just gets a giant object of data, and will display the first 5 instances of what matches the input text. I am trying to make one of those search filter things, where as you type it suggests things that match from the data.
The array gets updated, and I can see the JSX objects in the array, they just do not render. I have a feeling it is due to the array not re-rendering?
Any help is much appreciated. Thanks!
You could make the Search component into a stateful component and store the searchResults in your state instead, so that when it is updated your component will be re-rendered.
Example
class Search extends React.Component {
state = { searchResults: [] };
findResults = searchText => {
const { coinObject, coinKeys } = this.props;
const searchResults = [];
for (let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]];
if (
FullName.toLowerCase().includes(searchText) &&
searchResults.length < 5
) {
searchResults.push(FullName);
}
}
this.setState({ searchResults });
};
render() {
return (
<Fragment>
<Header title="Find Coins!" />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input
onChange={event => findResults(event.target.value)}
className={css.searchInput}
type="text"
placeholder="Start Typing a Coin"
/>
</div>
{this.state.searchResults.map((fullName, i) => (
<SearchItem key={i} searchText={fullName} />
))}
</div>
</Fragment>
);
}
}
Related
I am trying to use the "show button" to render the Countryinfo component in the given code but I know what I am doing is doing wrong and maybe I need to add state to the list but i am really struggling as to how?
The search function is to work as follows:
The fetched database is filtered acc. to input value and,
If the matches turn out >10 then no list is rendered and,
if the matches turn out between 2-10 then the list of matching countries with a show button is presented, which is supposed to display the countryinfo of that country [THE BUTTON DOESNT WORK ]
when the input value only matches one country then the countryinfo component is rendered
codesandbox link : https://codesandbox.io/s/country-db-issue-forked-kzkyr?file=/src/App.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
/*
const divMagnify={
transform: 'scale(4)'
};
*/
const Countryinfo = (props) => {
console.log('name', props.cty[0].name.common);
console.log('lang', props.cty[0].languages);
console.log('flag', props.cty[0].flag);
return (
<div>
<h1>{props.cty[0].name.official}</h1>
<h4>aka {props.cty[0].name.common} </h4>
<p>Capital: {props.cty[0].capital}</p>
<p>Population: {props.cty[0].population}</p>
<h2>Languages</h2>
<ul>
{Object.entries(props.cty[0].languages).map(([key, value], index) => {
return <li key={index}>{value}</li>;
})}
</ul>
<h3>Flag</h3>
<div>{props.cty[0].flag}</div>
</div>
);
};
const Displaycountry = (props) => {
console.log('length', props.name.length);
let len = props.name.length;
if (len > 10) {
console.log('too many');
return <div>Too many matches, specify another filter</div>;
} else if (len === 1) {
return <Countryinfo cty={props.name} />;
} else if (1 < len && len <= 10) {
return (
<div>
{props.name.map((item, index) => {
return (
<li key={index}>
{item.name.common}
<button onClick={() => <Countryinfo cty={item} />}>Show</button>
</li>
);
})}
</div>
);
} else {
return null;
}
};
function App() {
const [countrydb, setCountrydb] = useState([]);
const [countryName, setCountryName] = useState([]);
useEffect(() => {
axios.get('https://restcountries.com/v3.1/all').then((response) => {
setCountrydb(response.data);
});
}, []);
const handleCountrySearch = (e) => {
console.log(e.target.value);
setCountryName(
countrydb.filter((item) => {
return item.name.common
.toLowerCase()
.includes(e.target.value.toLowerCase());
})
);
};
return (
<div>
<section>
<h3>Search Country Database</h3>
<input onChange={handleCountrySearch} />
</section>
<section>
<Displaycountry name={countryName} />
</section>
</div>
);
}
export default App;
You can try like this https://codesandbox.io/s/country-db-issue-forked-nvq2y
I've changed the return of Displaycountry and saved to state index of the clicked country to show its info. Also, I've changed the Countryinfo component, so it can display the value of one single country (not an array)
I'm trying to create an onClick event, that will select the pressed mapped item. Could anyone help?
const renderExample= () => {
return example.map((arrayItem, i) => {
const example = arrayItem.example;
const song =
arrayItem.song ||
"urltosong";
...
then in return
<div key={i}>
<SELECTABLE>
{example}
</SELECTABLE>
<SONG>{song}</SONG>
</div>
render return <div>{renderData()}</div>;
At the moment I have a list of selectable'examples' rendering. But I want to know which example has been pressed by the user specifically.
You can pass the complete item to the handleClick to play around. The code would be
const App = () => {
function handleClick(item){
console.log('item,item);
}
function renderData(){
// assuming you have data in example array
return example && example.map(item=> {
const example = arrayItem.example;
const song =arrayItem.song || "urltosong";
return (
<div key={i} onClick={()=>{handleClick(item)}>
<SELECTABLE>
{example}
</SELECTABLE>
<SONG>{song}</SONG>
</RecentMessages>
</div>
)
})
}
return <div>{renderData()}</div>
}
Maintain state for selected song and update when selection change.
Here is minimal working sample with stackblitz
import React, { Component } from 'react';
import { render } from 'react-dom';
const songs = ["first song", "song 2", "hello song"];
class App extends Component {
constructor() {
super();
this.state = {
song: 'hello song'
};
}
render() {
return (
<div>
<select onChange={(ev) => this.setState({song: ev.target.value})} value={this.state.song}>
{songs.map(x => <option value={x}> {x} </option>)}
</select>
<p>
{this.state.song}
</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));
You can play with it like this:
// Supposing you're using React Functional Component
// Click handler
const handleSelect = (elementIndex) => {
console.log(`You clicked on element with key ${elementIndex}`)
}
// render()
// ... some other to-render stuff
// implying that code below have access to element's index (i in your map)
<Selectable onClick={e => handleSelect(i)}>
{example}
</Selectable>
// ...
I have a React class that takes a JSON object array and outputs a set of divs representing keys and values. The thing is, each object in the json has around 60 key value pairs inside; in this example i am rendering the divs for the 19th index for each of the objects:
import React, { Component } from "react";
import "./Maps.css";
import df3 from "./data/df3.json"
import sample from "./data/sample.json"
class Maps extends Component {
constructor() {
super();
const data = df3;
this.state = data
}
renderDiv = () => {
var df4 = df3["Devotions"];
return df4.map(v => {
return Object.keys(v).map((host) => {
return (
<div class={host}>
{host} {v[host][19]}
<div class='space' style={{ borderRadius:'19px',
transform:`scale(${v[host][19]},${v[host][19]})`,
opacity:'9%'}} >
</div>
</div>
);
});
});
};
render() {
return <div id="Maps">{this.renderDiv()}</div>;
}
}
export default Maps
what I would like to do is control the rendering, so that the divs for each index appears sequentially on the screen.
return Object.keys(v).map((host) => {
return (
<div class={host}>
{host} {v[host][19]}
<div class='space' style={{ borderRadius:'19px',
transform:`scale(${v[host][19]},${v[host][19]})`,
opacity:'9%'}} >
</div>
</div>
Im not sure if if should just wrap all of sets of divs id like to return in a single div, and just have them connected to a single keyframe, but im not sure if theres a more elegant way to do it.
As always, help is appreciated!
I think this is what you need :
Considering, you have multiple objects, inside each object, there are some data in array, and you want to display all of them in sequence.
renderDiv = () => {
var df4 = df3["Devotions"];
let updatedArray = [];
df4.forEach(v => { //<--- no need of map
Object.keys(v).forEach((hosts) => { //<--- no need of map
updatedArray = [...updatedArray ,
...v[hosts].map((host) => {
return (
<div className={host}>
{host} {host}
<div className='space' style={{ borderRadius:'19px',
transform:`scale(${host},${host})`,
opacity:'9%'}} >
</div>
</div> )
})
]
})
})
return updatedArray;
}
I have a Grid with 3*3 squares.
When a click on a square , we change the background color to green.
So, I tried to put the all the states in the parent GridContainer.
state = {
gridCells: []
};
This will hold the indices that are clicked.
GridContainer nests Grid and Grid nests Square.
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
Here is my current implementation.
Now how do I clear the background cells when I reset clicks and make the background back to white again?
function Square(props) {
const liClickHandler = event => {
event.target.classList.add("highlight");
props.clickAction();
};
return <li onClick={e => liClickHandler(e)} />;
}
function Grid(props) {
const gridHandler = index => {
props.action(index);
};
return (
<ul>
{Array.from(new Array(9)).map((item, index) => (
<Square key={index} clickAction={() => gridHandler(index)} />
))}
</ul>
);
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
handler = index => {
let temp = [...this.state.gridCells];
temp.push(index + 1);
this.setState({
gridCells: temp
});
};
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
}
So when I click a Sqaure , it calls a method clickAction that calls handler
that updates the state and we have an array which indices were clicked in order.
How do I implement Reset clicks that updates the background of those Sqaures back to white ? How do I let know my child know.
Am I maintaining the state wrong?
Sandbox link : https://codesandbox.io/s/3-x-3-grids-s0b43?file=/src/index.js:640-1563
I'd advise to rethink the way how your components are structured.
Each component should be independent unit with it's own logic and state (if needed of course). I'm saying if needed for state, cause ideally components should be stateless.
There are several problems with Square class:
It adds class via event.target, which is not react way to go. React works with virtual DOM and has it's own set of methods to interact with html. Working with DOM directly - will bite your later, starting from writing tests for your code in the future.
It does not contain incoming information whether it should be highlighted or not
Both these problems result in fact that you cannot reset presentation of your squares easily.
I've updated your sample: https://codesandbox.io/s/3-x-3-grids-uflhr?file=/src/index.js
It's still not ideal, but you can notice that gridCells is passed from top via props. And then each square gets own props param. This allows state to come through the flow and let squares rerender with updated class.
In react you should think the "react" way:
pass the necessary state down through the props
pass down the callbacks so that children can update the parent state
Here is corrected version of the demo:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Square(props) {
return (
<li onClick={props.onClick} className={props.active ? "highlight" : ""} />
);
}
function Grid(props) {
let squares = [];
for (let i = 0; i < 9; i++) {
squares.push(
<Square
key={i}
onClick={() => props.onCellClick(i)}
active={props.cells[i]}
/>
);
}
return <ul>{squares}</ul>;
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
onCellClick = index => {
this.setState(prevState => {
const newCells = [...prevState.gridCells];
newCells[index] = true;
return {
gridCells: newCells
};
});
};
render() {
return (
<div>
<Grid cells={this.state.gridCells} onCellClick={this.onCellClick} />
<button
onClick={() => {
let that = this; //we could bind the callback aswell
that.setState(() => ({ gridCells: [] }));
}}
>
Reset Clicks
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<GridContainer />, rootElement);
Backstory
Note: This question is an expansion to the answer T.J. Crowder provided here.
I expanded on this by creating a state to hold the rows array so that I could update the array (remove from) using setState.
I added a handleClick function to handle what I would like the user to be able to do to the array based on the index of the element being clicked. (currently all that is included is that right click removes the target index from the array.
/rowList.js
import React, { Component } from "react";
import Row0 from "./../rows/row0";
import Row1 from "./../rows/row1";
import Row2 from "./../rows/row2";
import Row3 from "./../rows/row3";
const row0 = () => <Row0 />;
const row1 = () => <Row1 />;
const row2 = () => <Row2 />;
const row3 = () => <Row3 />;
class RowInfo {
static id = 0;
constructor(Comp) {
this.Comp = Comp;
this.id = RowInfo.id++;
}
}
class RowList extends Component {
constructor() {
super();
this.state = {
rows: [
new RowInfo(row0),
new RowInfo(row1),
new RowInfo(row2),
new RowInfo(row3)
]
};
}
handleClick = (a, b) => e => {
e.preventDefault();
if (e.nativeEvent.which === 1) {
//left click
console.log(a); //for testing
console.log(b); //for testing
} else if (e.nativeEvent.which === 3) {
//right click
this.setState({
rows: this.state.rows.filter((_, i) => i !== a)
});
}
};
render() {
return (
<>
{this.state.rows.map(({ id, Comp }) => (
<tr
key={id}
onClick={this.handleClick(id)}
onContextMenu={this.handleClick(id)}
>
<Comp />
</tr>
))}
</>
);
}
}
export default RowList;
I then tested calling the <RowList /> component twice so that I can test removing rows across two components.
Note: Mod0 is imported into a /main.js file which is imported to /index.js which is rendered to <div id="root"></div> in index.html
/mod0.js
import React, { Component } from "react";
import RowList from "./../rows/rowList";
class Mod0 extends Component {
render() {
return (
<>
<table>
<tbody>
<RowList />
</tbody>
</table>
<table>
<tbody>
<RowList />
</tbody>
</table>
</>
);
}
}
Problem
When testing the removal I realised a crucial error, the nature of which my current knowledge can only make me relatively certain of so my explanation may be innacurate/flawed. It would seem I can only remove from the first 4 rows that are rendered in <RowList /> as the second 4 rows do not corrospond to the array in this.state.rows.
I think the solution is to build a new array based on the original state array and the amount of times <RowList /> is called. And render that array instead.
Perhaps I could update the state could look something like this to begin with:
constructor() {
super();
this.state = {
rows: [
new RowInfo(row0),
new RowInfo(row1),
new RowInfo(row2),
new RowInfo(row3)
],
rendRows: this.rendRowsTemp
};
this.rendRowsTemp = []; //push to build a new array into here?
}
And then use the new array instead like so:
render() {
return (
<>
{this.state.newRows.map(({ id, Comp }) => (
<tr
key={id}
onClick={this.handleClick(id)}
onContextMenu={this.handleClick(id)}
>
<Comp />
</tr>
))}
</>
);
}
Expected Result
I need a method to build the new array based on the original state array and the amount of times <RowList /> is called.
I think the problem is that in the filter's condition (this.state.rows.filter((_, i) => i !== a)) you're using the RowInfo's index in the array not it's ID, try: this.state.rows.filter(({ id }) => id !== a)