I am trying to get my head around React, and I am doing some projects. One of them involving rerendering a component after a variable changes after setTime out runs out.
I am supposing it has to rerender, since it changes after the initial render ( and that it doesn't react (heh) the the updated variable.
I am guessing I am misunderstanding some React fundamentals here.
Arrows to highligt my problem.
Component:
import React from 'react';
import classes from './Navigation.module.css';
const navigation = (props) => {
let attachedClasses = [classes.SmallDot];
let showCloseButton = false; <-------!
if (props.currentState) {
attachedClasses = [classes.BigDot]
setTimeout(() => {
showCloseButton = true; <----------!
}, 500)
}
return (
<div onClick={props.currentState ? null : props.toggleMenuState} className={attachedClasses.join(' ')}>
{showCloseButton ? <--------!
<div onClick={props.toggleMenuState} className={classes.CloseButton}>
<div>X</div>
</div>
: null}
</div>
)
};
export default navigation;
Related
I am currently making a Trello Clone. It has been going well so far and I've had a lot of help from everyone here, so thank you!
My current issue is that I am trying to pass the state of modalData in App.js to <ModifyModal />.
I have tried researching and Googling, and even re-writing functions and creating new ones. However, nothing had worked. I know that the state is being updated with the correct text since I made the title from Trello Clone! to {modalData} and it worked. I want the data of modalData to be passed from App.js to <ModifyModal />.
Edit: Made a functional component and it is still showing undefined for the data.
App.js:
import React, { Component } from 'react';
import './App.css';
import Todobox from './Todobox';
import ModifyModal from './ModifyModal';
import Item from './Item';
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
const Widget2 = () => <ModifyModal />
class App extends Component {
constructor(props){
super(props);
this.handleCallback = this.handleCallback.bind(this);
this.state={
elements: [],
modal: [],
modalData: null
}
}
// Creates new element box
handleNewElement = () => {
const newElement = [...this.state.elements, Widget];
this.setState({
elements: newElement
});
}
handleCallback = (itemWidget, itemData) =>{
const newModal = [...this.state.modal, itemWidget];
const newData = itemData;
this.setState({
modal: newModal,
modalData: newData
});
}
render() {
const { elements, modal, modalData } = this.state;
return (
<>
<div className='page-container'>
<div className='header'>
<a className='header-title'>{modalData}</a>
<a className='header-button' onClick={this.handleNewElement.bind(this)}>Create a list</a>
</div>
<div className='element-field'>
{elements.length !== 0 &&
elements.map((Widget, i) => <Widget key={i} parentCallback2={this.handleCallback}/>)}
</div>
</div>
{modal.length !== 0 &&
modal.map((Widget2, i) => <Widget2 key={i} itemDataToChild={modalData} />)}
</>
);
}
}
export default App;
ModifyModal.jsx:
import React from "react";
import { useState } from "react";
import trash from './trash_can.png';
import './App.css'
function ModifyModal({ itemDataToChild }){
const [hideModal, setHideModal] = useState(false);
const [content, setContent] = useState(itemDataToChild);
const handleCancel = () =>{
setHideModal(true);
}
return(
<>
<div className={`modify-modal-container ${hideModal ? 'modify-modal-container-hide' : ''}`}>
<div className='modify-modal'>
<a className='modify-title'>{content}</a>
<textarea className='modify-input' />
<div className='modify-buttons'>
<a className='modify-btn' id='modify-update-btn'>Update</a>
<a className='modify-btn' id='modify-cancel-btn' onClick={handleCancel}>Cancel</a>
<img src={trash} id='modify-delete'/>
</div>
</div>
</div>
</>
)
}
export default ModifyModal;
Any help is appreciated since I am new to this. :)
The problem is when you declared and initialized Widget2.
const Widget2 = () => <ModifyModal />
What is actually happening under the hood is that Widget2 received a function which returns a JSX.Element, it didn't actually become ModifyModal, the functional component.If you look at the line above is actually doing right.
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
There is 2 solution for this.
you can do just as Widget.
const Widget2 = ({itemDataToChild}) => <ModifyModal itemDataToChild={itemDataToChild}/>
Which I think should be the best approach since you can just rename your imports if was exported as default, and deleting the line const Widget2 = () => <ModifyModal />
import Widget2 from './ModifyModal';
Keeping in mind that the second approach would result error if used for Named Exports. Imports Reference.
For broad your understanding of JSX element and functional component I recommend take a look at their official documentation.
JSX, Components and Props
I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});
I am coding an app in which there is a collection of reviews and a person can respond to a review, but each review can only have one response. So far, I am doing this by rendering a ReviewResponseBox component in my ReviewCardDetails component and passing the review_id as props.
I have implemented the logic so that once there is one ReviewResponse, the form to write another will no longer appear. However, before I was initializing the state in this component with an empty array, so when I refreshed my page the response went away and the form came back up. (This is now commented out)
I am trying to resolve this by persisting my state using React LocalStorage but am having trouble writing my method to do this. Here is what I have so far:
Component that renders ReviewResponseBox and passes review_id as props:
import React from "react";
import './Review.css';
import { useLocation } from "react-router-dom";
import StarRatings from "react-star-ratings";
import ReviewResponseBox from "../ReviewResponse/ReviewResponseBox";
const ReviewCardDetails = () => {
const location = useLocation();
const { review } = location?.state; // ? - optional chaining
console.log("history location details: ", location);
return (
<div key={review.id} className="card-deck">
<div className="card">
<div>
<div className='card-container'>
<h4 className="card-title">{review.place}</h4>
<StarRatings
rating={review.rating}
starRatedColor="gold"
starDimension="20px"
/>
<div className="card-body">{review.content}</div>
<div className="card-footer">
{review.author} - {review.published_at}
</div>
</div>
</div>
</div>
<br></br>
<ReviewResponseBox review_id={review.id}/>
</div>
);
};
export default ReviewCardDetails;
component that I want to keep track of the state so that it can render the form or response:
import React from 'react';
import ReviewResponse from './ReviewResponse';
import ReviewResponseForm from './ReviewResponseForm';
import { reactLocalStorage } from "reactjs-localstorage";
class ReviewResponseBox extends React.Component {
// constructor() {
// super()
// this.state = {
// reviewResponses: []
// };
// }
fetchResponses = () => {
let reviewResponses = [];
localStorage.setResponses
reviewResponses.push(reviewResponse);
}
render () {
const reviewResponses = this.getResponses();
const reviewResponseNodes = <div className="reviewResponse-list">{reviewResponses}</div>;
return(
<div className="reviewResponse-box">
{reviewResponses.length
? (
<>
{reviewResponseNodes}
</>
)
: (
<ReviewResponseForm addResponse={this.addResponse.bind(this)}/>
)}
</div>
);
}
addResponse(review_id, author, body) {
const reviewResponse = {
review_id,
author,
body
};
this.setState({ reviewResponses: this.state.reviewResponses.concat([reviewResponse]) }); // *new array references help React stay fast, so concat works better than push here.
}
getResponses() {
return this.state.reviewResponses.map((reviewResponse) => {
return (
<ReviewResponse
author={reviewResponse.author}
body={reviewResponse.body}
review_id={this.state.review_id} />
);
});
}
}
export default ReviewResponseBox;
Any guidance would be appreciated
You would persist the responses to localStorage when they are updated in state using the componentDidUpdate lifecycle method. Use the componentDidMount lifecycle method to read in the localStorage value and set the local component state, or since reading from localStorage is synchronous directly set the initial state.
I don't think you need a separate package to handle this either, you can use the localStorage API easily.
import React from "react";
import ReviewResponse from "./ReviewResponse";
import ReviewResponseForm from "./ReviewResponseForm";
class ReviewResponseBox extends React.Component {
state = {
reviewResponses: JSON.parse(localStorage.getItem(`reviewResponses-${this.props.review_id}`)) || []
};
storageKey = () => `reviewResponses-${this.props.review_id}`;
componentDidUpdate(prevProps, prevState) {
if (prevState.reviewResponses !== this.state.reviewResponses) {
localStorage.setItem(
`reviewResponses-${this.props.review_id}`,
JSON.stringify(this.state.reviewResponses)
);
}
}
render() {
const reviewResponses = this.getResponses();
const reviewResponseNodes = (
<div className="reviewResponse-list">{reviewResponses}</div>
);
return (
<div className="reviewResponse-box">
{reviewResponses.length ? (
<>{reviewResponseNodes}</>
) : (
<ReviewResponseForm addResponse={this.addResponse.bind(this)} />
)}
</div>
);
}
addResponse(review_id, author, body) {
const reviewResponse = {
review_id,
author,
body
};
this.setState({
reviewResponses: this.state.reviewResponses.concat([reviewResponse])
}); // *new array references help React stay fast, so concat works better than push here.
}
getResponses() {
return this.state.reviewResponses.map((reviewResponse) => {
return (
<ReviewResponse
author={reviewResponse.author}
body={reviewResponse.body}
review_id={this.state.review_id}
/>
);
});
}
}
I am actually building a tic tac toe. But this error is not actually letting me update history. As I am following a tutorial on skillshare.com and I did the same as he does. But still getting error. I am a beginner in React. And I use a nano-react npm project for creating this project.
This is App.js:
import React , {useState} from "react";
import Board from "./components/Board"
import History from "./components/History"
import {calculateWinner} from './support'
import StatusMessage from './components/StatusMessage'
import './styles/root.scss'
const NEW_GAME = [
{
board: Array(9).fill(null),
isXNext : true
}
]
const App = () => {
const [history, setHistory] = useState(NEW_GAME);
const [currentMove, setCurrentMove] = useState(0);
const current = history[currentMove];
const {winner , winningSquare} = calculateWinner(current.board);
const clickHandleFunction = (position) => {
if (current.board[position] || winner) {
return;
}
setHistory((prev) => {
const last = prev[prev.length-1];
const newBoard = last.board.map((square, pos) => {
if (pos === position) {
return last.isXNext ? 'X' : '0';
}
return square;
});
return prev.concat({board: newBoard, isXNext : !last.isXNext})
});
setCurrentMove(prev => prev +1);
};
const moveTo = (move) => {
setCurrentMove(move);
}
const onNewGame = () => {
setHistory(NEW_GAME);
setCurrentMove(0);
}
return(
<div className="app">
<h1>TIC TAC TOE</h1>
<StatusMessage winner ={winner} current ={current}/>
<Board board = {current.board} clickHandleFunction = {clickHandleFunction} winningSquare = {winningSquare}/>
<button type="button" onClick = {onNewGame}>Start New Game</button>
<History history={history} moveTo = {moveTo} currentMove = {currentMove} />
</div>
)
}
export default App;
And It is my History.js:
import React from 'react.'
function History({history, moveTo, currentMove}) {
return (
<ul>
{
history.map((_, move) => {
return( <li key={move}> <button style={{
fontWeight: move === currentMove ? 'bold' : 'normal'
}} type="button" onClick = {moveTo(move)} >
{move === 0 ? 'Go to game start!': `Gove to move #${move}`} </button> </li> );
})
}
</ul>
)
}
export default History
The problem is in History.js:
onClick={moveTo(move)}
You need to provide a function in the onClick prop. Instead, you are calling the moveTo function and passing its return value as the onClick prop.
Due to this, whenever React renders the History component, it also inadvertently calls the moveTo function which triggers an update in the App component. This is what the error says - can't update another component while rendering a component.
To fix this, change moveTo(move) to () => moveTo(move). Now you pass a function into onClick that will call the moveTo function when the user clicks. Working sandbox: https://codesandbox.io/s/practical-frog-tcyxm?file=/src/components/History.js
In React Navigation move the setParams and setOptions inside method componentDidMount() in class components or in useEffects() hook in functional components.
Why this does not work ?
import React from 'react';
function Room() {
let check = null;
const ibegyouwork = () => {
check = <button>New button</button>;
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
And this works fine ?
import React from 'react';
function Room() {
let check = null;
return (
<div>
<button>No need for this button because in this case the second button is auto-displayed</button>
{check}
</div>
);
}
export default Room;
Basically I try to render a component based on a condition. This is a very basic example. But what I have is very similar. If you wonder why I need to update the check variable inside that function is because in my example I have a callback function there where I receive an ID which I need to use in that new component.
The example that I provided to you is basically a button and I want to show another one when I press on this one.
I am new to React and despite I searched in the past 2 hours for a solution I couldn't find anything to address this issue.
Any tips are highly appreciated !
Your component has no idea that something has changed when you click the button. You will need to use state in order to inform React that a rerender is required:
import React, {useState} from 'react'
function Room() {
const [check, setCheck] = useState(null);
const ibegyouwork = () => {
setCheck(<button>New button</button>);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
When you call setCheck, React basically decides that a rerender is required, and updates the view.
The latter is working because there are no changes to the check value that should appear on the DOM.
If check changes should impact and trigger the React render function, you would want to use a state for show/hide condition.
import React from 'react';
const Check = () => <button>New button</button>;
function Room() {
const [show, setShow] = React.useState(false);
const ibegyouwork = () => {
setShow(true);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{show && <Check />}
</div>
);
}
export default Room;