I have a note list component that is connected to redux and render a list of items. I create 2 unit test for testing component for when component have list and when is empty. A first unit test no notes on the list is a success but when testing having multiple notes on the list it keeps the previous unit test dom and the test is failed.
import React from 'react';
import '#testing-library/jest-dom/extend-expect';
import { render, screen } from '#testing-library/react';
import configureStore from 'redux-mock-store';
import NoteList from '../../../../../src/components/note/note-list-children/NoteList';
const tasks = [
{
title: 'e2e',
content: 'Learning E2E',
date: '2020/03/05',
id: 'evRFE5i5',
done: false,
},
{
title: 'unit',
content: 'Learning Unit Test',
date: '2020/03/04',
id: 'FTSTSrn7',
done: false,
},
];
describe('No notes on list.', () => {
const mockStore = configureStore();
const initialState = {
tasksReducer: [],
mainReducer: {},
};
const store = mockStore(initialState);
beforeEach(() => {
render(<NoteList store={store} tasks={[]} />);
});
it('Should having no notes on the list (showing a proper message).', () => {
expect(screen.getByText(/No notes yet./i)).toBeTruthy();
});
});
describe('Having multiple notes on the list.', () => {
beforeEach(() => {
const mockStore = configureStore();
const initialState = {
tasksReducer: [],
mainReducer: {},
};
const store = mockStore(initialState);
render(<NoteList store={store} tasks={tasks} />);
});
it('Should not showing empty massege for note lists', () => {
screen.debug();
expect(screen.queryByText(/No notes yet./i)).not.toBeInTheDocument();
});
});
Note list component
import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
// Redux
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { deleteTask, doneTask } from '../../../redux/actions/actionTasks';
import { activeMain } from '../../../redux/actions/actionMain';
// Materail UI
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemSecondaryAction from '#material-ui/core/ListItemSecondaryAction';
import ListItemText from '#material-ui/core/ListItemText';
import Checkbox from '#material-ui/core/Checkbox';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
// Component
import DeleteNotes from './DeleteNotes';
const NoteList = props => {
const { tasks, active } = props;
const handleActiveMain = item => {
props.activeMain('singleNote', item.title, item.content, item.date, item.id, item.done);
};
const handleToggle = item => {
if (item.done === false) {
props.doneTask(true, item.id);
} else {
props.doneTask(false, item.id);
}
};
const handleDeleteNote = id => {
props.deleteTask(id);
if (active.id == id || tasks.length === 1) {
props.activeMain('create');
}
};
const handleEditNote = item => {
props.activeMain('edit', item.title, item.content, item.date, item.id, item.done);
};
return (
<div className="list-tasks" data-test="note-list">
<List className="note-list">
{tasks.length ? (
tasks.map(item => (
<ListItem ContainerProps={{ 'data-test': 'note-item' }} key={item.id} dense button>
<ListItemIcon>
<Checkbox
edge="start"
onClick={() => handleToggle(item)}
checked={item.done === true}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': item.id, 'data-test': 'note-check' }}
/>
</ListItemIcon>
<Link href="/" as={process.env.BACKEND_URL + '/'}>
<ListItemText
onClick={() => handleActiveMain(item)}
className={item.done === true ? 'done' : ''}
id={item.id}
primary={<span data-test="note-title">{item.title}</span>}
secondary={<span data-test="note-date">{item.date}</span>}
/>
</Link>
<ListItemSecondaryAction>
<IconButton
onClick={() => handleDeleteNote(item.id)}
edge="end"
aria-label="delete"
>
<DeleteIcon data-test="note-delete" />
</IconButton>
<Link
href={`/edit?id=${item.id}`}
as={`${process.env.BACKEND_URL}/edit?id=${item.id}`}
>
<IconButton onClick={() => handleEditNote(item)} edge="end" aria-label="edit">
<EditIcon data-test="note-edit" />
</IconButton>
</Link>
</ListItemSecondaryAction>
</ListItem>
))
) : (
<p data-test="no-note-yet">No notes yet.</p>
)}
</List>
{tasks.some(item => item.done === true) === true && <DeleteNotes selectedNotes={tasks} />}
</div>
);
};
NoteList.propTypes = {
active: PropTypes.object.isRequired,
tasks: PropTypes.array.isRequired,
deleteTask: PropTypes.func.isRequired,
activeMain: PropTypes.func.isRequired,
doneTask: PropTypes.func.isRequired,
};
const mapDispatchToProps = dispatch => {
return {
deleteTask: bindActionCreators(deleteTask, dispatch),
activeMain: bindActionCreators(activeMain, dispatch),
doneTask: bindActionCreators(doneTask, dispatch),
};
};
const mapStateToProps = state => {
return {
active: state.mainReducer,
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(NoteList);
Related
https://codesandbox.io/s/large-data-array-with-redux-toolkit-forked-7tp63?file=/demo.tsx
In the given codesandbox example I am using react typescript with redux-toolkit
in codesandbox example. I am trying to bind countries with checkbox and textbox.
When I check on checkboxes it looks slow and textbox edit also feels very slow.
some time it breaks the page.
I am not sure what I am doing wrong.
You should make CountryItem it's own component and make it a pure component:
import React, { FC, useEffect } from "react";
import { createStyles, Theme, makeStyles } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import { countryList } from "./dummyData";
import { Checkbox, Grid, TextField } from "#material-ui/core";
import { useAppDispatch, useAppSelector } from "./store/hooks";
import {
setCountries,
setCountrySelected,
setCountryValue
} from "./store/slice/geography-slice";
import { Country } from "./interface/country.modal";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: "100%",
backgroundColor: theme.palette.background.paper
}
})
);
const CountryItem: FC<{ country: Country }> = ({ country }) => {
console.log('render item',country.name)
const dispatch = useAppDispatch();
const handleCheckboxChange = (
event: React.ChangeEvent<HTMLInputElement>,
country: Country
) => {
const selectedCountry = { ...country, isSelected: event.target.checked };
dispatch(setCountrySelected(selectedCountry));
};
const handleTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedCountry = { ...country, value: event.target.value };
dispatch(setCountryValue(selectedCountry));
};
return (
<ListItem button>
<Checkbox
checked={country?.isSelected ?? false}
onChange={(event) => {
handleCheckboxChange(event, country);
}}
/>
<ListItemText primary={country.name} />
<TextField value={country?.value ?? ""} onChange={handleTextChange} />
</ListItem>
);
};
const PureCountryItem = React.memo(CountryItem)
export default function SimpleList() {
const classes = useStyles();
const dispatch = useAppDispatch();
const { countries } = useAppSelector((state) => state.geography);
useEffect(() => {
dispatch(setCountries(countryList));
}, []);
return (
<div className={classes.root}>
<Grid container>
<Grid item xs={6}>
<List component="nav" aria-label="secondary mailbox folders">
{countries.map((country, index) => (
<PureCountryItem country={country} key={`CountryItem__${index}`} />
))}
</List>
</Grid>
</Grid>
</div>
);
}
I have two functions that do about the same thing only in a different array in the state
For no apparent reason only one of them works
Only the addToCart function works
I can not understand why the addToWishList function does not work
Home component
import React, { useEffect, useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
import { useHistory } from "react-router-dom";
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action";
const useStyles = makeStyles({
root: {
maxWidth: 345,
},
media: {
height: 240,
},
});
function Home({ addToCart }) {
const classes = useStyles();
const history = useHistory();
const [products, setProducts] = useState([]);
useEffect(() => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((json) => setProducts(json));
}, []);
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
<CardActionArea
onClick={() => history.push(`/product/${product.id}`)}
>
<CardMedia className={classes.media} image={product.image} />
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{product.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
component="h2"
>
{product.category}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
onClick={() => addToCart(product)}
size="small"
variant="contained"
color="primary"
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)}
size="small"
variant="contained"
color="secondary"
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)),
};
};
export default connect(null, mapDispatchToProps)(Home);
shopping-action.js
import * as actionTypes from "./shopping-types";
export const addToCart = (item) => {
return {
type: actionTypes.ADD_TO_CART,
payload: {
item: item,
},
};
};
export const removeFromCart = (itemId) => {
return {
type: actionTypes.REMOVE_FROM_CART,
payload: {
id: itemId,
},
};
};
export const adjustQty = (itemId, value) => {
return {
type: actionTypes.ADJUST_QTY,
payload: {
id: itemId,
qty: value,
},
};
};
export const addToWishList = (item) => {
console.log(item);
return {
type: actionTypes.WISH_LIST,
payload: {
item: item,
},
};
};
shopping-reducer.js
import * as actionTypes from "./shopping-types";
const INITIAL_STATE = {
cart: [],
wishList: [],
};
const shopReducer = (state = INITIAL_STATE, action) => {
console.log(action.type);
switch (action.type) {
case actionTypes.ADD_TO_CART:
const item = action.payload.item;
const isInCart = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
cart: isInCart
? state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...state.cart, { ...item, qty: 1 }],
};
case actionTypes.REMOVE_FROM_CART:
return {
...state,
cart: state.cart.filter((item) => item.id !== action.payload.item.id),
};
case actionTypes.ADJUST_QTY:
return {
...state,
cart: state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: action.payload.qty }
: item
),
};
case actionTypes.WISH_LIST:
const itemForWish = action.payload.item;
const isInWish = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
wishList: isInWish ? null : [...state.wishList, { itemForWish }],
};
default:
return state;
}
};
export default shopReducer;
shopping-types.js
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const ADJUST_QTY = "ADJUST_QTY";
export const WISH_LIST = "WISH_LIST";
Thanks for your time!
You forgot to destructure the addToWishList prop that is injected by the connect HOC, so you are calling directly the action creator and not the one wrapped in a call to dispatch.
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action"; // <-- (1) imported
function Home({ addToCart }) { // <-- (3) not destructured
...
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
...
<CardActions>
<Button
onClick={() => addToCart(product)}
...
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)} // <-- (4) non-wrapped action
...
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)), // <-- (2) injected
};
};
export default connect(null, mapDispatchToProps)(Home);
To resolve, you should destructure and use the correctly wrapped action.
function Home({ addToCart }) {
Should be
function Home({ addToCart, addToWishList }) {
I am using react +redux+typescript+firebase for a simple todo application. How to move all actions from components to store if i use react hooks(without other third party libraries, if it is possible).
Components/Todo.tsx:
import React from 'react';
import {connect} from 'react-redux';
import Switch from '#material-ui/core/Switch';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import "./Todo.scss";
import {todosRef} from "../../firebase/firebase";
const Todo = (props: any) => {
const classes = [''];
const { todo } = props;
const updateTodo = () => {
todosRef.child(todo.id).set({...todo,done:!todo.done})
}
if (todo.done) {
classes.push('_completed');
}
return (
<div className="Todo">
<Switch
edge="end" checked={todo.done} onChange={updateTodo}
inputProps={{ "aria-labelledby": "switch-list-label-bluetooth" }}
/>
<p className={classes.join(' ')}>{todo.task}</p>
<IconButton aria-label="delete" onClick={e => todosRef.child(todo.id).remove()}>
<DeleteIcon fontSize="large" />
</IconButton>
</div>
);
}
export default connect()(Todo);
components/TodoForm.tsx
import React, { useState } from "react";
import TextField from '#material-ui/core/TextField';
import {todosRef} from "../../firebase/firebase";
const TodoForm:React.FC = () => {
const [title, setTitle] = useState<string>("");
const createTodo = (e: React.FormEvent<EventTarget>) => {
e.preventDefault();
const item = {
task: title,
done: false,
};
todosRef.push(item);
setTitle("");
};
return (
<form onSubmit={createTodo}>
<TextField
style={{ width: "100%" }}
id="outlined-basic"
value={title}
onChange={(e:React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
label="Write task"
variant="outlined"
/>
</form>
);
}
export default TodoForm;
components/TodoList.tsx:
import React, {useState, useEffect} from "react";
import Todo from "../Todo/Todo";
import Divider from '#material-ui/core/Divider';
import {todosRef} from "../../firebase/firebase";
const TodoList:React.FC = () => {
const [todos,setTodos] = useState<any>([]);
useEffect(() => {
todosRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
task: items[item].task,
done: items[item].done
});
}
setTodos(newState)
});
},[])
return (
<>
{todos.map((todo: any, i: number) => (
<React.Fragment key={todo.id}>
<Todo todo={todo} />
{i<todos.length -1 && <Divider />}
</React.Fragment>
))}
</>
);
}
export default TodoList;
actions/index.ts:
export const fetchTodos = (todo:any) => {
return {
type: 'FETCH_TODOS',
payload: todo,
}
}
export const addToDo = (setTodos:any) => {
return {
type: 'ADD_TODO',
payload: setTodos,
}
}
export const updateTodo = (todo:any) => {
return {
type: 'UPDATE_TODO',
payload: todo,
}
}
export const removeTodo = (todo:any) => {
return {
type: 'REMOVE_TODO',
payload: todo,
}
}
I started describing the actions, but I don't know exactly what to write in the context of the store.
const initialState = {
todos: [],
setTodos: [],
}
export default (state = initialState, action:any) => {
switch (action.type) {
case FETCH_TODOS:
return {
...state,
todo:action.payload
};
case REMOVE_TODO:
...
default:
return state;
...
I will also be grateful if you can indicate with a couple of examples (instead of any) which types I should use, since I'm still new to typescript
I am using redux thunk with useEffect to fetch data from API. However I end up with infinity loop of fetching data from API. I put empty array as second parameter in useEffect but it still fetch data for infinite times. However can I solve the issue. Here is my code:
Here is AcademicYearList.js
import React, { useEffect, useCallback } from 'react';
import cx from 'classnames';
import { makeStyles } from '#material-ui/core/styles';
import Academic from '#material-ui/icons/CalendarToday';
import Edit from '#material-ui/icons/Edit';
import Delete from '#material-ui/icons/Delete';
import IconButton from '#material-ui/core/IconButton';
import Tooltip from '#material-ui/core/Tooltip';
import { Link } from 'react-router-dom';
import GridContainer from 'components/theme/Grid/GridContainer';
import GridItem from 'components/theme/Grid/GridItem';
import Card from 'components/theme/Card/Card';
import CardBody from 'components/theme/Card/CardBody';
import CardIcon from 'components/theme/Card/CardIcon';
import CardHeader from 'components/theme/Card/CardHeader';
import ThemeButton from 'components/theme/CustomButtons/Button';
import Table from 'components/common/Table/Table';
import LoadablePaper from 'components/common/Paper/LoadablePaper';
import LoadablePaperContent from 'components/common/Paper/LoadablePaperContent';
import { useSelector, useDispatch } from 'react-redux';
import { getTranslate } from 'react-localize-redux';
import * as ROUTES from 'variables/routeNames';
import { getAcademicYears } from 'redux/actions/academicYear';
import settings from 'settings';
import styles from 'assets/sts/jss/views/academicYear/academicYearStyle';
import * as moment from 'moment';
const useStyles = makeStyles(styles);
const AcademicYear = (props) => {
const classes = useStyles();
const dispatch = useDispatch();
const localize = useSelector((state) => state.localize);
const isLoading = useSelector(state => state.academicYear.isLoadingGet);
const academicYears = useSelector(state => state.academicYear.academicYears);
const translate = getTranslate(localize);
const fetchData = useCallback(
() => dispatch(getAcademicYears()),
[dispatch]
);
useEffect(() => {
if (academicYears.length === 0 && !isLoading) {
fetchData();
}
}, []);
return (
<LoadablePaper
rendered
loading={isLoading}
>
<LoadablePaperContent>
<GridContainer>
<GridItem xs={12}>
<Card>
<CardHeader color="gold" icon>
<CardIcon color="gold">
<Academic />
</CardIcon>
<h4 className={classes.cardIconTitle}>{translate('academicYear.title')}</h4>
</CardHeader>
<CardBody>
<Table
size='small'
tableHead={[
{ id: 'start_year', isNeedSort: false, label: translate('academicYear.year') },
{ id: 'start_date', isNeedSort: false, label: translate('academicYear.start') },
{ id: 'end_date', isNeedSort: false, label: translate('academicYear.finish') },
{ id: 'short_vacation', isNeedSort: false, label: translate('academicYear.vacation.short') },
{ id: 'long_vacation', isNeedSort: false, label: translate('academicYear.vacation.long') },
{ id: 'action', isNeedSort: false, label: '' }
]}
tableData={
academicYears.map(academic => {
const start_date = moment(academic.start_date, settings.dateFormat);
const end_date = moment(academic.end_date, settings.dateFormat);
const studyYear = `${start_date.year()}-${end_date.year()}`;
const editButton = (
<Tooltip
title={translate('common.button.edit')}
key={translate('common.button.edit')}
>
<IconButton
color='primary'
aria-label='edit'
className={classes.tableIconButton}
component={Link}
to={`${ROUTES.ACADEMIC_YEAR_EDIT_ROOT}/${academic.id}`}
>
<Edit className={classes.icon} />
</IconButton>
</Tooltip>
);
const deleteButton = (
<Tooltip
title={translate('common.button.delete')}
key={translate('common.button.delete')}
>
<IconButton
color='secondary'
aria-label='delete'
className={classes.tableIconButton}
onClick={() => {console.log('delete')}}
>
<Delete className={classes.icon} />
</IconButton>
</Tooltip>
);
return [
studyYear,
academic.start_date,
academic.end_date,
academic.short_vacation ? `${academic.short_vacation.start_date}-${academic.short_vacation.end_date}` : '',
academic.long_vacation ? `${academic.long_vacation.start_date}-${academic.long_vacation.end_date}` : '',
[editButton, deleteButton]
];
})}
customCellClasses={['center', 'center', 'center']}
customClassesForCells={[3, 4, 5]}
customHeadCellClasses={['center', 'center', 'center']}
customHeadClassesForCells={[3, 4, 5]}
count={20}
limit={10}
page={0}
/>
</CardBody>
</Card>
<Link to={ROUTES.ACADEMIC_YEAR_NEW}>
<ThemeButton
color="primary"
className={classes.allButton}
>
{translate('common.button.new')}
</ThemeButton>
</Link>
</GridItem>
</GridContainer>
</LoadablePaperContent>
</LoadablePaper>
);
};
export default AcademicYear;
Here is my action:
export const getAcademicYears = () => {
return (dispatch) => {
dispatch(getAcademicRequested());
axios.get('/get-academic-years')
.then(
res => {
dispatch(getAcademicSuccess(res.data.data));
}
)
.catch(e => {
dispatch(getAcademicFail(e));
dispatch(showErrorNotification(e.message));
});
};
};
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'}
/>