I want to update icon of component from another component. whenever I clicked on a playlist music start playing and icon should be change to pause instead of play but I don't know how I can update the state of a component from another component.
PlayList Component -
playlist and musics are in this component
class PlayList extends React.Component {
render() {
const playMusic = () => {
musics.forEach(e => e.pause());
musics[this.props.arr].play();
musics[this.props.arr].currentTime = 0;
nowPlaying = this.props.arr;
clickedOnMusic = 'clicked';
};
return (
<div>
<Card>
<CardActionArea onClick={playMusic} />
</Card>
</div>
)
}
BottomAppBar Component -
icons and some function to playing music are here
class BottomAppBar extends React.Component {
state = {
displayPlay: 'none',
displayPause: '',
displayVolume: '',
displayMute: 'none'
};
render(){
return(
<IconButton onClick={handleChangePlay} style={{ color: 'white' }}>
<PauseCircleOutlineRoundedIcon
style={{ fontSize: 46, display: this.state.displayPlay }}
/>
<PlayCircleOutlineIcon
style={{ fontSize: 46, display: this.state.displayPause }}
/>
)
}
thank you very much for reading !
Wrap them in a container and maintain their states over there.
Ex:
<Parent>
<PlayList/>
<BottomAppBar />
</Parent>
You can use the context api, any ascendent of PlayerLogic can access whatever you put in the context with React.useContext and will be re rendered when values in the Context change.
const PlayerContext = React.createContext();
const PlayerLogic = ({ children }) => {
const [state, setState] = React.useState({
playing: false,
});
const setPlaying = React.useCallback(
val =>
setState(current => ({ ...current, playing: val })),
[]
);
const pause = React.useCallback(() => setPlaying(false), [
setPlaying,
]);
const play = React.useCallback(() => setPlaying(true), [
setPlaying,
]);
return (
<PlayerContext.Provider
value={{
state,
pause,
play,
}}
>
{children}
</PlayerContext.Provider>
);
};
const ComponentOne = () => {
const {
pause,
play,
state: { playing },
} = React.useContext(PlayerContext);
return (
<div>
{playing ? (
<button onClick={pause}>pause</button>
) : (
<button onClick={play}>play</button>
)}
</div>
);
};
class ComponentTwo extends React.Component {
render() {
return this.context.state.playing
? 'now playing'
: 'nothig is playing';
}
}
ComponentTwo.contextType = PlayerContext;
const A = () => <B />;
const B = () => <C />;
const C = () => {
const {
state: { playing },
} = React.useContext(PlayerContext);
return `In component C, is playing ${JSON.stringify(
playing
)}`;
};
const App = () => (
<PlayerLogic>
<ComponentOne />
<ComponentTwo />
<div>
<A />
</div>
</PlayerLogic>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
thank you in advance, however, before answering the question, read carefully what I ask for help with all due respect. What i need:
I need that when the delete button is clicked, the component is not only deleted, but also leaves behind another button, by clicking on which, the remote component is rendered back
Functionality that already works: rendering a component on click, as well as deleting by a button
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
interface IParams {
id: number;
}
interface IBlock {
deleteBlock(blockToDelete: number) : void
id: number
}
function App() {
const [newBlock, setNewBlock] = useState([] as IParams[])
const createOnClick = () => {
const newId = {
id: newBlock.length + 1
}
setNewBlock([...newBlock, newId])
}
const deleteBlock = (blockToDelete: number) => {
setNewBlock(
newBlock.filter((x) => {
return x.id !== blockToDelete
})
)
}
const FunctionalBlock: React.FC<IBlock> = ({id, deleteBlock}) => {
return (
<div style={{display: 'flex', maxWidth: '300px', justifyContent: 'space-between'}}>
<div>i was created {id} times</div>
<button onClick={() => {
deleteBlock(id)
}}>now delete me</button>
</div>
)
}
return (
<div className="App">
<button onClick={createOnClick}>
New block
</button>
{
newBlock.map((x) => (
<FunctionalBlock id={x.id} key={x.id} deleteBlock={deleteBlock}/>
))
}
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Why not just set a state on the block?
const {useState} = React
const FunctionalBlock = ({ id, idx, isDeleted, toggleBlockState }) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between"
}}
>
{
!isDeleted
? <React.Fragment>
<div>i was created {idx} times</div>
<button
onClick={toggleBlockState}
>
now delete me
</button>
</React.Fragment>
: <button onClick={toggleBlockState}>REVIVE BLOCK</button>
}
</div>
)
;
};
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
})
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block
return {
...block,
isDeleted: !block.isDeleted
}
}
const App = () => {
const [newBlock, setNewBlock] = useState([])
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1)
setNewBlock([...newBlock, block])
}
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)))
}
return (
<div>
<div>NEW BLOCK</div>
<div><button onClick={createOnClick}>ADD NEW BLOCK +</button></div>
<div>
{
newBlock.map(block => <FunctionalBlock {...block} toggleBlockState={() => toggleBlockStateById(block.id)}/>)
}
</div>
</div>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am new on React.js and I don't understand,
how I should pass the score children to app parent,
and reset all components score via "Reset Components Score" on the app parent button,
I try everything noting work,
thanks for the help!
Player children components:
import React, { useState } from 'react';
function Player(props) {
const [score, setScore] = useState(0);
function scoreUp(params) {
setScore(score + 1);
}
function scoreDown(params) {
score <= 0 ? setScore(0) : setScore(score - 1);
}
const scoreReset = () => {
setScore(0);
};
return (
<div>
<center>
<h1>{props.name}</h1>
<h2>{score}</h2>
<button style={{ backgroundColor: 'green' }} onClick={scoreUp}>
+
</button>
<button style={{ backgroundColor: 'red' }} onClick={scoreDown}>
-
</button>
<button style={{ backgroundColor: 'purple' }} onClick={scoreReset}>
Reset {props.name} Score
</button>
</center>
</div>
);
}
export default Player;
App Parent:
import React from 'react';
import Player from './Player';
function App() {
return (
<div>
<Player name='Benjamin' />
<Player name='Alex' />
<button style={{ backgroundColor: 'black' }}>
Reset Components Score
</button>
</div>
);
}
export default App;
the App img
Benja. In react, data flows only in one direction (from parent to child). In this case, we want to lift the state one level up ( the parent component: App) so that both the parents and children have access to the state. so we will take the score state from the player component to the app component. then we will pass the score and a handler function as well as props to the player component.
with that our code will look like this =>
the App component:
import React, { useState } from "react";
import Player from "./components/Player";
interface PlayerProps {
[key: string]: number;
}
function App() {
const [playerScore, resetPlayerScore] = useState<PlayerProps>({
Benjamin: 0,
Alex: 0,
});
const handle_playerScore = (player: string) => {
const scoreUp = () => {
resetPlayerScore({ ...playerScore, [player]: playerScore[player] + 1 });
};
const scoreDown = () => {
resetPlayerScore({ ...playerScore, [player]: playerScore[player] - 1 });
};
const scoreReset = () => {
resetPlayerScore({ ...playerScore, [player]: 0 });
};
return { scoreUp, scoreDown, scoreReset };
};
const resetAll = () => {
resetPlayerScore({ Benjamin: 0, Alex: 0 });
};
return (
<div>
<Player
score={playerScore.Benjamin}
name="Benjamin"
handleScore={handle_playerScore}
/>
<Player
score={playerScore.Alex}
name="Alex"
handleScore={handle_playerScore}
/>
<button onClick={resetAll}>Reset All</button>
</div>
);
}
export default App;
<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>
and our Player component:
import React from "react";
interface PlayerProps {
name: string;
score: number;
handleScore: (player: string) => {
scoreUp: () => void;
scoreDown: () => void;
scoreReset: () => void;
};
}
const Player = ({ name, score, handleScore }: PlayerProps) => {
const { scoreUp, scoreDown, scoreReset } = handleScore(name);
return (
<div>
<h1>{name}</h1>
<h2>{score}</h2>
<button style={{ backgroundColor: "green" }} onClick={scoreUp}>
+
</button>
<button style={{ backgroundColor: "red" }} onClick={scoreDown}>
-
</button>
<button style={{ backgroundColor: "purple" }} onClick={scoreReset}>
Reset {name} Score
</button>
</div>
);
};
export default Player;
<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>
We can apply other techniques as well to solve this scenario. such as Higher-order components and render props. They also have the same underlying principle.
I'm creating a playlist and I want to show my "favorite tracks" with a different icon, like a heart filled, by default render a bordered heart. Basically the default code is working but when refresh the page, the icon filled is gone and render the default.
And if anyone has a tip, i'm new in react and dont know if I can have many "useStates" like that.
I forgot to mention, the app are using Redux-persist, so the info continues in the store after the page refresh. That is why I want show the icon filled based on info at the store
import React, { useState, useEffect } from "react";
import ReactDOMServer from "react-dom/server";
import axios from "axios";
import { useSelector, useDispatch } from "react-redux";
import { ListContainer, PlayerStyle, InfoBox } from "./style.jsx";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import FavoriteIcon from "#material-ui/icons/Favorite";
import PlayCircleOutlineIcon from "#material-ui/icons/PlayCircleOutline";
import PauseCircleOutlineIcon from "#material-ui/icons/PauseCircleOutline";
import MusicPlayer from "../MusicPlayer/index";
const List = () => {
const [isLoading, setLoading] = useState(true);
const [valueList, setValueList] = useState(20);
const [search, setSearch] = useState("");
const [dataList, setDataList] = useState([]);
const [trackList, setTrackList] = useState([]);
const [bannerAlbum, setBannerAlbum] = useState("");
const [createdBy, setCreatedBy] = useState("");
const [isPlayed, setIsPlayed] = useState(false);
const [musicPlayed, setMusicPlayed] = useState("");
const [showTrackList, setShowTrackList] = useState(true);
const [showFavoriteList, setShowFavoriteList] = useState(false);
const [favoriteData, setFavoriteData] = useState([]);
const favoriteList = useSelector(
(state) => state.FavoriteListReducer.favorites
);
const dispatch = useDispatch();
let chartPlaylist = `https://api.deezer.com/playlist/1111141961?&limit=${valueList}`;
useEffect(() => {
axios.get(chartPlaylist).then((res) => {
// console.log(res.data, "album");
setDataList(res.data);
setTrackList(res.data.tracks.data);
setBannerAlbum(res.data.picture_medium);
setCreatedBy(res.data.creator.name);
// Ao terminar a requisição dos dados, mostra os componentes
setLoading(false);
});
}, [chartPlaylist]);
useEffect(() => {
axios.all(favoriteList.map((l) => axios.get(l))).then(
axios.spread(function (...res) {
// all requests are now complete
setFavoriteData(res);
})
);
if (!showTrackList && favoriteList.length === 0) {
setShowTrackList(true);
setShowFavoriteList(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [favoriteList]);
const handleInput = (e) => {
setSearch(e.target.value);
};
const handlePlayed = () => {
if (!isPlayed) {
setIsPlayed(true);
} else {
setIsPlayed(false);
}
};
const handleIconSwap = (e) => {
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
!isPlayed ? <PlayCircleOutlineIcon /> : <PauseCircleOutlineIcon />
);
};
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteIcon style={{ color: "red" }} />
)
);
};
const Del_fav = (e) => {
dispatch({
type: "DELETE_FAV",
remove: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteBorderIcon fontSize={"inherit"} />
)
);
};
// eslint-disable-next-line no-array-constructor
const handleFav = (e) => {
favoriteList.includes(
`https://api.deezer.com/track/${e.currentTarget.getAttribute("value")}`
)
? Del_fav(e)
: Add_fav(e);
};
const toggleData = () => {
if (showTrackList && favoriteList.length > 0) {
setShowTrackList(false);
setShowFavoriteList(true);
}
if (!showTrackList) {
setShowTrackList(true);
setShowFavoriteList(false);
}
};
if (isLoading) {
return <div>Loading...</div>;
}
return (
<>
<button
onClick={() => {
if (valueList < 100) {
setValueList(valueList + 20);
}
}}
>
adicionar +20 musicas
</button>
<br />
<br />
<br />
<button
onClick={() => {
toggleData();
}}
>
Mostrar Favoritos
</button>
<InfoBox>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<div className="content">
<img src={bannerAlbum} alt="" />
<div className="Info--desc">
<h1>{dataList.title}</h1>
<div>
<span>criado por: </span>
{createdBy}
</div>
</div>
</div>
</InfoBox>
<ListContainer>
<div className="headerList">
<div className="rank">#</div>
<div className="favorite--icon">
{/* <FavoriteBorderIcon fontSize={"inherit"} /> */}
</div>
<div className="title--music">FAIXA</div>
<div className="title--artist">ARTISTA</div>
<div className="title--album">ALBUM</div>
<div className="title--duration">D.</div>
</div>
<div className="bodyList">
{showTrackList &&
trackList.map((item, key) => {
return (
<>
<div
key={key}
onMouseEnter={handleIconSwap}
onMouseLeave={(e) => {
if (!isPlayed) {
e.currentTarget.firstElementChild.innerHTML = key + 1;
} else {
e.currentTarget.firstElementChild.innerHTML =
ReactDOMServer.renderToString(
<PauseCircleOutlineIcon />
);
}
}}
>
<div
className="rank"
onClick={() => setMusicPlayed(trackList[key].preview)}
>
{key + 1}
</div>
<div className="favorite--icon">
// Here that has to dynamically render
<span value={item.id} onClick={(e) => handleFav(e)}>
<Icon favIco={false} />
</span>
</div>
<div className="title--music">
<a target="_blank" rel="noreferrer" href={item.link}>
{item.title}
</a>
</div>
<div className="title--artist">{item.artist.name}</div>
<div className="title--album">{item.album.title}</div>
<div className="title--duration">
{item.duration / 60 < 10
? "0" +
(item.duration / 60)
.toFixed(2)
.toString()
.replace(".", ":")
: (item.duration / 60)
.toFixed(2)
.toString()
.replace(".", ":")}
</div>
</div>
</>
);
})}
</div>
</ListContainer>
</>
);
};
const Icon = (props) => {
switch (props.favIco) {
case true:
return <FavoriteIcon style={{ color: "red" }} />;
case false:
return <FavoriteBorderIcon fontSize={"inherit"} />;
default:
return <FavoriteBorderIcon fontSize={"inherit"} />;
}
};
export default List;
My Reducer store is working fine, just not render the heart filled on load the page, if I click at heart and remove the info from the store and click again the icon change to filled again.
const initialState = {
favorites: [],
};
const List = (state = initialState, action) => {
switch (action.type) {
case "ADD_FAV":
return { ...state, favorites: [...state.favorites, action.newId] };
case "DELETE_FAV":
let index = state.favorites.indexOf(action.remove);
if (index > -1) {
state.favorites.splice(index, 1);
}
return {
...state,
favorites: [...state.favorites],
};
default:
}
return state;
};
export default List;
Thank you all
I've seen in your comment above that you're currently using redux-persist to keep the store between reloads, so having access to the data is not the problem.
Looking your code I think the problem might be with the way you're rendering the icons.
If I understood correctly, that is done by the Add_fav function:
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteIcon style={{ color: "red" }} />
)
);
};
But this function only runs when you add a new favorite - hence the absence of the icons after a reload, because this part of the code would not be ran at all.
My suggestion would be to change the icon rendering logic to the return JSX of the component, instead of inside the event handler function. Something like this:
<span value={item.id} onClick={(e) => handleFav(e)}>
{
favoriteList.includes(item.id)
? <FavoriteIcon style={{ color: "red" }} />
: <FavoriteBorderIcon fontSize={"inherit"} />
}
</span>
So figured out I was send to the store was a string and at the render logic was searching for a int number, then a edited the function add the method parseInt()
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: parseInt(e.currentTarget.getAttribute("value")),
});
};
and now works fine bacause the logic is searching for a Number Int.
<span value={item.id} onClick={(e) => handleFav(e)}>
{
favoriteList.includes(item.id)
? <FavoriteIcon style={{ color: "red" }} />
: <FavoriteBorderIcon fontSize={"inherit"} />
}
</span>
I have handle click and I want to access the className of the div element, how can I do that
this is the element i want to get the div className
<div className="milad">
<IconButton
style={{outline:'none', color: 'white', float:'right', marginRight:'20px', marginTop: "5px"}}
className = "iconButton"
>menu
<MenuIcon/>
</IconButton>
</div>
this is my
checkHandleClick = (event) => {
console.log(event);
}
-----------------------------------------------------------------
<ClickAwayListener onClickAway={((e) => this.checkHandleClick(e))}>
-----some code------
</ClickAwayListener>
I want to access this Console.log
You could do using event.target.className
checkHandleClick = (event) => {
console.log(event.target.className);
}
-----------------------------------------------------------------
<ClickAwayListener onClickAway={((e) => this.checkHandleClick(e))}>
-----some code------
</ClickAwayListener>
You should use ref for it.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Or if you're using hooks
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I'm trying to wrap my head around how one uses redux in the case where a prop that is being passed into a component is supposed to be used to change the state of the application.
I have a working example here.
let Input = ({handleChange}) => (
<input type="text" onChange={handleChange('mySpecialInput')} />
)
let Text = ({message, color}) => (
<span style={{color}}>{message}</span>
)
let App = ({message, color, handleChange}) => (
<div>
<Text message={message} color={color} /> <br />
<Input handleChange={handleChange} />
</div>
)
class ConnectedApp extends React.Component {
constructor(props) {
super(props)
this.state = {
color: 'orange',
message: 'Hello World'
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
return (event) => {
console.log(id)
if (id === 'mySpecialInput') {
this.setState({'color': event.target.value})
}
}
}
render() {
return (
<App
message={this.state.message}
color={this.state.color}
handleChange={this.handleChange} />
)
}
}
ReactDOM.render(
<ConnectedApp/>,
document.getElementById('react_example')
);
How would something simple like this be worked into using redux?
Here's the code above built using redux!
Working Example
let {createStore} = Redux
let {connect, Provider} = ReactRedux
// React component
let Input = ({handleChange}) => (
<input type="text" onChange={handleChange('mySpecialInput')} />
)
let Text = ({message, color}) => (
<span style={{color}}>{message}</span>
)
let App = ({message, color, handleChange}) => (
<div>
<Text message={message} color={color} /> <br />
<Input handleChange={handleChange} />
</div>
)
// Action
const CHANGE_COLOR = 'CHANGE_COLOR'
function changeColorAction(color) {
return {
type: CHANGE_COLOR,
color
}
}
// Reducer
function reducer(state = {color: "#ffa500"}, action) {
let count = state.count
switch (action.type) {
case CHANGE_COLOR:
return { color: action.color }
default:
return state
}
}
// Store
let store = createStore(reducer)
// Map Redux state to component props
function mapStateToProps(state) {
return {
color: state.color,
message: "Hello World"
}
}
function changeColorDispatcher (dispatch) {
return (id) => {
return (event) => {
if (id === 'mySpecialInput') {
return dispatch(changeColorAction(event.target.value))
}
}
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
handleChange: changeColorDispatcher(dispatch)
}
}
// Connected Component
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App)
ReactDOM.render(
<Provider store={store}>
<ConnectedApp/>
</Provider>,
document.getElementById('react_example')
);
You can simply connect each of your components individually instead of using connect() on your top-level component and passing down the props in the hierarchy tree.
This will come way more handy.
See a working example!