I have some trouble with my code. I made an app where I use an API last fm and I want to add a rating button, I get few things from Google. Rating is displayed where I want to be, I can console log him, but it's on external file and I have no idea how to modify rate state from my app.js. Here is my code:
App.js
import React, { Component } from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import { getArtists } from './services/api';
import {
TextField,
Button,
List
} from '#material-ui/core';
import { ArtistCard } from './components/ArtistCard';
import { SearchResult } from './components/SearchResult';
import './App.css';
import { get } from 'https';
const handleChangeRate = (state) => {
this.setState({rate: state})
}
const isEmpty = (str) => str.length === 0;
class App extends Component {
state = {
searchTerm: '',
savedArtists: [],
rate:[]
}
componentDidMount() {
const existing = localStorage.getItem('savedArtists')
if (existing) {
this.setState({ savedArtists: JSON.parse(existing) })
}
}
onTextChange = (event) => {
const value = event.target.value;
this.setState({ searchTerm: value });
}
search = async (terms) => {
const artists = await getArtists(terms);
this.setState({ artists: artists })
}
onSearchClick = () => {
this.search(this.state.searchTerm);
}
clearSearch = () => {
this.setState({
searchTerm: '',
artists: []
})
}
updateArtists = (newArtists) => {
this.setState({ savedArtists: newArtists })
localStorage.setItem('savedArtists', JSON.stringify(newArtists));
}
deleteArtist = (artist) => {
const result = this.state.savedArtists.filter(item => item.name !== artist.name);
this.updateArtists(result);
}
onResultClick = (artist) => {
this.clearSearch();
const savedArtists = this.state.savedArtists;
savedArtists.push(artist);
this.updateArtists(savedArtists);
}
handleChangeRate = (state) => {
this.setState({rate: state})
}
render() {
const results = this.state.artists || [];
return (
<div className="App">
<header className="App-header">
<AppBar position="static" color="primary">
<Toolbar className="search-bar">
<Typography variant="h6" color="inherit">
Photos
</Typography>
<TextField
placeholder="Search on Last.fm"
className="search-input"
onChange={this.onTextChange}
value={this.state.searchTerm}
/>
<Button
onClick={this.onSearchClick}
variant="contained"
color="secondary"
disabled={isEmpty(this.state.searchTerm)}
>
Search
</Button>
{!isEmpty(this.state.searchTerm) && (
<Button
onClick={this.clearSearch}
variant="contained"
>
Clear
</Button>)
}
</Toolbar>
</AppBar>
</header>
<List className="search-results">
{
results.map((artist, index) => {
return <SearchResult key={index} artist={artist} onResultClick={this.onResultClick} />
})
}
</List>
<div className="artist-container">
{
this.state.savedArtists.map((artist, index) => {
return <ArtistCard artist={artist} key={index} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
})
}
</div>
</div>
);
}
}
export default App;
artistCard.js
import React from 'react';
import { Card, CardContent, CardActions, Button } from '#material-ui/core'
import ReactStars from 'react-stars'
export const ratingChanged = (newRating) => {
const { onChangeRating } = this.props;
onChangeRating(newRating);
}
export const ArtistCard = (props) => {
const { artist, deleteArtist } = props;
console.log(artist.cardImage)
return (
<Card className="artist-card">
<div className="image-container">
<img src={artist.cardImage} alt={artist.name} />
</div>
<CardContent>
<h3>{artist.name}</h3>
<p>{artist.listeners} listeners.</p>
<ReactStars
count = {5}
onChange={ratingChanged}
size={27}
color2 ={'#ffd700'}
/>
</CardContent>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="secondary" onClick={() => deleteArtist(artist)}>
Delete
</Button>
</CardActions>
</Card>
)
}
You need to pass the function to change State to artistCard as props
In App.js add the following fucntion
const handleChangeRate = (state) => {
this.setState(rate: state)
}
and Pass the same as props to ArtistCard like specified
<ArtistCard artist={artist} key={index} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
And in artistCard.js change ratingChanged method to
const ratingChanged = (newRating) => {
const { onChangeRating } = this.props;
onChangeRatng(newRating);
}
PS: This answer is based on the understanding i gained after going through this question, If this is not the requirement Please feel free to comment
EDIT
const handleChangeRate = (state) => {
this.setState(rate: state)
}
try adding the value prop to ReactStart like specified below
<ReactStars
value={this.props.rate}
count={5}
onChange={ratingChanged}
size={24}
color2={'#ffd700'}
/>
Pass rate state to artist card component as prop like specified below
<ArtistCard artist={artist} key={index} deleteArtist= {this.deleteArtist} onChangeRating={this.handleChangeRate} rate={this.state.rate} />
EDIT
cardComponent.js
import React from 'react';
import ReactStars from 'react-stars'
export const ArtistCard = (props) => {
const { artist, deleteArtist, onChangeRating } = props;
console.log(props.rating)
return (
<ReactStars
value={props.rating}
count = {5}
onChange={(newRating) => onChangeRating(newRating)}
size={27}
color2 ={'#ffd700'}
/>)}
App.js
handleChangeRate = (state) => {
this.setState({rate: state})
}
<ArtistCard artist={'artist'} key={'index'} rating={this.state.rate} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
FINAL EDIT
Changes made in your code
App.js
modified state object to
state = {
searchTerm: '',
savedArtists: [],
rate: ''
}
Artistcard component line to
<ArtistCard rate={this.state.rate} artist={artist} key={index} onChangeRating={(val) => {this.setState({ rate: val })}} deleteArtist={this.deleteArtist} />
In ArtistCard.js
rendering reactstart component like this
<ReactStars
value={props.rate}
count = {5}
onChange={(newRating) => onChangeRating(newRating)}
size={27}
color2 ={'#ffd700'}
/>
Related
I am learning to build a to-do app in Nextjs + Typescript. I have a Chakra UI modal which pops up when the user clicks on the "edit task" button. The modal contains a Input element and it should have the value of task title. Here is the solution that I have implemented.
// components/tasks.tsx
import { Box } from '#chakra-ui/react';
import { NextPage } from 'next';
import Task from '../components/task';
import EditTaskModal from '../components/edit-task-modal';
import { useState } from 'react';
import { useModalContext } from '../store/modal';
const Tasks: NextPage = () => {
const { onOpen } = useModalContext();
const [editTaskID, setEditTaskID] = useState('');
const handleOpenModal = (id: string) => {
setEditTaskID(id);
onOpen();
};
return (
<>
<EditTaskModal id={editTaskID} />
<Box>
<Task handleOpenModal={handleOpenModal} />
</Box>
</>
);
};
export default Tasks;
// components/task.tsx
import { NextPage } from 'next';
import { Box, Checkbox, Button, Text } from '#chakra-ui/react';
import { useTaskContext } from '../store/tasks';
import { useModalContext } from '../store/modal';
type TaskProps = {
handleOpenModal: (id: string) => void;
};
const Task: NextPage<TaskProps> = ({ handleOpenModal }) => {
const { tasks } = useTaskContext();
const { onOpen } = useModalContext();
return (
<Box>
{tasks.map((task) => {
return (
<Box
key={task.id}
border="3px solid"
borderColor="gray.200"
marginBottom="1em"
p="1em"
borderRadius="5px"
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Text key={task.id}>{task.title}</Text>
<Box width="35%" display="flex" justifyContent="space-between">
<Button
size="sm"
onClick={() => {
handleOpenModal(task.id);
}}
>
Edit
</Button>
<Button size="sm" colorScheme="red">
Delete
</Button>
</Box>
</Box>
);
})}
</Box>
);
};
export default Task;
// components/edit-task-modal.tsx
import { NextPage } from 'next';
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
FormControl,
Input,
ModalFooter,
} from '#chakra-ui/react';
import { useModalContext } from '../store/modal';
import { useEffect, useState } from 'react';
import { useTaskContext } from '../store/tasks';
type EditTaskModalProps = {
id: string;
};
type updateTaskType = { id: string; title: string; status: boolean };
const updateTask = async (task: updateTaskType) => {
try {
const response = await fetch('http://localhost:3000/api/tasks', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
} catch (error) {
console.log(error);
}
};
const EditTaskModal: NextPage<EditTaskModalProps> = ({ id }) => {
const { tasks } = useTaskContext();
const task = tasks.find((task) => {
return task.id === id;
});
const [title, setTitle] = useState(task?.title);
const { isOpen, onClose } = useModalContext();
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Update Task Name</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<Input
placeholder="Task Name"
value={title}
onChange={(e) => {
setTitle(e.target.value);
}}
/>
</FormControl>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
onClick={() => {
if (task !== undefined)
updateTask({
title: task.title,
status: task.status,
id: task.id,
});
}}
>
Update
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default EditTaskModal;
// store/modal.tsx
import React, { createContext, useContext } from 'react';
import { useDisclosure } from '#chakra-ui/react';
const ModalContext = createContext<any>(null);
const ModalProvider = ({ children }: { children: React.ReactNode }) => {
const { onOpen, onClose, isOpen } = useDisclosure();
return (
<ModalContext.Provider value={{ onOpen, onClose, isOpen }}>
{children}
</ModalContext.Provider>
);
};
const useModalContext = () => {
return useContext(ModalContext);
};
export { ModalProvider, useModalContext };
However, I noticed that the value attribute in Input element is always one state update behind for some reason and I can't figure out why. Is there a better solution to pass task id to the modal?
I am new to react, i have this code which i got from my manager, i have to add an event, actually on button click i have to add a class to other component, the button is in Header component, and the class i have to add is in LeftNav.
Here is my header.js
export class Header extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
anchorEl: null,
gettingUser: false,
loggingOut: false,
menuOpen: false
};
this.cookies = new Cookies();
}
i made a state menuOpen.
and tried setting state to true like this in header.js
<Button className="menu-btn-wrapper header-button" onClick={() => self.setState({ menuOpen: true })}>
<i className="icon circle zf-cs-icon-burger"></i>
</Button>
and moving on to my app.js, i have this
<Header history={history} />
<LeftNav className="leftFixed leftNav" />
How can i get the state of menuOpen from Header here?
i tried
<Header history={history} menuOpen={this.state.menuOpen} />
but i am getting a stateless error.
There is no class present in app.js
Here is my full app.js
import React, { useEffect } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import IdleTimer from 'react-idle-timer';
import Cookies from 'universal-cookie';
import { LinearProgress, IconButton, Tooltip } from '#material-ui/core';
import { Cancel as CancelIcon } from '#material-ui/icons';
import { getToken, updateMaxAge, expirySet } from '../auth';
import { cancelFileUpload, setSnackbar } from '../actions/appActions';
import { logoutUser, saveUserSettings, setAccountActivationCode, setShowConnectingDialog } from '../actions/userActions';
import { privateRoutes, publicRoutes } from '../config/routes';
import { onboardingSteps } from '../constants/onboardingConstants';
import SharedSnackbar from '../components/common/sharedsnackbar';
import ErrorBoundary from '../errorboundary';
import { getOnboardingState } from '../selectors';
import { colors } from '../themes/mender-theme';
import Tracking from '../tracking';
import { getOnboardingComponentFor } from '../utils/onboardingmanager';
import LiveChatBox from './livechatbox';
import ConfirmDismissHelptips from './common/dialogs/confirmdismisshelptips';
import DeviceConnectionDialog from './common/dialogs/deviceconnectiondialog';
import Header from './header/header';
import LeftNav from './leftnav';
import 'react-perfect-scrollbar/dist/css/styles.css';
import PerfectScrollbar from 'react-perfect-scrollbar';
const activationPath = '/activate';
const timeout = 900000; // 15 minutes idle time
const cookies = new Cookies();
export const AppRoot = ({
cancelFileUpload,
currentUser,
history,
logoutUser,
onboardingState,
setAccountActivationCode,
setShowConnectingDialog,
showDeviceConnectionDialog,
showDismissHelptipsDialog,
setSnackbar,
snackbar,
trackingCode,
uploadProgress
}) => {
useEffect(() => {
if (trackingCode) {
if (!cookies.get('_ga')) {
Tracking.cookieconsent().then(({ trackingConsentGiven }) => {
if (trackingConsentGiven) {
Tracking.initialize(trackingCode);
Tracking.pageview();
}
});
} else {
Tracking.initialize(trackingCode);
}
}
history.listen(trackLocationChange);
trackLocationChange(history.location);
}, []);
const trackLocationChange = location => {
// if we're on page whose path might contain sensitive device/ group/ deployment names etc. we sanitize the sent information before submission
let page = location.pathname || '';
if (page.includes('=') && (page.startsWith('/devices') || page.startsWith('/deployments'))) {
const splitter = page.lastIndexOf('/');
const filters = page.slice(splitter + 1);
const keyOnlyFilters = filters.split('&').reduce((accu, item) => `${accu}:${item.split('=')[0]}&`, ''); // assume the keys to filter by are not as revealing as the values things are filtered by
page = `${page.substring(0, splitter)}?${keyOnlyFilters.substring(0, keyOnlyFilters.length - 1)}`; // cut off the last & of the reduced filters string
} else if (page.startsWith(activationPath)) {
setAccountActivationCode(page.substring(activationPath.length + 1));
history.replace('/settings/my-profile');
}
Tracking.pageview(page);
};
const onIdle = () => {
if (expirySet() && currentUser) {
// logout user and warn
return logoutUser('Your session has expired. You have been automatically logged out due to inactivity.').catch(() => updateMaxAge());
}
};
const onboardingComponent = getOnboardingComponentFor(onboardingSteps.ARTIFACT_CREATION_DIALOG, onboardingState);
const containerProps = getToken() ? { id: 'app' } : { className: 'flexbox centered', style: { minHeight: '100vh' } };
return (
<div {...containerProps}>
{getToken() ? (
<>
<IdleTimer element={document} onAction={updateMaxAge} onIdle={onIdle} timeout={timeout} />
<Header history={history} />
<LeftNav className="leftFixed leftNav" />
<PerfectScrollbar className="rightFluid container main">
<ErrorBoundary>{privateRoutes}</ErrorBoundary>
</PerfectScrollbar>
{onboardingComponent ? onboardingComponent : null}
{showDismissHelptipsDialog && <ConfirmDismissHelptips />}
{showDeviceConnectionDialog && <DeviceConnectionDialog onCancel={() => setShowConnectingDialog(false)} />}
{
// eslint-disable-next-line no-undef
ENV === 'production' && <LiveChatBox />
}
</>
) : (
publicRoutes
)}
<SharedSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
{Boolean(uploadProgress) && (
<div id="progressBarContainer">
<p className="align-center">Upload in progress ({Math.round(uploadProgress)}%)</p>
<LinearProgress variant="determinate" style={{ backgroundColor: colors.grey, gridColumn: 1, margin: '15px 0' }} value={uploadProgress} />
<Tooltip title="Abort" placement="top">
<IconButton onClick={cancelFileUpload}>
<CancelIcon />
</IconButton>
</Tooltip>
</div>
)}
</div>
);
};
const actionCreators = { cancelFileUpload, logoutUser, saveUserSettings, setAccountActivationCode, setShowConnectingDialog, setSnackbar };
const mapStateToProps = state => {
return {
currentUser: state.users.currentUser,
onboardingState: getOnboardingState(state),
showDismissHelptipsDialog: !state.onboarding.complete && state.onboarding.showTipsDialog,
showDeviceConnectionDialog: state.users.showConnectDeviceDialog,
snackbar: state.app.snackbar,
trackingCode: state.app.trackerCode,
uploadProgress: state.app.uploadProgress
};
};
export default compose(withRouter, connect(mapStateToProps, actionCreators))(AppRoot);
and in main.js
export const Main = () =>
render(
<Provider store={store}>
<MuiThemeProvider theme={theme}>
<ErrorBoundary>
<Router basename="/ui/#">
<App />
</Router>
</ErrorBoundary>
</MuiThemeProvider>
</Provider>,
document.getElementById('main') || document.createElement('div')
);
Main();
The root component is AppRoot, that is a Functional Component.
define the menuOpen state in AppRoot with useState:
const [menuOpen,setMenuOpen]=useState(false)
then define a function in AppRoot to handle menuOpen changes:
const handleMenuOpen=()=>{
setMenuOpen(true)
}
then pass the handler function to the Header via props:
<Header history={history} onButtonClick={handleMenuOpen} />
inside the Header:
<Button className="menu-btn-wrapper header-button" onClick={() => this.props.onButtonClick()}>
<i className="icon circle zf-cs-icon-burger"></i>
</Button>
AppRoot after all above steps:
export const AppRoot = ({
cancelFileUpload,
currentUser,
history,
logoutUser,
onboardingState,
setAccountActivationCode,
setShowConnectingDialog,
showDeviceConnectionDialog,
showDismissHelptipsDialog,
setSnackbar,
snackbar,
trackingCode,
uploadProgress
}) => {
const [menuOpen,setMenuOpen]=useState(false)
const handleMenuOpen=()=>{
setMenuOpen(true)
}
useEffect(() => {
if (trackingCode) {
if (!cookies.get('_ga')) {
Tracking.cookieconsent().then(({ trackingConsentGiven }) => {
if (trackingConsentGiven) {
Tracking.initialize(trackingCode);
Tracking.pageview();
}
});
} else {
Tracking.initialize(trackingCode);
}
}
history.listen(trackLocationChange);
trackLocationChange(history.location);
}, []);
const trackLocationChange = location => {
// if we're on page whose path might contain sensitive device/ group/ deployment names etc. we sanitize the sent information before submission
let page = location.pathname || '';
if (page.includes('=') && (page.startsWith('/devices') || page.startsWith('/deployments'))) {
const splitter = page.lastIndexOf('/');
const filters = page.slice(splitter + 1);
const keyOnlyFilters = filters.split('&').reduce((accu, item) => `${accu}:${item.split('=')[0]}&`, ''); // assume the keys to filter by are not as revealing as the values things are filtered by
page = `${page.substring(0, splitter)}?${keyOnlyFilters.substring(0, keyOnlyFilters.length - 1)}`; // cut off the last & of the reduced filters string
} else if (page.startsWith(activationPath)) {
setAccountActivationCode(page.substring(activationPath.length + 1));
history.replace('/settings/my-profile');
}
Tracking.pageview(page);
};
const onIdle = () => {
if (expirySet() && currentUser) {
// logout user and warn
return logoutUser('Your session has expired. You have been automatically logged out due to inactivity.').catch(() => updateMaxAge());
}
};
const onboardingComponent =
getOnboardingComponentFor(onboardingSteps.ARTIFACT_CREATION_DIALOG, onboardingState);
const containerProps = getToken() ? { id: 'app' } : { className: 'flexbox centered', style: { minHeight: '100vh' } };
return (
<div {...containerProps}>
{getToken() ? (
<>
<IdleTimer element={document} onAction={updateMaxAge} onIdle={onIdle} timeout={timeout} />
<Header history={history} onButtonClick={handleMenuOpen} />
<LeftNav className="leftFixed leftNav" />
<PerfectScrollbar className="rightFluid container main">
<ErrorBoundary>{privateRoutes}</ErrorBoundary>
</PerfectScrollbar>
{onboardingComponent ? onboardingComponent : null}
{showDismissHelptipsDialog && <ConfirmDismissHelptips />}
{showDeviceConnectionDialog && <DeviceConnectionDialog onCancel={() => setShowConnectingDialog(false)} />}
{
// eslint-disable-next-line no-undef
ENV === 'production' && <LiveChatBox />
}
</>
) : (
publicRoutes
)}
<SharedSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
{Boolean(uploadProgress) && (
<div id="progressBarContainer">
<p className="align-center">Upload in progress ({Math.round(uploadProgress)}%)</p>
<LinearProgress variant="determinate" style={{ backgroundColor: colors.grey, gridColumn: 1, margin: '15px 0' }} value={uploadProgress} />
<Tooltip title="Abort" placement="top">
<IconButton onClick={cancelFileUpload}>
<CancelIcon />
</IconButton>
</Tooltip>
</div>
)}
</div>
);
};
Is there a way in React Query to return previous data when error occurs? Right now when there is error all posts disappear and I have empty page because data is undefined but I want to keep the old data. I want to have old data when error occurs. I have keepPreviousData: true but this doesn't solve the problem.
import React from 'react';
import AxiosClient from 'api/axiosClient';
import { createFetchResourcesAction } from 'api/actions/resources/resourcesActions';
import { Post, Resources } from 'api/actions/resources/resources.types';
import { Home } from './Home';
export const HomeContainer = () => {
const { selectedPage, perPage, handlePageChange } = usePagination();
const fetchResources = ({ pageParam = 1 }) =>
AxiosClient.request<Resources<Post>>(createFetchResourcesAction(perPage, pageParam));
const { data, isError, isFetching } = useQuery(
['resources', selectedPage, perPage],
() => fetchResources({ pageParam: selectedPage }),
{ keepPreviousData: true },
);
return (
<Home
posts={data?.data.results}
isLoading={isFetching}
isError={isError}
selectedPage={selectedPage}
numberOfPages={Math.ceil(resolverNumberOfPages())}
onPageChange={(action, value) => handlePageChange(action, +value)}
/>
);
};
Home.tsx
import React from 'react';
import { useLocale } from 'hooks';
import { AppMessages } from 'i18n/messages';
import { Post } from './post/Post';
import { Box, Typography } from '#material-ui/core';
import { useStyles } from './Home.style';
import { HomeProps } from './Home.types';
import { Loader, ErrorMessage, Button } from 'ui';
import { Pagination } from 'ui';
export const Home = ({
posts,
isLoading,
isError,
numberOfPages,
selectedPage,
onPageChange,
}: HomeProps) => {
const { formatMessage } = useLocale();
const styles = useStyles();
const handlePageChange = (page: number) => {
onPageChange('page', page);
};
const postList = <>{posts && posts.map(post => <Post post={post} key={post.id} />)}</>;
return (
<Box className={styles.home}>
<Box className={styles.main}>
<Typography variant="h2" className={styles.title}>
{formatMessage({ id: AppMessages['home.allEntries'] })}
</Typography>
<Box className={styles.postsWrapper}>
{posts && posts.length < 1 && !isError && !isLoading && (
<Typography variant="h3" className={styles.genericMessage}>
{formatMessage({ id: AppMessages['home.noEntries'] })}
</Typography>
)}
{postList}
{isLoading && <Loader position="fixed" width={60} height={60} />}
{isError && (
<ErrorMessage position="top" description={formatMessage({ id: AppMessages['home.errorLoadingEntries'] })} />
)}
{isDesktop && numberOfPages > 1 ? (
<Pagination numberOfPages={numberOfPages} selectedPage={selectedPage} onPageClick={handlePageChange} />
) : (
nyll
)}
</Box>
</Box>
</Box>
);
};
createFetchResourcesAction.ts
import { AxiosRequestConfig } from 'axios';
export const createFetchResourcesAction = (perPage: number, page: number): AxiosRequestConfig => {
return {
method: 'get',
url: /resources?per_page=${perPage}&page=${page},
};
}
As far as I'm aware, react-query doesn't delete any data from the cache if a query goes from success to error state. if you have data in your cache, you will get it, even if the query errors after that. So this is a possible state:
{ state: 'error', isError: true, data: { json from previous successful response }}
As, I am new to react I don't know how to perform dynamic add and edit and cancel operations on the textarea. I have dynamic array , i want to perform edit and cancel operations for every textarea individually . If I click on a edit button the mouse cursor should point to the specific textbox, and it should turn into editable mode . If, I click on cancel button the specific textarea should turn into non-editable mode. codesandboxdemo please Run the code in codesandox and give me the solution
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
App.js
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
count: [],
disabled: false
};
this.newText = {};
this.handleEdit = this.selectText.bind(this);
}
handleCancel(e,index) {
this.setState({disabled:true})
}
handleRemove(index)
{
this.state.count.splice(index,1)
this.setState({count: this.state.count})
}
selectText(e, index) {
newText = this.state.count[index];
console.log(newText);
this.newText.select();
}
add(e) {
this.setState({ count: [...this.state.count, ""] ,disabled:false});
}
handleChange(e, index) {
this.state.count[index] = e.target.value;
this.setState({ count: this.state.count });
}
render() {
return (
<div>
<label>Enter the text</label>
{this.state.count.map((counts, index) => {
return (
<div key={index}>
<input
ref={(newText) => (this.newText = newText)}
onChange={(e) => this.handleChange(e, index)}
value={counts}
disabled = {(this.state.disabled)? "disabled" : ""}
/>
<button onClick={(e) => this.handleEdit(e,index)}>Edit</button>
<button onClick={() => this.handleRemove(index)}>Remove</button>
<button onClick = {(e) =>this.handleCancel(e,index)}> cancel </button>
</div>
);
})}
<button onClick={(e) => this.add(e)}> Add</button>
</div>
);
}
}
export default App;
.App {
font-family: sans-serif;
text-align: center;
}
`]2
I just tried doing it this way for you. This is not a complete answer (just to make sure I don't spoon-feed you, but this is a possible approach). Tell me if this works?
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [Value, setValue] = useState("");
const [EditMode, setEditMode] = useState(false);
const toggleEditMode = () => setEditMode(!EditMode);
return EditMode ? (
<input
type="text"
value={Value}
onChange={(e) => setValue(e.target.value)}
onBlur={toggleEditMode}
/>
) : (
<span onClick={toggleEditMode}>{Value}</span>
);
};
export default App;
Click and it will make it editable. Come out and it shows updated value.
CodeSandbox: https://c4fog.csb.app/
Here is full working code of App.js
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
count: [],
disabled: [],
};
this.references = []
}
handleRef(r, index) {
this.references[index] = r
}
handleCancel(e,index) {
const { disabled } = this.state;
disabled[index] = true
this.setState({ disabled })
}
handleRemove(index)
{
this.state.count.splice(index,1)
this.setState({count: this.state.count})
}
handleEdit(e, index) {
const { disabled } = this.state;
disabled[index] = false
this.setState({ disabled }, () => {
this.references[index].focus()
})
}
add(e) {
this.setState({ count: [...this.state.count, ""] });
}
handleChange(e, index) {
const { count } = this.state;
count[index] = e.target.value;
this.setState({ count });
}
render() {
const { disabled, count } = this.state
return (
<div>
<label>Enter the text</label>
{count.map((counts, index) => {
return (
<div key={index}>
<input
ref={(newText) => this.handleRef(newText, index)}
onChange={(e) => this.handleChange(e, index)}
value={counts}
disabled ={disabled[index]}
/>
<button onClick={(e) => this.handleEdit(e,index)}>Edit</button>
<button onClick={() => this.handleRemove(index)}>Remove</button>
<button onClick={(e) =>this.handleCancel(e,index)}>Cancel</button>
</div>
);
})}
<button onClick={(e) => this.add(e)}> Add</button>
</div>
);
}
}
export default App;
I'm trying to fetch like counts from the nested array(Likes) within the posts object array.
I'm trying to do
<Like like={id} likes={Likes.data.count} />
but getting this error.
TypeError: Cannot read property 'count' of undefined
PostItem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, getLikeCount, postLike} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
myId: 0,
likes:0
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
const myLike = this.props.likeCount
const like = this.props.likeCount
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, Likes, clickLike} = this.props
return(
<div>
{/* {this.getLikes(id)} */}
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
<Like like={id} likes={Likes.data.count} />
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(PostItem);
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, getLikeCount, postLike, UpdatePost,EditChange, DisableButton} from '../actions/';
import PostItem from './PostItem';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
backend
router.get('/myPosts', async (req, res) =>{
await models.Post.findAll({
include:[{
model:models.Likes
}],
order:[
['createdAt', 'DESC'],
], limit: 6 })
.then( (posts) =>{
res.json(posts);
})
});
Likes is an Array so you can get the length by doing Likes.length. Or if you want the total of true like, you can do Likes.reduce((count, ({ like })) => like ? count + 1 : count), 0)