React eslint error:Component definition is missing display name - reactjs

Using Reac.memo to wrap my functional component, and it can run smoothly, but the eslint always reminded me two errors:
error Component definition is missing display name react/display-name
error 'time' is missing in props validation react/prop-types
Here is my code:
type Data = {
time: number;
};
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
My whole code:
import React, { useState, useMemo } from 'react';
const Father = () => {
const [time, setTime] = useState(0);
const [random, setRandom] = useState(0);
return (
<>
<button type="button" onClick={() => setTime(new Date().getTime())}>
getCurrTime
</button>
<button type="button" onClick={() => setRandom(Math.random())}>
getCurrRandom
</button>
<Child time={time} />
</>
);
};
function changeTime(time: number): string {
console.log('changeTime excuted...');
return new Date(time).toISOString();
}
type Data = {
time: number;
};
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
export default Father;

It's because you have eslint config which requries you to add displayName and propTypes
Do something like
const Child: React.FC<Data> = React.memo(({ time }) => {
console.log('child render...');
const newTime: string = useMemo(() => {
return changeTime(time);
}, [time]);
return (
<>
<p>Time is {newTime}</p>
{/* <p>Random is: {children}</p> */}
</>
);
});
Child.propTypes = {
time: PropTypes.isRequired
}
Child.displayName = 'Child';

If you are working with React and TypeScript, you can turn off the react/prop-types rule.
This is because TypeScript interfaces/props are good enough to replace React's prop types.

Related

how to handle onClick event with multiple component with the same class name in React?

I'm new to react.js and I want to apply the toggle feature at 'place-box' by using 'isOpen' state and my intention is it only works when I click single place-box div so I added onClick event at 'place-box' div. but all of the elements are toggled at the same time.
I guess it's because they all have the same class name.
how can I fix this?
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [isOpen, setIsOpen] = useState(false);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setIsOpen((isOpen) => !isOpen);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{isOpen && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!isOpen && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
Your variable isOpen is used for all cities. If you change isOpen to true all place-boxes are opened. You should store the id of the currently opened city inside a variable and compare against it to check if the current city in the for loop should be opened.
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [openedPlaceId, setOpenedPlaceId] = useState(undefined);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setOpenedPlaceId(placeId);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{openedPlaceId === place.id && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!(openedPlaceId === place.id) && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
This way only the clicked place will be opened.

Deconstructing state in useState in react and typescript

Is there a way to destructure a current state that has a specified shape? I have a current state in personJob but I want to able to specify which object to look at (when I click on a button that will slice that certain object and render only that data).
I get an error in TypeScript const {company, dates, duties, title} = personJob[value]; when I try to slice by that index
The error is:
Cannot destructure property 'company' of 'personJob[value]' as it is undefined.
Component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const url = 'https://course-api.com/react-tabs-project';
interface IPerson {
id: string;
company: string;
dates: string;
duties: string[];
title: string;
}
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value];
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
<section>
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>
</main>
);
}
export default App;
Issue
On the initial render personJob is still an empty array and personJob[0] is undefined, so values can't be destructured from it.
Solution
Provide a fallback object to destructure from, personJob[value] || {}.
Conditionally render the section if personJob[value] is truthy and exists.
Code:
function App() {
const [personJob, setPersonJob] = useState<IPerson[]>([]);
const [value, setValue] = useState<number>(0);
const fetchData = async () => {
const response = await axios(url);
setPersonJob(response.data);
};
useEffect(() => {
fetchData();
}, []);
const { company, dates, duties, title, id } = personJob[value] || {}; // <-- fallback for destructuring
return (
<main>
<h1>Jobs</h1>
{personJob.map((job, index) => {
return (
<button key={job.id} onClick={() => setValue(index)}>
{job.company}
</button>
);
})}
{personJob[value] && <section> // <-- conditionally render section if data available
<article className="border-2 px-5 py-5">
<div key={id}>
<h2>{title}</h2>
<h3>{company}</h3>
<p>{dates}</p>
{duties.map((duty) => {
return <div>*** {duty}</div>;
})}
<button type="button">More Info</button>
</div>
</article>
</section>}
</main>
);
}
Demo

How can I edit a todo in my react app using hooks?

I'm trying to figure out how to edit a todo item in my react app using hooks, but I can't seem to figure out how to write the code.
Most of the solutions I've seen online are using class components and it's not written with the same logic as my app.
Here is my current code
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = id => {
const removedArr = [...todos].filter(todoId => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = e => {
setTodos(e.target.value);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{todos.map(todo => (
<div>
<div
key={todo.id}
className={todo.isComplete ? 'complete' : ''}
key={todo.id}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</div>
))}
</>
);
}
Here is the code from the other component
function TodoForm(props) {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
complete: false
});
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='todo...'
value={input}
onChange={handleChange}
name='text'
/>
<button onClick={handleSubmit}>add todo</button>
</form>
);
}
So right now everything works where I can add todos and delete todos + cross out todos. Only thing missing is being able to edit them.
I saw some suggestions about updating the text value with an input form, but I'm not too sure how I'd implement that in my editTodo function.
Similar to your removeTodo handler, you want to pass the todo.id to completeTodo.
<div className={todo.isComplete ? "complete" : ""} key={todo.id} onClick={() => completeTodo(todo.id)}>
Then you would update a bool value in the todo object.
const completeTodo = (id) => {
let updatedTodos = todos.map(todo => {
if(todo.id === id){
todo.isComplete = true
}
return todo
})
setTodos(updatedTodos)
};
Edit: add styling strikethrough
You'll then conditionally add a css style based on isComplete boolean
CSS
.complete {
text-decoration: line-through;
}
To be able to click on the Remove button, place it outside the todo div in your map function.
{todos.map((todo, isComplete) => (
<>
<div
key={todo.id}
onClick={completeTodo}
className={isComplete ? 'complete' : ''}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</>
))}
As discussion with you in another question here it is:
TodoList.js
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import Todo from "./Todo";
function TodoList({ onClick }) {
const [todos, setTodos] = useState([]);
//Track is edit clicked or not
const [editId, setEdit] = useState(false);
//Save input value in input box
const [inputValue, setInputValue] = useState("");
const handleEditChange = (id, text) => {
setEdit(id);
setInputValue(text);
};
const addTodo = (todo) => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = (id) => {
const removedArr = [...todos].filter((todoId) => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = (id) => {
let updatedTodos = todos.map((todo) => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = (id, text) => {
let editTodos = todos.map((todo) => {
if (todo.id === id) {
todo.text = text;
}
return todo;
});
setTodos(editTodos);
setEdit(false);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{/* I want to move this code below into a new component called Todo.js */}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
editTodo={editTodo}
handleEditChange={handleEditChange}
editId={editId}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</>
);
}
export default TodoList;
Todo.js
// I want to move this code into this component
import React, { useState } from "react";
import { FaWindowClose, FaRegEdit } from "react-icons/fa";
const Todo = ({
todos,
completeTodo,
removeTodo,
editTodo,
editId,
handleEditChange,
inputValue,
setInputValue
}) => {
return todos.map((todo) => (
<div className="todo-row">
{editId === todo.id ? (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
) : (
<div
key={todo.id}
className={todo.isComplete ? "complete" : ""}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
)}
{editId === todo.id ? (
<button onClick={() => editTodo(todo.id, inputValue)}>Edit todo</button>
) : (
<>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
<FaRegEdit onClick={() => handleEditChange(todo.id, todo.text)} />
</>
)}
</div>
));
};
export default Todo;
Make sure you read and understand code first. Logic is pretty simple what you do in completeTodo. You just need to update text part. Tricky part is to open in input. So logic is like track if user click on id set that id. And check if id is there open input with that id value other wise normal one.
Here is demo of this POC: https://codesandbox.io/s/nostalgic-silence-idm21?file=/src/Todo.js:0-1059

How to pass function as props from functional parent component to child

Parent Component:
const initialValue_modalProps = [
{ show: false, response: "" }
];
const [modalProps, setModalProps] = useState(initialValue_modalProps)
const passedFunction = () => {
setModalProps(modalProps => initialValue_modalProps);
}
..
..
<div>
<Modal show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction}></Modal>
</div>
Child Component:
export default function ModalComp(props) {
const [modalOpen, setmodalOpen] = useState(true);
console.log('modalOpen', modalOpen);
if (props.show === false || modalOpen === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={() => {
setmodalOpen(modalOpen => false);
props.passedFunction();
}}>Close</Button>
</ModalFooter>
</Modal>)
}
Here I want to passedFunction function from Parent to child so that the Child component can execute it to reset the state in parent
You can take this as an reference with live example demo https://codesandbox.io/s/modal-6fvyx
function App() {
const [status, setState] = React.useState(false);
const [text, setText] = React.useState("");
const handleClick = () => {
setState(prevStatus => !prevStatus);
};
const handleChange = e => {
setText(e.target.value);
};
return (
<>
<button onClick={handleClick}>Open photo entry dialog</button>
<ChildComponent
isOpen={status}
text={text}
handleChange={handleChange}
handleClick={handleClick}
/>
</>
);
}
const ChildComponent = ({ isOpen, text, handleChange, handleClick }) => {
return (
<>
{isOpen && (
<Model
status={isOpen}
handleClick={handleClick}
text={text}
handleChange={handleChange}
/>
)}
</>
);
};
You need to remove the parentheses behind passedFunction, because otherwise you are executing the function first and passing the result to the child afterwards. Pass your function as it is via passedFunction={passedFunction}.
const ParentComponent = () => {
const initialModalProps = { ... };
const [modalProps, setModalProps] = useState(initialModalProps);
const passedFunction = () => {
setModalProps(initialModalProps);
}
return (
<div>
<Modal
show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction} />
</div>
);
};
Changed the child component to this. and its working
export default function ModalComp(props) {
//const [modalOpen, setmodalOpen] = useState(true);
if (props.show === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={props.passedFunction}>Close</Button>
</ModalFooter>
</Modal>)

How to derive loading state when using lazy and suspense in React?

I am trying to build a lazy loading tabs component that fetches the code/bundle for the tab only when it is clicked. I am trying to use lazy+suspense for it. I would like to animate/color the tab the user clicked on when it is loading to indicate that a tab is being loaded. How can I do that best?
Here is some example code I have whipped up. The bug here is that the tab-header can sometimes get rendered twice when the code is being loaded. How can I avoid the issue and display a loading state on the new tab.
import React, {lazy, Suspense, useState, useReducer} from "react";
import "./App.css";
import classNames from "classnames";
import Spinner from "./Spinner";
const Tokyo = lazy(() => {
return import("./Tokyo");
});
const Mexico = lazy(() => {
return import("./Mexico");
});
const London = lazy(() => {
return import("./London");
});
const App = () => {
const [_, setTab] = useState("Tokyo");
return (
<div className="App">
<header className="App-header">
<Tabs initialTab="Tokyo" onTabChange={setTab}>
<Tab id="Tokyo" name="Tokyo">
<Tokyo />
</Tab>
<Tab id="Mexico" name="Mexico">
<Mexico />
</Tab>
<Tab id="London" name="London">
<London />
</Tab>
</Tabs>
</header>
</div>
);
};
const Tab = () => {
return null;
};
function genClickLog(log, current) {
const set = new Set([current]);
const newLog = [current];
log.forEach(l => {
if (!set.has(l)) {
log.push(l);
newLog.push(l);
}
});
return newLog;
}
function createSuspenseTree(targetTab, log, child, tabs, handleTabChange) {
const head = log.shift();
if (head !== targetTab) {
console.warn(`expect ${head} to be ${targetTab}`);
}
let current = child;
log.forEach(l => {
current = (
<Suspense
fallback={
<Fallback
tabs={tabs}
prevTab={l}
activeTab={targetTab}
onTabChange={handleTabChange}
/>
}
>
{current}
</Suspense>
);
});
return <Suspense fallback={<Spinner />}>{current}</Suspense>;
}
function reducer(state, action) {
switch (action.type) {
case "change":
if (state.current === action.id) {
return state;
}
return {
current: action.id,
prev: state.current,
clickLog: genClickLog(state.clickLog, action.id),
};
case "initial":
return {
current: action.id,
prev: null,
clickLog: [action.id],
};
default:
throw new Error("bad reducer action");
}
}
const Tabs = props => {
const {children, onTabChange, initialTab} = props;
const [state, dispatch] = useReducer(
reducer,
{
clickLog: [],
prev: null,
current: null,
},
{type: "initial", id: initialTab}
);
const handleTabChange = tab => {
dispatch({type: "change", id: tab});
onTabChange(tab);
};
const tabs = React.Children.map(children, x => ({
id: x.props.id,
name: x.props.name,
render: x.props.children,
}));
const child = (
<>
<TabHeader
tabs={tabs}
activeTab={state.current}
onTabChange={handleTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={state.current}
render={x.render}
/>
</div>
))}
</>
);
return (
<div className="TabContainer">
{createSuspenseTree(
state.current,
[...state.clickLog],
child,
tabs,
handleTabChange
)}
</div>
);
};
const Fallback = props => {
const {prevTab, activeTab, onTabChange, tabs} = props;
if (prevTab && prevTab !== activeTab) {
return (
<>
<TabHeader
tabs={tabs}
activeTab={prevTab}
loadingTab={activeTab}
onTabChange={onTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={prevTab}
render={x.render}
/>
</div>
))}
</>
);
}
return <Spinner />;
};
const TabFrag = props => {
if (props.id === props.activeTab) {
return props.render;
}
return null;
};
const TabHeader = props => {
const {tabs, activeTab, loadingTab, onTabChange} = props;
return (
<div className="TabHeader">
{tabs.map(x => (
<TabItem
id={x.id}
key={x.id}
name={x.name}
active={x.id === activeTab}
loading={x.id === loadingTab}
onTabChange={onTabChange}
/>
))}
</div>
);
};
const TabItem = props => {
const {id, name, loading, active, onTabChange} = props;
const handleTabChange = () => {
onTabChange(id);
};
return (
<div
className={classNames("TabItem", {
ActiveTab: active,
LoadingTab: loading,
})}
onClick={handleTabChange}
>
{name}
</div>
);
};

Resources