React Dynamically using Component Names - reactjs

I'm getting the following error:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component />
external components
import Comment from 'material-ui-icons/Comment'
import Attachment from 'material-ui-icons/Attachment'
import History from 'material-ui-icons/History'
import TrendingUp from 'material-ui-icons/TrendingUp'
this.components = {
comment: Comment,
attachment: Attachment,
history: History,
trendingup: TrendingUp
}
render:
{this.menu.map((data, index) => (
<span key={index}>
{dynamicClassName = this.state[data.value.class]}
<span
key={index}
id="historicalTestsColorBandTitle"
className={"mobileBottomBannerOptions "+dynamicClassName}
onClick={this.handleBannerOptionsClick.bind(this, data.value.type)}
>
{DynamicComponentName = this.components[data.value.icon]}
{!dynamicClassName &&
<div>
<table><tbody><tr>
<td>
<DynamicComponentName
className="mobileBottomBannerIcon"
/>
</td>
</tr></tbody></table>
</div>
}
{dynamicClassName &&
<div>
<table><tbody><tr>
<td>
<DynamicComponentName
className="mobileBottomBannerIcon"
/>
</td>
<td className="menuOptionTitle">
{data.value.name}
</td>
</tr></tbody></table>
</div>
}
</span>
</span>
))}
You can see 'DynamicComponentName' is where the component name is created. I need to use a method like this as the order of these components changes depending on the last one clicked.
The components display fine but I get the error message at the top...
Any advice on how to resolve the error message?
thankyou!

You need to move your assignment out of JSX:
render() {
const dynamicClass = this.state[data.value.class];
const DynamicComponent = this.components[data.value.icon];
return // ...
}
If you need to make your assignment inside of map, you can do that too:
this.menu.map((data, index) => {
const dynamicClass = this.state[data.value.class];
const DynamicComponent = this.components[data.value.icon];
return // ...
})

Related

How to fix this error in react `onClick` listener to be a function, instead got a value of `string` type

I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}

What is the best way to filter data in React?

I'm building a catalogue of restaurants in the country. The API returns an array of objects, each one consisting of fields such as restaurantName, restaurantLocation, restaurantPriceRange.
I want to create a filtering component, which will reduce the array of restaurants to a subset containing only those that match the search criteria. I also need to pass the filter values via the URL, for which I believe I should use a router.
I was thinking about storing the initial data set with useState hook and then using useReducer to store active filter selection and a subset of the filtered restaurant collection with only those matching the search criteria, but I feel like this is a clumsy solution, as effectively I would be duplicating a subset of the entire restaurant collection.
However, if I do not store the initial data set in a separate object using useState, all non-matches will be gone forever once the filters have been applied.
What would be the best way to achieve this?
DISCLAIMER: I know how to filter arrays in JavaScript and am fully aware of functions such as filter, map, sort, etc. This question is about the React ecosystem and what would be the cleanest, simplest and best way to structure my code in a React application. I know that I will still have to use Array.prototype.filter in my reducer when I write the code for it.
You are asking for opinions so here is mine. React state (whether it’s useState or useReducer) should only be used for stateful values — the values which change and cause your rendered data to change. The filtered list of restaurants is not a stateful value. The filters are a stateful value, and the filtered list is derived from that state. The filtered list should be a const that you generate on each render. If there are other states or props in your component that change which do not effect the filtered list, you can use the useMemo hook to memoize the filtered list to prevent unnecessary recalculations.
Pseudo-code
import React, {useMemo} from “react”;
import {useLocation} from “react-router-dom”;
export const MyComponent = ({restaurants}) => {
const location = useLocation();
const filteredRestaurants = useMemo( () => {
return restaurants.filter( /* some code */ );
}, [location, restaurants] ); //dependency on location and restaurants
return (
<Wrapper>
<EditFiltersComponent />
<List>
{filteredRestaurants.map( restaurant =>
<Restaurant {...restaurant} />
)}
</List>
</Wrapper>
)
}
Generally we get data from props
const OurComponent = ({data, filter}) => { . . .
or
const { filter } = useParams(); // via query string
Also you may have filter input fields in this component then store their values ​​in a local state for a rerender.
For a complex filter is good idea to have filter function:
const filterFunc = ({address}) => {
address.toLowerCase().includes(filter.address.toLowerCase());
}
and render will be like
return (
<ul>
{
data.filter(filterFunc).map({id, name, address} => (
<li key={id}>{`${name} - ${address}`}</li>
))
}
</ul>
)
I used this method
import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import swal from 'sweetalert';
import axios from 'axios';
const Users = () => {
const navigate = useNavigate();
const [users, setUsers] = useState([]);
const [mainUsers, setMainUsers] = useState([]);
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(res => {
setUsers(res.data);
setMainUsers(res.data);
}).catch(err => {
console.log(err);
})
}, []);
const handleSearch = (e) => {
setUsers(mainUsers.filter(u => u.name.toLowerCase()
.includes(e.target.value.toLowerCase())
));
console.log(e.target.value);
}
return (
<div className={`mt-5 p-4 container-fluid`}>
<div className='row my-2 mb-4 justify-content-between w-100 mx-0'>
<div className='form-group col-10 col-md-6 col-lg-4'>
<input type="text" className='form-control shadow'
placeholder='Search' onChange={handleSearch} />
</div>
</div>
{users.length ? (
<table className='table'>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">User Name</th>
<th scope="col">Email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{users.map(u =>
<tr key={u.id}>
<th scope="row">{u.id}</th>
<td>{u.name}</td>
<td>{u.username}</td>
<td>{u.email}</td>
<td>
<i className='fa fa-edit text-warning'></i>
<i className='fa fa-edit text-warning pointer'></i>
</td>
</tr>
)}
</tbody>
</table>
) : (
<h6 className='text-center text-info'>wating ...</h6>
)}
</div>
)
}
export default Users;

React looping over every value that a state variable has had before rendering on the final value

I have a class that listens for clicks on a map, and when a click has been detected, it is added to an array routeWayPoints:
import React, { useState } from "react";
import Tab from "./Tab";
import RouteWayPointsTable from "./RouteWayPointsTable";
import { map, removeFromTheMap, addToTheMap } from "../javascript/mapInterprater";
function RouteAnalysisTab() {
const [routeWayPoints, setRouteWayPoints] = useState([]);
function beginPlottingRoute() {
setRouteWayPoints([]);
}
map.on("click", onMapClick);
function onMapClick(event) {
if (isRouteActive) {
setRouteWayPoints(routeWayPoints.concat(event.latlng));
}
}
return (
<Tab id="analyse-route" title="Analyse route" className="data-display__tab-content">
<h3>Analyse route</h3>
{!isRouteActive && routeWayPoints.length > 0 && (
<button className="button" id="new-route-button" type="button" onClick={() => beginPlottingRoute()}>
Plot new route
</button>
)}
<div className="data-display__tab-content--route-analysis">
{!isRouteActive && routeWayPoints.length === 0 && (
<button className="data-display__button--home" id="plot-route-button" type="button" onClick={() => beginPlottingRoute()}>
Plot a route
</button>
)}
<RouteWayPointsTable routeWayPoints={routeWayPoints} />
</div>
</Tab>
);
}
export default RouteAnalysisTab;
I pass the array to the RouteWayPointTable component and attempt to loop over each item and create a row in the table using the RouteWayPointRow component.
import React from "react";
import RouteWayPointRow from "../components/RouteWayPointRow";
function RouteWayPointsTable(props) {
return (
<div id="route-analysis">
<table className="data-display__waypoint-table" id="analyse-route-waypoints">
<thead>
<tr>
<th className="data-display__waypoint-table--way-point-col">Way point</th>
<th className="data-display__waypoint-table--lat-col">Latitude</th>
<th className="data-display__waypoint-table--lng-col">Longitude</th>
<th className="data-display__waypoint-table--remove-col"></th>
</tr>
</thead>
<tbody>
{props.routeWayPoints.map((wayPoint, index) => {
return <RouteWayPointRow wayPoint={wayPoint} index={index} key={index} />;
})}
</tbody>
</table>
</div>
);
}
export default RouteWayPointsTable;
Now the rows are being displayed, but I'm observing some strange behaviour I didn't expect.
As I add points to the array, React is iterating over every state the array existed in before it renders the latest point, so the amount of time it takes to update the table is getting exponentially longer as it has to perform more loops over the array every time a new point has been added.
So my issue was that I had declared the map listener inside the component, and so a new listener was being created each time I clicked on the map which cause React to re-render the DOM
function RouteAnalysisTab() {
const [routeWayPoints, setRouteWayPoints] = useState([]);
function beginPlottingRoute() {
setRouteWayPoints([]);
}
map.on("click", onMapClick);
Probably not the most elegant solution, but removing the listener before creating it did the trick:
map.off("click");
map.on("click", onMapClick);

Callback or setState when path is loaded

im trying to find an event or a function, that run everytime i loaded a component, i didn't want componentDidMount because it only get triggered once at the initial render, what i want is an event that run everytime the component gets rendered.i want to do this, because, i want to print an updated version of an array, but i need to use setState to trigger the re-rendering, is there any event or function that runs like this ?, i tried combining componentDidUpdate and componentDidMount but it just gave me an infinite loop
this is my home class to print the array :
export class Home extends React.Component{
constructor(){
super();
this.state = ({refresh: false})
this.refresh = this.refresh.bind(this)
}
render() {
return (
<Router>
<Switch>
<Route exact path="/home">
<div className="konten container-sm col-sm-10" >
<body>
<br></br><br></br>
<div className="tabel card rounded">
<div className="card-body">
<p className="head panel-body">User List</p>
<input type="text" className="search form-control col-sm-2" placeholder="Search user here">
</input>
<Link to="/add"><a href="" className="tambah btn" onClick={this.add}>Add User</a></Link>
<br/>
<table className="table">
<thead>
<tr>
<th scope="col">Nama</th>
<th scope="col">Username</th>
<th scope="col">Email</th>
</tr>
</thead>
{user_list.map(user =>
{
return(
<tbody>
<tr>
<td>{user.nama}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td><button className="edit btn">Edit</button></td>
</tr>
</tbody>
)
}
)}
</table>
</div>
</div>
</body>
</div>
</Route>
<Route path="/add">
<Tambah />
</Route>
</Switch>
</Router>
)
}
}
and here is my add class, to input a new data to the array :
save(event){
event.preventDefault()
user_list.push({
nama: this.state.nama ,
username: "John Doe",
email: this.state.company,
admin: this.state.admin,
status: this.state.active})
console.log(user_list)
this.setState({createNew: true})
}
note that this two code that i just posted is inside a different class, and i create a new data inside the array using push method, and when i get back to the home page, the data is not updated until i re-render the state, the question is, is there any function that can re-render every time i entered the home path ?
any help will be appreciated, thanks before
Use useEffect without dependencies it help's you
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
If your function is dependent on the new props you'll be receiving from the parent that will render the component then you can use componentWillRecieveProps lifecycle. But if your re-render component is not dependent on the parent then you can simple add the array in you render and it will change on you re-render which will be base on the state.

Switch to different component after button click with React

I am absolutely new to React and need to create a button to take the user from a current component (MovieView) to the main one (MainView). I managed to create it properly and I used onClick to trigger the display of MainView. I know I cannot call a class from a function (so as console says), so I created a second function to be called and trigger MainView. But as you may wonder, it does not work. This is the code I am using:
import React from 'react';
import { MainView } from '../main-view/main-view';
function NewButton() {
return <MainView />;
}
export class MovieView extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
const { movie } = this.props;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={NewButton}>Back</button>
</div>
</div>
);
}
}
Could you any seeing this to point the path to take or how I can call this MainView class properly. I understand it's a simple task, but I am afraid I still haven't absorbed React principles yet. Thanks in advance to you all.
Without introducing additional dependencies, probably the easiest way for this example is to use state to track if the button has been clicked. If it has been clicked, render MovieView, if not render MainView.
For this you need to following:
Set state variable that tracks that MainView should be rendered in onClick event. (boolean will probably suffice)
in render():
if the state var is false, render the content of MovieView.
if the state var is true, render the MainView component.
Implementation details are left as an exercise :)
If you're using a router already (like react-router) you could probably just update the url to an url that is linked to the MainView component..
I make the Class into a Function Component instead, and implement it like this :-)
import React from "react";
import { MainView } from '../main-view/main-view';
export const MovieView = ({ movie }) => {
const { showMainView, setShowMainView } = React.useState(false);
if (!!movie) {
return null;
}
function toggleMainView() {
setShowMainView(true);
}
return (
<React.Fragment>
{ showMainView ? (
<MainView />
) : (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={() => toggleMainView()}>Back</button>
</div>
</div>
)}
</React.Fragment>
);
};
export default MovieView;
If you need to add or remove components or show a new view based on an user action in react we need to do this via state .
import React from 'react';
import { MainView } from '../main-view/main-view';
export class MovieView extends React.Component {
constructor() {
super();
this.state = {
showMainView: false;
};
}
toggleMainView = () => this.setState(prevState => ({ showMainView:
!prevState.showMainView}))
render() {
const { movie } = this.props;
const { showMainView } = this.state;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={this.toggleMainView}>Back</button>
</div>
// render the mainView based on the state
{ showMainView && <MainView />}
</div>
);
}
}
I reached a solution that I'd not do with the help I had here. I have first pasted here some pieces of code but, I believe it can be helpful to give the whole answer.
So, the question I placed here was regarding a component called <MovieView, that was a part of an app that interacted with another component (in another file) called MainView.
The solution was the insertion of the method onClick on the live 14 of <MovieView> and the button on line 39 that you can see at this file on Github. If the file was updated, check the version for November 22nd.
The solution had, however, been found in the main component, <MainView>, adding code on lines 48 and 49. Here is the link to the file: https://github.com/cgobbet/react-client/blob/master/src/components/main-view/main-view.jsx
The repository with the whole app is on this page: https://github.com/cgobbet/react-client.
And sorry for the reviewers (I had accidentally deleted part of the fantastic answer submitted by #nick-miller.

Resources