I am "new to react". I am working on a project where I am creating three buttons named as India, China, Russia. On button click, text of paragraph changes.
For this, I have created 4 Presentational Components, 3 actions, 1 reducer and extra reducer for initial state.
I am trying to send text to paragraph, from store to Presentational Component via connect(). However, it's not working.
My code is as following:
index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';
import App from './components/App';
const store = createStore(rootReducer);
console.log(store.getState());
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
actions/index.js
export const india = text => ({
type: 'INDIA',
text
});
export const china = text => ({
type: 'CHINA',
text
});
export const russia = text => ({
type: 'RUSSIA',
text
});
reducers/country.js
const initialState = {
text: 'ABC',
isIClicked: false,
isCClicked: false,
isRClicked: false
};
const country = (state = initialState, action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false
};
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false
};
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true
};
default:
return state;
}
};
export default country;
components/IndiaBtn.js
import React from 'react';
const IndiaBtn = ({ isIClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isIClicked ? 'white' : 'black',
backgroundColor: isIClicked ? 'blue' : 'white'
}}
>
India
</button>
);
};
export default IndiaBtn;
components/ChinaBtn.js
import React from 'react';
const ChinaBtn = ({ isCClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isCClicked ? 'white' : 'black',
backgroundColor: isCClicked ? 'blue' : 'white'
}}
>
China
</button>
);
};
export default ChinaBtn;
components/RussiaBtn.js
import React from 'react';
const RussiaBtn = ({ isRClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isRClicked ? 'white' : 'black',
backgroundColor: isRClicked ? 'blue' : 'white'
}}
>
Russia
</button>
);
};
export default RussiaBtn;
components/display.js
import React from 'react';
const display = ({ text }) => {
return <div style={{ padding: '16px' }}>{text}</div>;
};
export default display;
components/App.js
import React from 'react';
import IndiaBtnContainer from '../containers/IndiaBtnContainer';
import ChinaBtnContainer from '../containers/ChinaBtnContainer';
import RussiaBtnContainer from '../containers/RussiaBtnContainer';
import DisplayContainer from '../containers/DisplayContainer';
const App = () => {
return (
<div>
<div>
<span><IndiaBtnContainer /></span>
<span><ChinaBtnContainer /></span>
<span><RussiaBtnContainer /></span>
</div>
<div>
<DisplayContainer />
</div>
</div>
);
};
export default App;
containers/IndiaBtnContainer.js
import { connect } from 'react-redux';
import IndiaBtn from '../components/IndiaBtn';
import { india } from '../actions';
const mapStateToProps = state => ({
isIClicked: state.isIClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(india('india'))
});
export default connect(mapStateToProps, mapDispatchToProps)(IndiaBtn);
containers/ChinaBtnContainer.js
import { connect } from 'react-redux';
import ChinaBtn from '../components/ChinaBtn';
import { china } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(china('china'))
});
export default connect(mapStateToProps, mapDispatchToProps)(ChinaBtn);
containers/RussiaBtnContainer.js
import { connect } from 'react-redux';
import RussiaBtn from '../components/RussiaBtn';
import { russia } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(russia('russia'))
});
export default connect(mapStateToProps, mapDispatchToProps)(RussiaBtn);
containers/DisplayContainer.js
import { connect } from 'react-redux';
import display from '../components/display';
const mapStateToProps = state => ({
text: state.text
});
export default connect(mapStateToProps)(display);
Note:
Sorry, for long code. But, I thought it is necessary to understand problem
Focus on Container Components, connect, mapStateToProps, mapDispatchToProps. According to me, problem must be there.
Your reducer returns an array and hence mapStateToProps isn't giving you right values since you expect the state to be an object, what you need is
const initialState = {
text: '',
isIClicked: false,
isCClicked: false,
isRClicked: false,
}
const country = (state=initialState,action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false,
}
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false,
}
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true,
}
default:
return state
}
export default country
Related
I have a basic app here and I'm trying to config React Context properly but it isn't working. My goal is to render PlayScreen with the content of currentStage inside the React Context. Game changes the context but App keeps rendering PlayScreen with the "welcome" string, instead of "won" or "lost".
Also, I know that gameContext.js is for autocompletion but I added "welcome" there to have a first default state. Somehow I couldn't find a way to set up that very first "welcome" context when App is rendered for the first time.
I tried feeding PlayScreen with the context itself and didn't work, and now I tried setting a state with it but it doesn't work either (even when using useEffect and having the context as a dependency).
So I have two questions, what am I doing wrong? and, my way to set up the "welcome" default state is wrong? If so, how can I do it? Thanks.
gameContext.js
import React from 'react';
const GameContext = React.createContext({
currentStage: 'welcome',
playerStage: (stage) => {},
});
export default GameContext;
GameProvider.jsx
import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import GameContext from './gameContext';
const defaultState = {
currentStage: '',
};
const gameReducer = (state, action) => {
if (action.type === 'STAGE') {
return {
currentStage: action.playerStage,
};
}
return defaultState;
};
const GameProvider = ({ children }) => {
const [gameState, dispatchGameAction] = useReducer(gameReducer, defaultState);
const playerStageHandler = (playerStage) => {
dispatchGameAction({
type: 'STAGE',
playerStage,
});
};
const gameContext = useMemo(
() => ({
currentStage: gameState.currentStage,
playerStage: playerStageHandler,
}),
[gameState.currentStage]
);
return (
<GameContext.Provider value={gameContext}>{children}</GameContext.Provider>
);
};
GameProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default GameProvider;
App.jsx
import React, { useContext, useState, useEffect } from 'react';
import GameProvider from './store/GameProvider';
import GameContext from './store/gameContext';
import PlayScreen from './components/PlayScreen';
import Settings from './components/Settings';
import Game from './components/Game';
const App = () => {
const gameContext = useContext(GameContext);
const [stage, setStage] = useState(gameContext.currentStage);
useEffect(() => {
setStage(gameContext.currentStage);
}, [gameContext]);
const [currentScreen, setCurrentScreen] = useState({
playScreen: true,
settings: false,
game: false,
});
const changeScreenHandler = (newScreen) => {
switch (newScreen) {
case 'playScreen':
setCurrentScreen({
playScreen: true,
settings: false,
game: false,
});
break;
case 'settings':
setCurrentScreen({
playScreen: false,
settings: true,
game: false,
});
break;
case 'game':
setCurrentScreen({
playScreen: false,
settings: false,
game: true,
});
break;
default:
break;
}
};
return (
<GameProvider>
{currentScreen.playScreen && (
<PlayScreen stage={stage} onChangeScreen={changeScreenHandler} />
)}
{currentScreen.settings && (
<Settings onChangeScreen={changeScreenHandler} />
)}
{currentScreen.game && <Game onChangeScreen={changeScreenHandler} />}
</GameProvider>
);
};
export default App;
PlayScreen.jsx
import PropTypes from 'prop-types';
const PlayScreen = ({ stage, onChangeScreen }) => {
const clickHandler = () => {
onChangeScreen('settings');
};
return (
<div>
<h1>{stage}</h1>
<button type="button" onClick={clickHandler}>
Go
</button>
</div>
);
};
PlayScreen.propTypes = {
stage: PropTypes.string.isRequired,
onChangeScreen: PropTypes.func.isRequired,
};
export default PlayScreen;
Game.jsx
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import GameContext from '../store/gameContext';
const Game = ({ onChangeScreen }) => {
const gameContext = useContext(GameContext);
const wonHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('won');
};
const lostHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('lost');
};
return (
<div>
<h1>GAME RUNNING</h1>
<button type="button" onClick={wonHandler}>
won
</button>
<button type="button" onClick={lostHandler}>
lost
</button>
</div>
);
};
Game.propTypes = {
onChangeScreen: PropTypes.func.isRequired,
};
export default Game;
You are consuming the GameContext above the GameProvider. The context shouldn't be available because App isn't being provided the context by the GameProvider.
Try moving everything underneath GameProvider into its own component and consume the context there.
Whenever I call a function from the reducer, it gets called once the first time, and then twice every other time.
Here's the code:
reducer.js:
import data from './data'
export const initialState = {
notes: data,
filter: '',
};
export const setFilter = filter => ({ type: 'setFilter', filter });
export const createNote = id => ({ type: 'createNote', id })
export const deleteNote = note => ({ type: 'deleteNote', note })
export const reducer = (state, action) => {
switch (action.type) {
case 'setFilter':
return { ...state, filter: action.filter };
case 'createNote':
console.count('Create note fired')
state.notes.push({
id: action.id,
tags: [],
content: ""
})
return { ...state }
case 'deleteNote':
return {
...state,
notes: state.notes.filter((note) => note.id !== action.note.id)
}
default: return state;
}
};
The component that calls the delete method:
import React from 'react'
import PropTypes from 'prop-types';
import { deleteNote } from "../../state/reducer";
import { useStateValue } from "../../state/StateContext";
import './Body.css'
import { Card, Badge } from 'react-bootstrap'
const Body = ({ notes }) => {
let [state, dispatch] = useStateValue();
return (
<div className="Body">
{
notes.map(note =>
<Card key={note.id} className="Card">
<Card.Body className="CardText HideScrollbar">
<Card.Text>{note.content}</Card.Text>
</Card.Body>
<Card.Footer>
{note.tags.map(tag =>
<Badge variant="primary">
{tag} </Badge>)}
</Card.Footer>
<div className="DeleteButton" onClick={() => dispatch(deleteNote(note))}>
<svg className="svg-icon" viewBox="0 0 20 20">
<path d="M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z"></path>
</svg>
</div>
</Card>
)
}
</div>
)
}
Body.propTypes = {
notes: PropTypes.arrayOf(PropTypes.object),
}
export default Body
Any kind of help would be really helpful, please tell me if there's any file missing or if I implemented the reducer in the wrong way, what I did was mostly following notes from a friend's University professor
make seperate action file. And get that action from redux through mapDispatchToProps in your component , where you want to dispatch that action.
const mapDispatchToProps = {
setProfileDialog: ProfileAction.setProfileDialog,
}
The issue is that reducers must be pure. When react is in 'strict-mode' it will fire reducers twice to ensure that the result is the same both times. Mutating the original state will cause unwanted side effects.
Changing:
case 'createNote':
console.count('Create note fired')
state.notes.push({
id: action.id,
tags: [],
content: ""
})
return { ...state }
To:
case 'createNote':
const notes = [
...state.notes,
{
id: action.id,
tags: [],
content: "",
}
]
return {...state, notes}
Should fix your example.
Problem: I don't know how to organise my state to easily make changes to it. It is retrieved as an array of objects. it is mapped in components but making updates to individual elements and then figuring out how to update these in the database.
The application set up: React Redux Saga Node Express Postgres
fetch call retrieves an array of objects (user details), this is added to a state object and then this array is used to map a component with 4 inputs. I want to be able to change any element of any object and have it update to the state. I would then add a fetch to update any changes to the original data to the database. I am looking for information such as websites, books, courses etc that would help to build a foundation so I could solve this issue.
for reference below are sections of my code;
data
userData: Array(4)
0: {firstname: "shane", surname: "smith", email: "shanesmith#gmail.com", auth: false}
1: {firstname: "Sahne", surname: "Smith", email: "shane#gmail.com", auth: false}
etc....
Reducer
FETCH_ADMIN_PAGE_START,
FETCH_ADMIN_PAGE_SUCCESS,
FETCH_ADMIN_PAGE_FAILURE,
UPDATE_ADMIN_USER_DATA,
} from "../types/types";
// import utility functions
const INITIAL_STATE = {
isFetching: false,
userData: [],
errorMessage: null,
};
const adminReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_ADMIN_PAGE_START:
return {
...state,
isFetching: true,
};
case FETCH_ADMIN_PAGE_SUCCESS:
return {
...state,
isFetching: false,
userData: action.payload.user[0],
};
case FETCH_ADMIN_PAGE_FAILURE:
return {
...state,
errorMessage: action.payload,
};
case UPDATE_ADMIN_USER_DATA:
return {
...state,
userData: [haven't found a silution],
};
default:
return state;
}
};
export default adminReducer;
Action
import {
FETCH_ADMIN_PAGE_START,
FETCH_ADMIN_PAGE_SUCCESS,
FETCH_ADMIN_PAGE_FAILURE,
UPDATE_ADMIN_USER_DATA,
} from "../types/types";
export const fetchAdminPageStart = () => ({
type: FETCH_ADMIN_PAGE_START,
});
export const fetchAdminPageSuccess = (data) => ({
type: FETCH_ADMIN_PAGE_SUCCESS,
payload: data,
});
export const fetchAdminPageFailure = (errorMessage) => ({
type: FETCH_ADMIN_PAGE_FAILURE,
payload: errorMessage,
});
export const updataAdminUserData = (data) => ({
type: UPDATE_ADMIN_USER_DATA,
payload: data,
});
Component
import * as React from "react";
import { useState, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import PropTypes from "prop-types";
// Material Ui Componnents
import AppBar from "#material-ui/core/AppBar";
import Tab from "#material-ui/core/Tab";
//Components
import UserDetailsComponent from "../../components/container-withheader/users-details.component";
// Styled Components
import { StyledTabs } from "./admin-page.styles";
// Selectors
import { selectAdminPageUserData } from "../../redux/admin-page/admin-page.selectors";
// Actions
import { updataAdminUserData } from "../../redux/admin-page/admin-page.actions";
const AdminPageComponent = ({ userDetails, updataAdminUserData }) => {
useEffect(() => {}, []);
// const [userData, setUserData] = useState({ users: userDetailData });
const [selectedTab, setSelectedTab] = useState(0);
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
};
const handleChangeData = (event) => {
console.log(event.target.name);
return updataAdminUserData(event.target);
};
const handleToggleChange = (event) => {
console.log(event.target);
return updataAdminUserData(event.target);
};
const tabArray = ["Users", "Options"];
return (
<div>
<AppBar position='static' color='default'>
<StyledTabs
value={selectedTab}
onChange={handleTabChange}
variant='scrollable'
scrollButtons='auto'
aria-label='scrollable auto tabs example'
>
{tabArray.map((tab, i) => (
<Tab label={tab} key={i} />
))}
</StyledTabs>
</AppBar>
{selectedTab === 0 && (
<UserDetailsComponent
userData={userDetails}
handleChangeData={handleChangeData}
handleToggleChange={handleToggleChange}
/>
)}
</div>
);
};
AdminPageComponent.propTypes = {
userDetails: PropTypes.object,
updataAdminUserData: PropTypes.func,
};
const mapStateToProps = createStructuredSelector({
userDetails: selectAdminPageUserData,
});
const mapDispatchToProps = (dispatch) => ({
updataAdminUserData: (data) => dispatch(updataAdminUserData(data)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AdminPageComponent);
Component Child of above
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Switch from "#material-ui/core/Switch";
import PropTypes from "prop-types";
// Styled Components
import {
StyledToolBar,
GridContainer,
RightElement,
StyledTextField,
StyledSwitch,
} from "./users-details.styles";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
const UserDetailsComponent = ({
userData,
handleChangeData,
handleToggleChange,
}) => {
const classes = useStyles();
const [edit, setEdit] = useState({
editUser: true,
});
const handleEditChange = (event) => {
setEdit({ ...edit, [event.target.name]: event.target.checked });
};
return (
<div className={classes.root}>
<br></br>
<StyledToolBar elevation={3}>
<GridContainer>
<RightElement>
<FormControlLabel
control={
<Switch
checked={edit.editUser}
onChange={handleEditChange}
name='editUser'
/>
}
label='Edit User'
/>
</RightElement>
</GridContainer>
</StyledToolBar>
<br></br>
<Paper styles={{ paddingTop: "2rem" }} elevation={3}>
<form className={classes.root} noValidate autoComplete='on'>
{userData.map((user, i) => (
<div key={i}>
<StyledTextField
name={Number(i)}
title={"Enter first name here"}
alt={"firstname"}
id={`firstname`}
input={`firstname`}
onChange={handleChangeData}
disabled={edit.editUser}
label='First Name'
value={user.firstname}
variant='outlined'
/>
<StyledTextField
name={Number(i)}
id={"surname"}
disabled={edit.editUser}
label='Surame'
value={user.surname}
variant='outlined'
/>
<StyledTextField
name={Number(i)}
id={"email"}
disabled={edit.editUser}
label='Email'
value={user.email}
variant='outlined'
/>
<React.Fragment>
<FormControlLabel
control={
<StyledSwitch
disabled={edit.editUser}
checked={user.auth}
onChange={handleToggleChange}
name={Number(i)}
id={"auth"}
/>
}
label='Has Administration Access'
/>
</React.Fragment>
</div>
))}
</form>
</Paper>
</div>
);
};
export default UserDetailsComponent;
As you can see I need to also find a better way to pass information in the event to differentiate each pice of state I wish to modify.
I don't know if I should modify the array into another type of object, how do I then map the users if I do so. What are best practices for this issue.
Ultimately I just want to learn or be pointed in the direction of information that would help me move past this problem. Any assistance if greatly appreciated.
Thank you in advance.
Solved - converted to object, used multiple values in name then split into array.
I have created action and reducers for my application. I am trying to create a new todo and want to save it in state using redux.
action/index.js
let taskID = 0;
export const addTodo = text => {
return { type: "ADD_TODO", text, id: taskID++ };
};
reducers/todos.js
const todo = (state = {}, action) => {
switch (action.type) {
case "ADD_TODO":
return {
id: action.id,
text: action.text,
status: false
};
default:
return state;
}
};
export default todo;
reducers/index.js
import { combineReducers } from "redux";
import todos from "./todos";
const todoApp = combineReducers({ todo });
export default todoApp;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";
import { Provider } from "react-redux";
import { createStore } from "redux";
import todoApp from "./reducers/todos";
let store = createStore(todoApp);
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById("root")
);
registerServiceWorker();
App.js
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import AppBar from "material-ui/AppBar";
import FloatingActionButton from "material-ui/FloatingActionButton";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import * as strings from "./Strings";
import * as colors from "./Colors";
import styles from "./Styles";
import ContentAdd from "material-ui/svg-icons/content/add";
import Dialog from "material-ui/Dialog";
import FlatButton from "material-ui/FlatButton";
import * as injectTapEventPlugin from "react-tap-event-plugin";
import TextField from "material-ui/TextField";
import { List, ListItem } from "material-ui/List";
import { connect } from "react";
import { addTodo } from "./actions/index";
const AppBarTest = () =>
<AppBar
title={strings.app_name}
iconClassNameRight="muidocs-icon-navigation-expand-more"
style={{ backgroundColor: colors.blue_color }}
/>;
class App extends Component {
constructor(props) {
injectTapEventPlugin();
super(props);
this.state = {
open: false,
todos: [],
notetext: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleOpen = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleCreateNote = () => {
let todos = [...this.state.todos];
todos.push({
id: todos.length,
text: this.state.notetext,
completed: false
});
this.setState({ todos: todos }, () => {
// setState is async, so this is callback
});
this.handleClose();
};
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
_renderTodos() {
return this.state.todos.map(event => {
return (
<ListItem
primaryText={event.text}
key={event.id}
style={{ width: "100%", textAlign: "center" }}
onTouchTap={this._handleListItemClick.bind(this, event)}
/>
);
});
}
_handleListItemClick(item) {
console.log(item);
}
render() {
return (
<MuiThemeProvider>
<div>
<AppBarTest />
<FloatingActionButton
style={styles.fab}
backgroundColor={colors.blue_color}
onTouchTap={this.handleOpen}
>
<ContentAdd />
</FloatingActionButton>
<Dialog
open={this.state.open}
onRequestClose={this.handleClose}
title={strings.dialog_create_note_title}
>
<TextField
name="notetext"
hintText="Note"
style={{ width: "48%", float: "left", height: 48 }}
defaultValue={this.state.noteVal}
onChange={this.handleChange}
onKeyPress={ev => {
if (ev.key === "Enter") {
this.handleCreateNote();
ev.preventDefault();
}
}}
/>
<div
style={{
width: "4%",
height: "1",
float: "left",
visibility: "hidden"
}}
/>
<FlatButton
label={strings.create_note}
style={{ width: "48%", height: 48, float: "left" }}
onTouchTap={this.handleCreateNote}
/>
</Dialog>
<List style={{ margin: 8 }}>
{this._renderTodos()}
</List>
</div>
</MuiThemeProvider>
);
}
}
export default App;
I want to save new todo inside handleCreateNote function, I am not sure how to use store, dispatch here to save it in state. Can anyone help me ?
Change
export default App;
To
function mapStateToProps(state) {
return {
todo: todo
}
}
export default connect(mapStateToProps, actions)(App)
You should also import all the actions using
import * as actions from './action/index';
After all these modify your this function as follows:-
handleCreateNote = () => {
let todos = [...this.state.todos];
let newTodo = {
id: todos.length,
text: this.state.notetext,
completed: false
};
todos.push(newTodo);
this.setState({ todos: todos }, () => {
// setState is async, so this is callback
});
this.props.addTodo(this.state.notetext);
this.handleClose();
};
Also your logic for adding todos is incorrect.
So your action creator should be something like this
let taskID = 0;
export const addTodo = text => {
return {
type: "ADD_TODO",
text: text,
id: taskId++
};
};
Now the reducer also needs to change, so that should be something like this:-
const todo = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
let newTodo = {
id: action.id,
text: action.text,
status: false
};
return [...state, newTodo]
default:
return state;
}
};
export default todo;
I hope this helps.Not the best of implementations, but will solve your issue.
I have reducer which hides and display a modal component;
import {SHOW_MODAL, HIDE_MODAL } from '../constants/ActionTypes'
import React from 'react';
import {connect} from 'react-redux';
import * as actions from '../actions';
const initialState = {
modalType: null,
modalProps: {}
}
export default function modal(state = initialState, action) {
switch (action.type) {
case 'SHOW_MODAL':
return {
modalType: action.modalType,
modalProps: action.modalProps
}
case 'HIDE_MODAL':
return initialState
default:
return state
}
}
Action that shows modal:
export const showAchievement = (modalProps) => ({ type: types.SHOW_ACHIEVEMENT, ...modalProps })
How can I send a function to my modal component that will dispatch an action 'HIDE_MODAL' :
openAchievementModal(){
this.props.showAchievement({
type: 'SHOW_MODAL',
modalType: 'ADD_ACHIEVEMENT',
modalProps: {
dayId: this.props.day.id,
onChange: this.props.addAchievement
}
})
}
I am using react-modal as wrapper for my modals which are mounted at the top of components:
import React, { Component } from 'react';
import Modal from 'react-modal';
import ModalWrapper from './ModalWrapper.js';
import Select from 'react-select';
export default class AddAchievementModal extends Component {
constructor() {
super();
this.logChange = this.logChange.bind(this);
}
logChange(e) {
this.props.onChange(this.props.dayId, e.label)
this.props.onClose()
}
render() {
console.log(this.props)
var options = [
{ value: 1, label: 'Play Music' },
{ value: 2, label: 'Football' }
];
return (
<span >
<ModalWrapper
onRequestClose={this.props.closeModal}
style={this.props.customStyles}
contentLabel="Modal" >
<h2>Add Achievement</h2>
<Select
name="form-field-name"
value="one"
options={options}
onChange={this.logChange}
/>
</ModalWrapper>
</span>
)
}
}
React modal wrapper:
import Modal from 'react-modal'
import React, { Component } from 'react'
const customStyles = {
content : {
top : '50%',
left : '50%',
right : '50%',
bottom : '30%',
marginRight : '-50%',
transform : 'translate(-50%, -50%)',
borderRadius : '10px',
border : '3px solid #ccc'
},
};
class ModalWrapper extends Component {
constructor() {
super();
this.state = {
modalIsOpen: true
};
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<Modal style={customStyles} isOpen={this.state.modalIsOpen} contentLabel="Model Wrapper" closeModal={this.props.closeModal}>
<header>
<button onClick={this.closeModal}>Close</button>
</header>
{this.props.children}
</Modal>
);
}
}
export default ModalWrapper
To close the modal, do I need to modelIsOpen to false, aswell as dispatching an action HIDE_MODAL ?
The actions should be plain objects; you are mixing them with functions.
What you should instead do is to only pass the callback functions which dispatch actions to the modal component.
You would do this in the smart component. See smart-and-dumb-components article.
onOpenAchievementModal() {
this.props.showAchievement({
...
})
}
onAddAchievementModal(achievement) {
this.props.addAchievement({
...
achievement,
})
}
and then render the modal, something like:
<Modal onOpen={this.onOpenAchievementModal} onClickAchievement={this.onAddAchievementModal}/>