I am making a react app, I have a "categories" collection and a presentation layer with a button to remove a category. My redux state refresh if I reload the page otherwise is persistent. I don't understand why it is not working since seems immutable to me, plus I am using the same pattern in another component and works fine. What am I missing ?
Here the reducer:
import {DELETE_CATEGORY} from './../../actionType';
const initialState = {
singleCategory: {},
categories: []
}
export default function(state = initialState, action){
switch(action.type){
case DELETE_CATEGORY:
return {
...state,
categories: state.categories.filter (category=>category._id !== category.payload)
}
default:
return state;
}
}
By the way if I refresh the page the store is filtered correctly.
Here is my action:
// Delete Category by id
export const deleteCategory = (categoryId) => dispatch => {
console.log('disp');
axios
.delete(`/api/categories/${categoryId}`)
.then( success => {
dispatch(successMessage);
return dispatch({
type: DELETE_CATEGORY,
payload: categoryId
})
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
}),
);
};
Here is my component:
class ShowCategoriesPage extends Component {
componentDidMount(){
this.props.getCategories()
}
handleDeleteCategory = (categoryId) => {
this.props.deleteCategory(categoryId);
}
render() {
const { categories } = this.props.categories;
return (
<SidebarLayout>
{categories.map(category => (
<CategoryCard
name={category.name}
type={category.type}
id={category._id}
handleDeleteCategory={this.handleDeleteCategory}
/>
))}
<Button variant="contained" color="primary" style={{marginTop: '36px'}} >
ADD NEW CATEGORY
</Button>
</SidebarLayout>
)
}
}
and this is the component:
const SimpleCard = ({ classes, name, type, id, deleteCategory })=>{
const onDeleteCategory = (categoryId) => {
deleteCategory(categoryId);
}
return (
<Card className={classes.card}>
<CardContent>
<Typography variant="h5" component="h2">
{name}
</Typography>
<Typography className={classes.pos} color="textSecondary">
{type}
</Typography>
<Button variant="small" color="primary" style={{marginTop: '36px'}} onClick={()=>onDeleteCategory(id)}>
REMOVE
</Button>
<Button variant="small" color="primary" style={{marginTop: '36px'}} >
EDIT
</Button>
</CardContent>
</Card>
);
}
I read the official redux documentation and it advise to use splice or filter like I did, can you understand why redux state is not refreshing clicking on delete button?
I want to avoid to force refresh the state.
are you sure this line is correct
categories: state.categories.filter (category=>category._id !== category.payload)
and not
categories: state.categories.filter (category=>category._id !== action.payload)
Related
I have two buttons that show two different components when toggling them. For UX reasons (to know which component is showing) I would like to style the buttons according to if the value of the state is true or false (give them an underline and a darker color if the state is true). Is this possible in any way?
This is my GitHub repo: https://github.com/uohman/Portfolio2022
And this is the component where I handle the buttons:
`
import React, { useState } from 'react'
import ReactDOM from 'react-dom';
import { Subheading } from 'GlobalStyles';
import { FrontendProjects } from './FrontendProjects'
import { GraphicDesignProjects } from './GraphicDesignProjects';
import 'index.css'
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: 'Development', value: true },
{ label: 'Graphic design', value: false }
]);
const handleButtonsChange = () => (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading><span>Featured projects</span></Subheading>
<SpecialButton {...{ buttons, setButtons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById('root');
ReactDOM.render(<FeaturedProjects />, rootElement);
`
I've given the buttons the pseudo element :focus and that nearly solves my problem, but still as a default the buttons are the same color although it is one of the components that is showing. Thankful for suggestions on how to solve this!
You can provide a style props to any html component.
You should pass an object where attributes are camelcased.
<button
style={{ // double bracket to pass an object
backgroundColor: yourVariable ? 'red' : undefined // notice css background-color became backgroundColor
}}
>
{button.label.toUpperCase()}
</button>
You can do the same with classes
<button
className={yourVariable && "yourClass"}
>
{button.label.toUpperCase()}
</button>
You can set styles for button based on a condition.
In this use case, you already have the state button.value which can be used as a condition to set inline styles (or classes) for the mapped button.
Example:
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange({ buttons, setButtons })(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
The buttons are set to become darker when selected in the above example, but you can further customize the styles for the desired result.
More about inline styles
On a side note, it is not necessary to pass state values to the the event by onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}.
The parent component always have these values, so you do not need to pass it down to SpecialButton and pass it back.
Hope this will help!
Full example:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Subheading } from "GlobalStyles";
import { FrontendProjects } from "./FrontendProjects";
import { GraphicDesignProjects } from "./GraphicDesignProjects";
import "index.css";
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: "Development", value: true },
{ label: "Graphic design", value: false },
]);
const handleButtonsChange = (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false,
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading>
<span>Featured projects</span>
</Subheading>
<SpecialButton {...{ buttons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<FeaturedProjects />, rootElement);
I'm having some trouble figuring out how to save the state in my app. I have a component where a user adds a 'NAME' and a 'WEIGHT'. When the user clicks the submit button, it redirects them to the Home Page and the newly added name is displayed (the weight will be displayed elsewhere).
What I'm having trouble with is when I go back and add another 'NAME' and 'WEIGHT', the previous name disappears and is replaced with the new one. What I would like to happen is have the previous 'NAME' stay on the Home Page when I add a new one.
Here is my AddPage component:
const AddPage = () => {
const [name, setName] = useState('');
const [weight, setWeight] = useState(0);
const classes = useStyles();
const dispatch = useDispatch();
return (
<div>
<Header title="Add Page" />
<div className={classes.addPage}>
<div className={classes.addMovementDiv}>
<TextField
className={classes.movementName}
key="name"
label="Enter Movement Name"
InputProps= {{className: "textBoxColor"}}
variant="outlined"
onChange={event => {
const { value } = event.target;
setName(value);
}}
/>
<TextField
className={classes.movementWeight}
key="weight"
label="Enter Movement Weight"
type="number"
variant="outlined"
onChange={event => {
const { value } = event.target;
setWeight(value);
}}
InputProps= {{endAdornment: <InputAdornment position="end">lb</InputAdornment>, className: "textBoxColor"}} />
<Button
className={classes.addButton}
variant="outlined"
onClick={() => dispatch(addMovement(name, weight))}
>
<AddCircleIcon />
</Button>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
weight: state.move.weight,
}
};
const mapDispatchToProps = (dispatch) => {
return({
addMovement: (name, weight) => dispatch(addMovement(name, weight)),
})
};
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(AddPage);
Here is my HomePage component:
const HomePage = () => {
const classes = useStyles();
const name = useSelector(state => state.move.name);
const displayMovementButtons = () => {
if (name) {
return (
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
>
<div className={classes.movementName} >{name}</div>
</Button>
)
}
return <div className={classes.noMovementsMessage} >Click add button to begin</div>
}
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<div>{displayMovementButtons()}</div>
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
export default compose(withConnect)(HomePage);
Here is my reducer:
const initialState = []
const addMovementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, {name: action.name, weight: action.weight} ]
default:
return state;
}
};
export default addMovementReducer;
Here is where my store is set up:
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>,
document.querySelector('#root')
);
Any help would be appreciated!
you're misusing redux state by using it along side with hooks. it's an either or situation
may I see your store and reducer ?
Any input on how I can fix this...
My goal here is for the user to search 'Jane Smith' into the search bar, click the search button and display the cards that contain 'Jane Smith'
Currently with what I have, the user searches for 'Jane Smith' which is saved as the 'term' and once the user clicks the search button the 'fetchStudents' function fails saying it doesn't have 'term'
I'm having trouble passing a value of 'term' from SearchBarInput tag over to the SearchBarButton tag, so in the student container I can use it in the mapDispatchToProps function.
My searchBar component consists of
const SearchBar = ({ onClick, value }) => (
<SearchBarWrapper>
<div>
<FontAwesomeIcon icon="search" className="searchIcon" />
<SearchBarInput
name="searchBarInput"
placeholder=" Search for something..."
label="searchBar"
defaultValue={value}
/>
<SearchBarButton onClick={onClick}>Search</SearchBarButton>
</div>
</SearchBarWrapper>
);
export default SearchBar;
In my student container I have
const Students = ({ studentsPayload = [], onClick }) => (
<StudentsContainer>
<SearchBarContainer>
<SearchBar onClick={onClick} />
</SearchBarContainer>
{studentsPayload.length > 0 ? (
<StudentsCard data={studentsPayload} />
) : null}
</StudentsContainer>
);
const mapDispatchToProps = dispatch => ({ onClick: ({ target: { value } }) => dispatch(fetchStudents(value)) });
const mapStateToProps = ({ students: { studentsPayload } }) => ({ studentsPayload });
/**
* Connects component to redux store
*/
const enhancer = connect(
mapStateToProps,
mapDispatchToProps,
)(StudentSearch);
export default enhancer;
My fetchStudents in actions looks like this
export const fetchStudents = term => async dispatch => {
try {
const { data: { items } } = await fetchData(
`${process.env.REACT_APP_STUDENTS_URLP1}${term}${process.env.REACT_APP_STUDENTS_URLP2}`);
dispatch({ type: FETCH_STUDENTS, studentsPayload: items });
} catch (error) {
throw new Error(error);
}
};
Thanks in advance!
You could convert the SearchBar component to utilize the useState hook. Then you can pass an onChange handler to your SearchInput that takes in the event and updates the SearchBar state based off of the event.target.value. Then when you hit submit, you can pass in the input value that you have stored in state.
Thank you for the help! I solved the problem by making the following changes :)
In the Search bar component
const SearchBar = ({ onClickButton, value, onValueChange }) => (
<SearchBarWrapper>
<div>
<FontAwesomeIcon icon="search" className="searchIcon" />
<SearchBarInput
name="searchBarInput"
label="searchBar"
placeholder="Search for something"
value={value}
onChange={onValueChange}
/>
<SearchBarButton onClick={() => onClickButton(value)}>Search</SearchBarButton>
</div>
</SearchBarWrapper>
);
export default SearchBar;
In the Student container
const Students = ({ studentsPayload = [], onClickButton }) => {
const [value, setValue] = useState('');
const handleChange = ({ target: { value } }) => setValue(value);
return (
<StudentsContainer>
<SearchBarContainer>
<SearchBar
onClickButton={onClickButton}
onValueChange={handleChange}
value={value}
/>
</SearchBarContainer>
{studentsPayload.length > 0 ? (
<StudentsCard data={studentsPayload} />
) : null}
</StudentsContainer>
);
};
const mapDispatchToProps = dispatch => ({ onClickButton: value => dispatch(fetchStudents(value)) });
const mapStateToProps = ({ students: { studentsPayload } }) => ({ studentsPayload });
/**
* Connects component to redux store
*/
const enhancer = connect(
mapStateToProps,
mapDispatchToProps,
)(Students);
export default enhancer;
I'm loading it like that:
const store = createStore(
AppReducer,
composeEnhancers(applyMiddleware(createDebounce(), thunkMiddleware,
websocketMiddleware)),
);
I've got the following action:
export const toggleCardLayersMenu = (index, value) => ({
type: "CARD:TOGGLE_LAYERS_MENU",
index: index,
value: value,
});
Which I dispatch something to open a menu and something to close it.
Now, the chrome extension display the action with a small delay if the action is used to open the menu, and won't display the action at all if it's used to close the menu.
It occurs only with that specific action. What are the possible causes, and what can be done?
Edit:
This is where I dispatch the action:
...
<IconButton
onClick={ e => dispatches.toggleCardLayersMenu(e.currentTarget) }
><LayersIcon />
</IconButton>
<Menu
anchorEl={ card.menuBtnElem }
open={ card.isMenuOpen }
onRequestClose={ () => dispatches.toggleCardLayersMenu(null) }
style={ styles.menu }
>
...
This is the dispatchToProps():
const mapDispatchToProps = (dispatch, ownProps) => ({ dispatches: {
updateCardLayers(value) {
dispatch(displayActions.updateCardLayers(ownProps.index, value));
},
toggleCardLayersMenu(value) {
dispatch(displayActions.toggleCardLayersMenu(ownProps.index, value));
},
...
I had designed the action and reducer in below:
// Action Types
const PUSH_BREADCRUMB = 'PUSH_BREADCRUMB';
const POP_BREADCRUMB = 'POP_BREADCRUMB';
// ActionCreator
const pushBreadcrumb = (payload: { text, link }) => ({
type: PUSH_BREADCRUMB,
payload
});
const popBreadcrumb = () => ({ type: PUSH_BREADCRUMB });
// Reducer
const initState = [
{ text: 'Home', link: '/' }
];
const breadcumbsReducer = (state = initState, action) => {
switch (action.type) {
case PUSH_BREADCRUMB:
return [...state, action.payload];
case POP_BREADCRUMB:
return state.slice(0, state.length - 1);
default:
return state
};
};
Below is my component and router:
//...import some components
// ...connect and map breadcrumbs
const Header = ({ breadcrumbs }) => {
return (
<ul>
{breadcrumbs.map({ item, link } =>
<li key={link}><Link to={link}>{text}</Link></li>
)}
</ul>
);
};
ReactDOM.render(
<Provider>
<Router>
<AppContainer>
<Header />
<Route exact path='/orders' component={OrderListContainer} />
<Route path='/orders/:id' component={OrderDetailContainer} />
</AppContainer>
</Router>
</Provider>,
document.getElementById('root')
);
Then:
Dispatch pushBreadcrumb in OrderListContainer#componenetWillMount and OrderDetailContainer#componenetWillMount
Dispatch popBreadcrumb in OrderListContainer#componentWillUnmount and OrderDetailContainer#componentWillUnmount.
The excepted breadcrumbs is Home > Orders > Detail;
But when i stay in /orders/:id and refresh page, it will be Home > Detail.
Is there better way to implement breadcrumbs with redux?
That happens because state is reset after you refresh. You can try to implement your solution with persistent store - this will keep selected state keys to browser localStorage.