React / NextJS: limit the rendering of a component and update props instead - javascript

I have a NextJS application that displays a list of audio tracks through a component called <AudioTrackEntry>. This component receives props such as the Track Title and a Link to the audio file through a mapping from an external data source.
Nested within the <AudioTrackEntry> component is the <AudioPlayer> that is toggled through a play button.
This currently results in multiple (overlapping) audio players being rendered, when play buttons on multiple audio track entries are being clicked. Clicking 3 Play buttons on 3 tracks result in 3 audio tracks simultaneously playing.
The behaviour I had intended was to just update the respective props of trackTitle and trackLink.
I assume that the reason why the player is being re-rendered is that I'm toggling the visibility state of the AudioPlayer component, and when the state changes, the component gets re-rendered.
How can I limit the button toggle to only update the props of trackTitle and trackLink, not render another player here?
import React, { useEffect, useState } from 'react';
import AudioPlayer from './AudioPlayer';
interface AudioTrackEntryProps {
trackLink: any;
trackClaimStatus: any;
trackTitle: any;
trackDuration: any;
trackBPM: any;
trackGenre: any;
trackClaimAmount: any;
}
const AudioTrackEntry: React.FC<AudioTrackEntryProps> = (props) => {
const [activeTrack, setActiveTrack] = useState('');
const toggleAudioPlayer = (trackTitle: string) => {
setActiveTrack(activeTrack === '' ? trackTitle : '');
};
return (
<div>
<div className="grid grid-cols-12 gap-4 border-b border-cllinegray p-2">
<div className="col-span-1 flex items-center">
<button
onClick={() => {
toggleAudioPlayer(props.trackTitle);
}}
className="border-transparent bg-transparent hover:border-transparent"
>
Play </button>
</div>
<div className="col-span-3 flex items-center">
<h4 className="m-0 font-bold uppercase">{props.trackTitle}</h4>
</div>
<div className="col-span-1 m-auto text-neutral-300">
<p>{props.trackDuration}</p>
</div>
<div className="col-span-1 m-auto text-neutral-300">
<p>{props.trackBPM}</p>
</div>
<div className="col-span-1 m-auto text-neutral-300">
<p>{props.trackGenre}</p>
</div>
<div className="col-span-1 m-auto text-xs text-neutral-300">
<InstrumentalButton />
</div>
<div className="col-span-1 m-auto text-xs text-neutral-300">
<RemixButton />
</div>
{activeTrack === props.trackTitle && (
<AudioPlayer
trackLink={props.trackLink}
trackTitle={activeTrack}
trackDuration={props.trackDuration}
/>
)}
</div>
);
};
export default AudioTrackEntry;
Here is the code of the <AudioPlayer> component:
import {
MediaControlBar,
MediaController,
MediaMuteButton,
MediaPlayButton,
MediaTimeDisplay,
MediaTimeRange,
MediaVolumeRange,
} from 'media-chrome/dist/react';
import Image from 'next/image';
import React from 'react';
import TrackIcon from '#/public/assets/images/icons/track_icon.svg';
function AudioPlayer(props: {
trackLink: any;
trackTitle: string;
trackDuration: string | number;
}) {
return (
<div className="fixed inset-x-0 bottom-0 z-30 rounded-md bg-neutral-900 p-6">
<div className="flex">
<div className="w-1/12">
<Image src={TrackIcon} alt="Full Circus" />
</div>
<div className="flex w-3/12 items-center">
<h4 className="font-bold uppercase">{props.trackTitle} </h4>
</div>
<div className="mx-auto flex w-8/12 items-center">
<MediaController audio className="w-full bg-neutral-900">
<audio src={props.trackLink} autoPlay slot="media"></audio>
<MediaControlBar className="bg-neutral-900">
<MediaPlayButton className="bg-transparent"></MediaPlayButton>
<MediaTimeRange className="w-80 bg-transparent"></MediaTimeRange>
<MediaTimeDisplay
className="w-32 bg-transparent font-trade"
showDuration
></MediaTimeDisplay>
<div className="mx-24 flex">
<MediaMuteButton className="bg-transparent"></MediaMuteButton>
<MediaVolumeRange className="w-80 bg-transparent"></MediaVolumeRange>
</div>
</MediaControlBar>
</MediaController>
</div>
</div>
</div>
);
}
export default AudioPlayer;

You'll want to move the state that controls the "active track" to a component higher up the component tree than all your AudioPlayer instances, and pass the appropriate state-setter down. For instance,
interface AudioTrackDetails {
trackLink: any;
trackClaimStatus: any;
... etc
}
const AudioTrackList: FC<{ availableTracks: AudioTrackDetails[] }> =
({ availableTracks }) =>
{
const [activeTrackTitle, setActiveTrackTitle] = useState<string | null>(null);
return <>{ availableTracks.map(entry =>
<AudioTrackEntry {...entry} key={activeTrack.trackTitle}
activeTrackTitle={activeTrackTitle}
setActiveTrack={setActiveTrackTitle}
/>
}</>
}
interface AudioTrackEntryProps extends AudioTrackDetails {
activeTrackTitle: string | null;
setActiveTrackTitle: (newTrack: (oldTrack: string | null) => string | null) => void;
}
const AudioTrackEntry: React.FC<AudioTrackEntryProps> = (props) =>
{
const toggleAudioPlayer = (trackTitle: string) => {
setActiveTrackTitle(
oldTitle => oldTitle !== trackTitle ? trackTitle : ""
);
};
...
}
This essentially gives you a mutex controlled by this parent AudioTrackList which ensures that only one (or zero) of the AudioTrackEntry instances can exist at a time. If the user toggles an inactive track then it hides the currently-active track; if the user toggles the active track then it stops playing altogether.

Related

Why is this Redux slice returning an initialization error?

Hey SO community I'm developing a dApp on Solana and I'm using Redux for state management. I'm creating a Redux slice that holds the state of the current "tip receiver" wallet address. I'm using Redux toolkit's createSlice but I keep getting this initialization error in my console:
redux.js:473 Uncaught Error: The slice reducer for key "receiver" returned undefined during initialization.
If the state passed to the reducer is undefined, you must explicitly return the initial state.
The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.
at redux.js:473:1
at Array.forEach (<anonymous>)
at assertReducerShape (redux.js:466:1)
at combineReducers (redux.js:531:1)
at configureStore (configureStore.ts:147:1)
at ./src/app/store.js (store.js:7:1)
at options.factory (react refresh:6:1)
at __webpack_require__ (bootstrap:24:1)
at fn (hot module replacement:62:1)
at ./src/index.js (counterSlice.js:73:1)
This is my slice at receiverAddressSlice.js:
import { createSlice } from '#reduxjs/toolkit';
const initalState = {
value: '',
}
const receiverAddressSlice = createSlice({
name: 'receiver',
initalState,
reducers: {
selectReceiver(state, action) {
const address = action.payload;
state.value = address;
}
}
});
export const { selectReceiver } = receiverAddressSlice.actions;
export const selectWalletAddress = (state) => state.receiver.value;
export default receiverAddressSlice.reducer;
I've seen lots of answers here that say you need to add a default case but I'm not using the traditional switch statement. How can I explicitly return the initial state when using createSlice? I haven't been able to find anything about this in the official Redux documentation.
For reference
store.js:
import { configureStore } from '#reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import postingReducer from './Slices/postingSlice';
import tippingReducer from './Slices/tippingSlice';
import receiverReducer from './Slices/receiverAddressSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
posting: postingReducer,
tipping: tippingReducer,
receiver: receiverReducer
},
});
The part of content.js where I attempt to dispatch the action in the slice:
{posts.filter(post => post.account.title != "BAYC #197" && post.account.cta != "https://twitter.com/dxlantxch").map((post, index) => (
<div className=" w-1/2 p-1 shadow-xl bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500 rounded-3xl m-6">
<a className="block p-6 bg-white sm:p-8 rounded-3xl text-black flex" href="">
<div className="sm:pr-1">
<h1 className="text-2xl mb-3 font-bold">{post.account.title}</h1>
{/* <h1 className="text-9xl">🖼️</h1> */}
<a href={post.account.artwork} target="_blank" rel='noreferrer'>
<img className="shadow-2xl rounded-sm h-auto max-w-full " src={post.account.artwork}>
</img>
</a>
</div>
<div className="text-center m-2">
<h3 className="font-bold">Created by {shortenAddress(post.account.creator.toString())}</h3>
<p className='my-2'>{post.account.description}</p>
<button className="w-1/3 border-black border-2 bg-black text-white rounded-lg p-2 my-4"
onClick={(event) => {
event.preventDefault();
dispatch(toggle());
console.log(tipping);
//attempting to dispatch
dispatch(selectReceiver(post.account.creator.toString()));
}}
>
TIP
</button>
<div className="w-full flex justify-evenly pt-3 pl-2 my-4">
<div className="flex">
<div>
<BiUpvote className="text-xl"/>
<BiDownvote className="text-xl"/>
</div>
<p className="text-gray-600 pt-1 pl-1">{post.account.points.toString()}</p>
</div>
<div className="flex">
<FiStar className='text-4xl'/>
<p className="text-gray-600 pt-1 pl-1">{post.account.stars.toString()}</p>
</div>
{post.account.cta ?
<button className='border-black border-2 rounded-lg bg-black text-white w-1/4 p-1 hover:shadow-2xl hover:bg-gray-700 hover:border-gray-700'>
<a href={post.account.cta} target='_blank' rel="noreferrer">
<span className="underline underline-offset-2">More?</span>
</a>
</button> :
<>
</>
}
</div>
</div>
</a>
</div>
))}
Correct the spelling from initalState to initialState, that could be the problem. Just had the same problem and had to scrambled around a little bit before I realized it.

How to properly conditionally render child component in react

I have the following components : (AddBookPanel contains the AddBookForm)
I need to have the ability to display the form on click of the 'AddBookButton', and also
have the ability to hide the form while clicking the 'x' button (img in AddBookForm component), however the component doesn't seem to load properly, what could be wrong here? What do you think is the best way to organize this?
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
}) => {
const [isFormActive, setIsFormActive] = useState(initialFormActive);
return (
<>
{isFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => setIsFormActive(!isFormActive)}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
You're always rendering the AddBookForm. That means in the initial render the useState is called with false. Since you never call setIsFormActive it is now stuck as "do not render".
Just don't use the useState and use the property initialFormActive directly. And maybe rename it to isFormActive. Let the parent handle the state change.
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} onClose={() => setAddBookPressed(false)} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
onColse: () => void;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
onClose,
}) => {
// const [isFormActive, setIsFormActive] = useState(initialFormActive); don't use this frozen state, allow the parent to control visibility
return (
<>
{initialFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => onClose()}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
Problem with your code is in how you tied initialFormActive to inner state of AddBookForm component. When looking at useState inside AddBookForm it is clear that prop initialFormActive only affects your initial state, and when looking at your parent component I can clearly see that you passed false as value for initialFormActive prop, thus initializing state inside AddBookForm with false value(form will stay hidden). Then you expect when you click on button, and when prop value changes(becomes true), that your <AddBookForm /> will become visible, but it will not because you just used initial value(false) from prop and after that completely decoupled from prop value change.
Since you want to control visibility outside of component, then you should attach two props to AddBookForm, isVisible and toggleVisibiliy. And do something like this:
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
const toggleAddBookPressed = () => setAddBookPressed(p => !p);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm isVisible={addBookPressed} toggleVisibility={toggleAddBookPressed } />
</div>
);
};
export const AddBookForm: React.FC<AddBookFormProps> = ({
isVisible, toggleVisibility
}) => {
return (
<>
{isVisible&& (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={toggleVisibility}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};

Issue with Changing state from 1 React Component inside of another React component

I've been trying to change the 'ModalOn' state which is created inside of the medCardSong.js file, and I've been trying to change that state from inside of the 'SongModal.js' file, which is a child component inside of medCardSong.js.
I've inputted the changeState function 'SetModalOn' as a prop to the SongModal.js component, and i've tried changing the state through that.
Here's medCardSong.js:
import React, { useEffect } from 'react';
import { useState} from 'react';
import SongModal from '../modals/SongModal';
function MediumCardSong(props) {
const [ModalOn, SetModalOn] = useState(false);
function killme() {
SetModalOn(false);
console.log(7)
}
console.log(ModalOn)
return (
<div className={" flex flex-col m-2 overflow-hidden duration-300 w-52 " + (ModalOn
? 'transition-none' : 'hover:scale-105 transition-all')} onClick={() =>
{SetModalOn(true);}}>
<img className='object-cover rounded-3xl'
src="https://i0.wp.com/coolhunting.com/wp-content/uploads/2022/07/steve-lacy-
bad-habit.jpg?fit=586%2C586&ssl=1" alt="" />
<div className='pb-2'>
<div className='flex'>
<div className='px-2 pt-2 text-2xl font-bold text-gray-400'>#1</div>
<div className='pl-3 text-xl pt-2 text-white'>Mercury</div>
</div>
<div className='text-left pl-5 text-gray-500'>Steve Lacy</div>
</div>
{ModalOn && <SongModal CloseModal={killme} />}
</div>
);
}
export default MediumCardSong;
and here's SongModal.js:
import React from 'react'
function SongModal(props) {
// setTimeout(() => {props.CloseModal(false)}, 1000)
return (
<div className="bg-neutral-800 bg-opacity-60 fixed inset-0 z-50 backdrop-blur-sm">
<div className="flex h-screen justify-center items-center">
<div className="justify-center bg-neutral-700 py-12 px-24 border-4 border-
pearmint rounded-xl text-xl text-white" >
<button className='text-3xl text-white pr-24 pb-24'
onClick={() => {props.CloseModal()}} >X</button>
sad
</div>
</div>
</div>
)
};
export default SongModal;
The imported CloseModal function works as intended when it's applied outside of the jsx. For example, the "SetTimeOut" piece of code would work properly if it's uncommmented. However this doesn't happen when I call "CloseModal" inside of an Onclick event-handler, such as the call I did for the 'x' Button.
I've also used console.log to see if the button has been registering the clicks I make and it has been. So I'm confused as to why the props.CloseModal function does not work as intended inside the OnClick event listener. Please give advice if you can.
try to call it inside of a hook
function SongModal(props) {
const close = () => {
props.CloseModal()
}
return (
<div>
<button onClick={close}>Close Btn</button>
</div>
)
};
also check the following acritical
link

React - "Functions are not valid as a React child" when I return text from a function

I am learning React.js and I am not sure what is happening here. I want to have a function that returns a string isWaterBoiling() depending on the value of a variable. Then I want to render that string in UI but I am getting this error. I dont understand why this is happening, it doesnt make sense:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
at div
Here is my code:
import { useState } from "react"
const App = () => {
const [celsius, setCelsius] = useState()
const [fahrenheit, setFahrenheit] = useState()
const calculateFahrenheit = newCelsius => {
setCelsius(newCelsius)
setFahrenheit(((newCelsius * 9 / 5) + 32).toFixed(2))
}
const calculateCelsius = newFahrenheit => {
setFahrenheit(newFahrenheit)
setCelsius(((newFahrenheit - 32) * 5 / 9).toFixed(2))
}
const isWaterBoiling = () => {
return (parseFloat(celsius >= 100))
? "boiling"
: "not boiling"
}
return (
<div className="bg-gray-800 h-full text-gray-300 p-4 text-center">
<div className="font-semibold">Temperature Calculator</div>
<div className="mt-4 space-y-4">
<div className="flex gap-3 items-center">
<span className="flex-[5] text-right">Celsius:</span>
<input
value={celsius}
onChange={event => calculateFahrenheit(event.target.value)}
className="flex-[7] bg-gray-700 rounded-sm py-1 px-3"
type="text"
/>
</div>
<div className="flex gap-3 items-center">
<span className="flex-[5] text-right">Fahrenheit:</span>
<input
value={fahrenheit}
onChange={event => calculateCelsius(event.target.value)}
className="flex-[7] bg-gray-700 rounded-sm py-1 px-3"
type="text"
/>
</div>
</div>
<div className="mt-4 italic text-gray-400">
The water is { isWaterBoiling }
</div>
</div>
)
}
export default App
As the error mentions, you are not calling your isWaterBoiling function, but returning it. You need to change that bit of code to The water is { isWaterBoiling() }

Button Function Not calling From Another file in React

I need to call CutomerDashboard.js file's "toggleIsTrucated" function and "isTruncated" to CustomerNotice.js files button onClick and text change places, How can I call that?
(In this customer dashboard file I'm creating a Read function to show some extent of notice text)
import React, {useState,useEffect} from 'react';
import { Input, Row, Col, Button } from 'antd';
import {fetchDashboardMetrics} from "./DashboardApi";
import {items} from "./DashboardItems";
import axios from 'axios';
import CustomerNotice from "./CustomerNotice";
function Read ({children}) {
const text = children;
const [isTruncated, setIsTrucated] = useState(true);
const result = isTruncated ? text.slice(0,90) : text;
function toggleIsTrucated(){
setIsTrucated(!isTruncated);
}
return (
<div>
{result}....
</div>
);
}
const CustomerDashboard = () => {
const [features, setFeatures] = useState(items);
const source = axios.CancelToken.source()
const [notice, setNotice] = useState(<Read>Customer Notice: Optimism Is Invaluable For The Meaningful Life. With A Firm Belief In A Positive Future You Can Throw Yourself Into The Service Of That Which Is Larger Than You Are. -Martin Seligman-</Read>);
const [noticeVisibility, setNoticeVisibility] = useState(true);
useEffect(() => {
fetchDashboardMetrics(features, setFeatures,source.token)
return (() => {
source.cancel();
})
}, []);
return (
<>
<div className='md:pl-8 sm:pl-0'>
<div className='my-5 '>
<p className='mb-8'>My Account - Dashboard Overview</p>
{noticeVisibility && <CustomerNotice notice={notice} setNoticeVisibility={setNoticeVisibility}/>}
</div>
<ul role="list" className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<li key={feature.name} className="col-span-1 bg-white rounded-lg shadow divide-y divide-gray-200 relative">
<div className="w-full flex items-center justify-between p-6 space-x-6">
<div className="flex-1 truncate">
<div className="flex items-center space-x-3 justify-between">
<h3 className="text-gray-900 text-lg truncate">{feature.name}</h3>
{feature.isNew && (
<div className="absolute -top-2 -right-2 p-1 px-4 text-white text-sm bg-red-500">
New
</div>
)}
</div>
</div>
</div>
<div>
<div className={'mx-4 mt-2 mb-3 '}>
{feature.details.map((singleDetail) => {
return (
<div className={'flex justify-between text-base'}>
<span>{singleDetail.name}</span>
<span>{singleDetail.value}</span>
</div>
)
})}
</div>
</div>
</li>
))}
</ul>
</div>
</>
)
}
export default CustomerDashboard;
import React, {useState,useEffect} from 'react';
import {XIcon} from "#heroicons/react/solid";
const CustomerNotice = ({notice, setNoticeVisibility}) => {
return (
<div>
<div className="mt-8 pb-2 sm:pb-5">
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="p-2 rounded-lg bg-orange-600 shadow-lg sm:p-3">
<div className="flex items-center justify-between flex-wrap">
<div className="w-0 flex-1 flex items-center">
<p className="ml-3 font-medium text-white truncate">
<span className="md:inline">{notice}</span>
</p>
</div>
<div className="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<a
href="#"
className="flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-orange-600 bg-white hover:bg-orange-50"
>
<button onClick={toggleIsTrucated}>{isTruncated ? "Read More" : "Read Less"}</button>
</a>
</div>
<div className="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
<button
onClick={() => setNoticeVisibility(false)}
type="button"
className="-mr-1 flex p-2 rounded-md hover:bg-orange-500 focus:outline-none focus:ring-2 focus:ring-white"
>
<span className="sr-only">Dismiss</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true"/>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CustomerNotice;
If this is not possible please suggest me a possible way.
Instead of doing a bunch of hacks, I would recommend simplifying the structure of your components.
import { useState } from 'react'
export default function CustomerDashboard() {
// I am not sure why you want to keep notice in state,
// because in your example you did not call setNotice
const [notice, setNotice] = useState(`
Customer Notice: Optimism Is Invaluable For The Meaningful Life.
With A Firm Belief In A Positive Future You Can Throw Yourself Into The Service
Of That Which Is Larger Than You Are. -Martin Seligman
`)
const [isNoticeVisible, setIsNoticeVisible] = useState(true)
return (
<div>
<h1>My Account - Dashboard Overview</h1>
{isNoticeVisible && (
<CustomerNotice
notice={notice}
setIsNoticeVisible={setIsNoticeVisible}
/>
)}
</div>
)
}
function CustomerNotice(props) {
const { notice, setIsNoticeVisible } = props
const [isTruncated, setIsTruncated] = useState(true)
function toggleIsTruncated() {
setIsTruncated(!isTruncated)
}
return (
<div>
<Read text={notice} isTruncated={isTruncated} />
<button onClick={toggleIsTruncated}>
{isTruncated ? 'Read More' : 'Read Less'}
</button>
<button onClick={() => setIsNoticeVisible(false)}>Dismiss</button>
</div>
)
}
function Read(props) {
const { text, isTruncated } = props
const result = isTruncated ? text.slice(0, 90) : text
return <div>{result}....</div>
}
List of the things that were bad in your code.
Keeping the component instance in the state. It is hard to manage. Even your simple case proves that.
Keeping the toggleIsTruncated function inside the Read component. I think we should keep it outside and pass only 2 props to the Read component. I enable exposed only two things
const { text, isTruncated } = props
As you can see it is easy to maintain and allow us to do whatever we want.
PS. If my review and example were helpful please leave the thumbs up.

Categories

Resources