How to toggle state by unique string in react redux? - javascript

I am creating a list inside the Component file dynamically by mapping data from a JSON response with the help of thunk middleware.
I want to select elements from that list and add them to "My Favorites",
My JSON response doesn't have a unique ID but a unique string.
Reducer:
const initialstate = { isFav: false }
const reducer = (state=initialstate, action) => {
switch(action.type){
case actionTypes.TOGGLE_FAVORITE:
return {
...state,
isFav: **What to do here?**
}
default:
break;
}
}
export default reducer;
Action.js:
export const TOGGLE_FAVORITE = 'TOGGLE_FAVORITE';
export const togglefav = (url) =>{
return{
type: TOGGLE_FAVORITE,
payload: url
}
}
Component.js
this.props.dailySource.map((source,index) =>{
...
<div className={classes.star}>
<span className="fa fa-star" key={index} onClick={()=>
this.props.onToggleFavorite(source.url) }
style={{ color: (this.props.toggleFav) ? 'red' : '' }}></span>
</div>
}
}))
const mapStateToProps = state =>{
return{
dailySource: state.list.newsItem,
toggleFav: state.list.isFav
}
}
const mapDispatchToProps = dispatch =>{
return{
onToggleFavorite: (url) => dispatch (actionCreators.togglefav(url))
}
}

It will be better if you can simplify your pattern.
Instead of extracting isFav to another state tree, it would be much easier if you put it inside the objects of the list itself.
Let's see the code.
Reducer
const initialState = [];
const reducer = (state=initialState, action) => {
switch(action.type) {
case actionTypes.TOGGLE_FAVORITE:
const dailySource = [...state].map((v) => {
if (v.url === action.url) {
v.isFav = true;
}
return v;
});
return dailySource;
default:
return state;
}
}
Component
this.props.dailySource.map((source,index) =>{
<div className={classes.star}>
<span className="fa fa-star" key={index} onClick={()=>
this.props.onToggleFavorite(source.url) }
style={{ color: source.isFav ? 'red' : '' }}></span>
</div>
}
}))

Related

React-Redux, how to change state?

i have such problem: I'm making To-Do-List, and now I want to make EditMode for my tasks. But when I try to do it, it returns string not an array, and that's why I have 3 errors (map,some,filter = is not a function). So I don't know how to change state(task) and return changed array.
Some details: I'm using connect to get props.
Component's code
class Item extends React.Component {
state = {
statusChange: false,
task: ''
}
activeStatusChange = () => {
this.setState( {
statusChange: true
}
);
}
deActivateStatusChange = () => {
this.setState( {
statusChange: false
}
);
this.props.editTask(this.state.task)
}
onStatusChange = (e) => {
this.setState({
task: e.currentTarget.value
})
}
render(){
return (
<div className={s.item}>
<span onClick={this.props.editStatus} className={s.statusTask}>
{this.props.status ? <img src="https://img.icons8.com/doodle/48/000000/checkmark.png"/>
: <img src="https://img.icons8.com/emoji/48/000000/red-circle-emoji.png"/>}
</span>
{ this.state.statusChange
? <input onChange={this.onStatusChange} autoFocus={true} onBlur={this.deActivateStatusChange} value={this.state.task} />
: <span className={this.props.status === true ? s.task : s.taskFalse} onClick={this.activeStatusChange}> {this.props.task} </span>}
<span onClick={this.props.deleteTask} className={s.close}><img src="https://img.icons8.com/color/48/000000/close-window.png"/></span>
</div>
)
}
}
export default Item;
Reducer's code
import React from 'react'
import shortid from 'shortid';
const ADD_TASK = 'ADD_TASK'
const EDIT_STATUS = 'EDIT_STATUS'
const TASK_DELETE = 'TASK_DELETE'
const REMOVE_ALL_DONE = 'REMOVE_ALL_DONE'
const REMOVE_ALL_TASKS = 'REMOVE_ALL_TASKS'
const EDIT_TASK = 'EDIT_TASK'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks]
}
}
case EDIT_STATUS: {
return {
...state,
tasks: state.tasks.map(task => task.id === action.id ? {...task, status: !task.status} : task)
}
}
case TASK_DELETE: {
return {
...state,
tasks: state.tasks.filter(t => t.id !== action.id)
}
}
case REMOVE_ALL_DONE: {
return {
...state,
tasks: state.tasks.filter(t => !t.status)
}
}
case REMOVE_ALL_TASKS: {
return {
...state,
tasks: []
}
}
case EDIT_TASK: {
return {
...state,
tasks: action.task
}
}
default:
return state
}
}
export const addTask = task => ({type: 'ADD_TASK', task});
export const editStatus = id => ({type: 'EDIT_STATUS', id})
export const deleteTask = id => ({type: 'TASK_DELETE', id})
export const removeAllDone = () => ({type:'REMOVE_ALL_DONE'})
export const removeAllTasks = () => ({type: 'REMOVE_ALL_TASKS'})
export const editTask = task => ({type: 'EDIT_TASK', task})
export default mainReducer;
You should create a container that uses the methods mapDispatchToProps so you can use your actions in the component.
https://react-redux.js.org/using-react-redux/connect-mapdispatch
So lets do it. Just create a file that will be your container for that component and put the code like this:
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Item from 'wherever your component is';
import { addTask } from 'wherever your action is';
const mapStateToProps = ({ }) => ({
// Here you can pass the redu state to your component
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
// Here you pass the action to your component
addTask
}, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Item);
Then when you want to use the Item component import it from the container and it will receive in props both the action and the state that you are passing from the container file.
In you Item component you can use the action like this:
// ITem component
render() {
return (
<button onClick={this.props.addTask} />
)
}
If any doubt just let me know!

Props changes arenot reflected on component UI although the new state is shown in console in React/Redux

I'm trying to do some react/redux basics here, but have the problem that the change in state inside the state store isn't reflected in component UI. here is my code, what wrong did I made?
projectReducer.js
Here is the reducer:
const initState = {
projects: [],
};
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
const project = action.project;
state.projects.unshift(project);
return {
...state
};
case GET_PROJECTS:
state.projects = action.projects;
return {
...state
};
default:
break;
}
return state;
}
export default projectReducer
projectAction.js
Here is the action
import axios from 'axios';
export const createProjectActionCreator = project => {
return (dispatch, getState) => {
// make async call to dispatch
axios.post('http://localhost:4000/projects/create-project', project).then(result => {
dispatch({
type: 'CREATE_PROJECT',
project: result.data.project
});
}).catch(err => {
console.log(err)
});
}
}
export const getProjectsActionsCreator = () => {
return (dispatch, getState) => {
axios.get("http://localhost:4000/projects").then(result => {
dispatch({
type: 'GET_PROJECTS',
projects: result.data.projects
});
}).catch(err => {
console.log(err)
});
};
}
createProjectComponent.js
Here is compnent has create project form
import React from 'react';
import { connect } from "react-redux";
import { createProjectActionCreator } from "../../store/actions/projectActions";
class CreateProject extends React.Component {
state = {
projectData: {
title: '',
content: ''
},
createProjectErrors: []
}
handleChange = e => {
const { id, value } = e.target;
const { projectData } = this.state;
projectData[id] = value;
this.setState({projectData});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.createProject(this.state.projectData);
}
render() {
return (
<div className="container">
<form onSubmit={e => this.handleSubmit(e)} className="white">
<h5 className="grey-text text-darken-3">Create New Project</h5>
<div className="input-field">
<label htmlFor="title">Title</label>
<input type="text" id="title" onChange={e => this.handleChange(e)}/>
</div>
<div className="input-field">
<label htmlFor="content">Content</label>
<textarea className="materialize-textarea" id="content" onChange={e => this.handleChange(e)}></textarea>
</div>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Create Project</button>
</div>
</form>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
createProject: project => dispatch(createProjectActionCreator(project))
}
}
export default connect(null, mapDispatchToProps)(CreateProject)
Dashboard.js
This component act like home page which renders project list and the form of project creation
import React, { Component } from 'react';
// import Notifications from './Notifications';
import ProjectList from '../projects/PorjectList';
import { connect } from 'react-redux';
import CreateProject from '../projects/CreateProject';
import { getProjectsActionsCreator } from "../../store/actions/projectActions";
class Dashoard extends Component {
componentWillMount() {
this.props.fetchProjects();
}
render() {
return (
<div className="dashboard container">
<div className="row">
<div className="col s12 m6">
<ProjectList projects={this.props.projects} />
</div>
<div className="col s12 m6">
<CreateProject />
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.project);
return {
projects: state.project.projects
}
}
const mapDispatchToProps = dispatch => {
return {
fetchProjects: () => dispatch(getProjectsActionsCreator())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashoard)
[enter image description here][1]
The problem is in your reducer, you shouldn't mutate state (See the Redux docs on immutability).
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
const project = action.project;
state.projects.unshift(project);
return {
...state
};
case GET_PROJECTS:
state.projects = action.projects;
return {
...state
};
default:
break;
}
return state;
}
In each of your cases you are returning a referentially different copy of state, but you're mutating the original state first. Here's how you want to do it instead:
const projectReducer = (state = initState, action) => {
switch (action.type) {
case CREATE_PROJECT:
return {
...state, projects: [action.project, ...state.projects]
};
case GET_PROJECTS:
return {
...state, projects: action.projects
};
default:
return state;
}
}
Note that ...state isn't strictly necessary in this case, since projects is your only state (and you want to overwrite it), but if you add more state, you'll need to spread state to avoid overwriting any other state in the store.

Where to make a toggle action in this react redux code?

I have 2 files here:
App.js and reducer.js
I try to use React & Redux for creating onclick toggle action (ex: background color change).
Can anyone help me to where can I make toggle action in this code? (I made setTimeout action in mapDispatchToProps before and it worked but toggle action not.)
see the code:
App.js
import React, { Component } from "react";
import "./App.css";
import { connect } from "react-redux";
class App extends Component {
render() {
return (
<div>
<button
style={{
backgroundColor: this.props.backgroundColor
}}
>
hello
</button>
<button onClick={this.props.changeTheColor}>change</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
backgroundColor: state.backgroundColor
};
};
const mapDispatchToProps = dispatch => {
return {
changeTheColor: () => {
dispatch({ type: "CHANGE_COLOR" }); //I think something should change here but I have no idea how :(
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
and reducer.js
const initialState = {
backgroundColor: "red"
};
const reducer = (state = initialState, action) => {
const updatedState = { ...state };
if (action.type === "CHANGE_COLOR") {
updatedState.backgroundColor = "yellow"; // I added else/if operation there before and didn't worked :(
}
return updatedState;
};
export default reducer;
does someone has any idea(s) how to make toggle action there?
I want to change button red background color to yellow and toggle back the acton
Change code like this:
<button onClick={() => this.props.changeTheColor(this.props.backgroundColor === 'red' ? 'yellow' : 'red')}>change</button>
const mapDispatchToProps = dispatch => {
return {
changeTheColor: (value) => {
dispatch(changeColor(value));
}
};
};
const changeColor = (value) => {
return {
type: 'CHANGE_COLOR',
value
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "CHANGE_COLOR" : {
return { ...state, backgroundColor : action.value }
}
default:
return state
};

React not rerendering after action

I have an application that has a dashboard with a list of soups. Every soup has the ability to be a daily soup. So each soup has a button that if clicked, triggers an action to update my MongoDB to make the soup a daily soup. When a soup is a daily soup, it then has 3 buttons: Remove, Low, Out. If any of these buttons are clicked they trigger an action to update my MongoDB to update that particular soup. The issue I have is that when any of these buttons are clicked, it performs the action but it is not re-rendered on the screen. I have to manually refresh the page to see that it actually worked.
Note: I am using reduxThunk to immediately dispatch the action (see code below)
I have tried using
Object.assign({}, state, action.payload)
in my reducer to be sure to avoid changing the state directly.
I also tried rewriting my reducer with:
case "UPDATE_SOUP":
return {
...state,
isDaily: action.payload.isDaily,
isLow: action.payload.isLow,
isOut: action.payload.isOut
};
React Soup Component:
class Soup extends Component {
render() {
const { soup } = this.props;
return (
<div>
<div key={soup.name} className="card">
<div
className={`card-header ${
soup.isDaily ? "alert alert-primary" : null
}`}
>
{soup.isDaily ? (
<span className="badge badge-primary badge-pill">Daily Soup</span>
) : (
"Soup"
)}
</div>
<div className="card-body">
<h5 className="card-title">{soup.name}</h5>
<p className="card-text">
{soup.isLow ? (
<span className="badge badge-warning badge-pill">
This soup is marked as LOW.
</span>
) : null}
{soup.isOut ? (
<span className="badge badge-dark badge-pill">
This soup is marked as OUT.
</span>
) : null}
</p>
{soup.isDaily ? (
<div>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isDaily: false,
isLow: false,
isOut: false
})
}
className="btn btn-danger "
>
Remove
</button>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isLow: true
})
}
className="btn btn-warning"
>
Getting Low
</button>
<button
onClick={() =>
this.props.updateSoup(soup._id, {
isOut: true
})
}
className="btn btn-dark"
>
Ran Out
</button>
</div>
) : (
<button
onClick={event =>
this.props.updateSoup(soup._id, {
isDaily: true
})
}
className="btn btn-primary"
>
Make Daily
</button>
)}
</div>
</div>
</div>
);
}
}
function mapStateToProps({ soupsReducer }) {
return { soupsReducer };
}
export default connect(
mapStateToProps,
actions
)(Soup);
React SoupList Component (To show all Soups):
class SoupList extends Component {
componentDidMount() {
this.props.allSoups();
}
renderSoup() {
const { soupsReducer } = this.props;
if (soupsReducer.length > 0) {
return soupsReducer.map(soup => {
if (soup.name !== "date") {
return <Soup key={soup._id} soup={soup} />;
} else {
return null;
}
});
}
}
render() {
console.log("SoupListProps=", this.props);
return <div>{this.renderSoup()}</div>;
}
}
function mapStateToProps({ soupsReducer, dateReducer }) {
return { soupsReducer, dateReducer };
}
export default connect(
mapStateToProps,
actions
)(SoupList);
Action:
export const updateSoup = (id, update) => async dispatch => {
const res = await axios.put(`/api/allsoups/${id}`, update);
dispatch({ type: "UPDATE_SOUP", payload: res.data });
};
Reducer:
export default function(state = [], action) {
switch (action.type) {
case "FETCH_SOUPS":
return action.payload;
case "ALL_SOUPS":
return action.payload;
case "UPDATE_SOUP":
return action.payload;
default:
return state;
}
}
The issue is that you are re-writing your whole state in every action by doing
return action.payload;
You need to do something like
return { ...state, someStateKey: action.payload.data.someKey }
Where depending on the action type you pull the required data from the response and set that in your state.
If you can provide more info on the response, I can update the answer with more specific details
My thoughts are revolving around this part of your code...
export const updateSoup = (id, update) => async dispatch => {
const res = await axios.put(`/api/allsoups/${id}`, update);
dispatch({ type: "UPDATE_SOUP", payload: res.data });
};
export default function(state = [], action) {
// ...code...
case "UPDATE_SOUP":
return action.payload;
// ...code...
}
}
Try this:
Identify the souptype AND the change to your action...
dispatch({ type: "UPDATE_SOUP", payload: res.data, souptype: id, update: update });
Update the state to the souptype to your reducer...
export default function(state = [], action) {
case "UPDATE_SOUP":
const newstate = action.payload;
neswstate.soups[action.souptype] = action.isDaily ? true : false;
return newstate;
Of course, why won't this work? Simply because I'm guessing what kind of state you have and how the soups are stored in this state. There is no constructor or state definition in your code, so, you'll need to adjust what's above to match how your state is defined.

Egghead.io Redux tutorial - Lesson 17: "TypeError: Cannot read property 'map' of undefined

I am following the egghead.io tutorial on Redux. I am on Lesson 17 and getting an error that Dan Abramov isn't. The code is below.
The error I am getting is
"TypeError: Cannot read property 'map' of undefined
From my understanding, I am getting the error because when I render TodoApp it is trying to map over this.props.todos, which is empty. However he isn't getting any errors?
What am I doing wrong?
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id:action.id,
text: action.text,
completed: false
}
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state,
completed: !state.completed
};
default:
return state
}
}
const todos = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t => todo(t, action))
default:
return state;
}
};
const visbilityFilter = (
state = 'SHOW_ALL',
action
) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
const { combineReducers } = Redux
const todoApp = combineReducers({
todos,
visbilityFilter
});
const { createStore } = Redux;
const store = createStore(todos);
const { Component } = React;
let nextTodoId = 0
class TodoApp extends Component {
render() {
return (
<div>
<button onClick = {() => {
store.dispatch({
type: 'ADD_TODO',
text: 'Test',
id: nextTodoId++
})
}}>
Add Todo
</button>
<ul>
{this.props.todos.map(todo =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
</div>
)
}
}
const render = () => {
ReactDOM.render(
<TodoApp todos={store.getState().todos} />,
document.getElementById('root')
)
};
store.subscribe(render);
render()
You combined your reducers but you create your store with just the todos reducer not the combined one.
const todoApp = combineReducers({
todos,
visbilityFilter
});
const { createStore } = Redux;
const store = createStore(todos); // <--- should be `todoApp`

Categories