I would like ask you about passing object to Redux.
Below is my code.
// src/actions/writingType.js
export const write = () => ({
type: 'WRITE',
})
export const update = (obj) => ({
type: 'UPDATE',
obj
})
// src/reducers/writingType.js
const initialState = {
writingType: "WRITE",
obj: null
}
const writingTypeReducer = (state = initialState, action) => {
console.log('\n inside writingTypeReducer');
console.log(action);
switch (action.type) {
case 'WRITE':
return {
...state,
writingType: 'WRITE'
};
case 'UPDATE':
return {
...state,
writingType: 'UPDATE',
obj: action.obj
};
default:
return state;
}
}
export default writingTypeReducer;
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
I used update(props.contentObj); in Contentview.js to pass props.contentObj to Redux and update obj of initialState in src/reducers/writingType.js. But obj of initialState hasn't changed and existed as null.
How should I change code?
Thank you.
use props.update to call in the main file
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
props.update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
Please use the above code
Related
I have problem with initialization of search and filter for ma countries api. I have stuck with problem how to implement code. I will be gratefull for any help or some advices how to do this. My files look like this:
Store.js
import { applyMiddleware, combineReducers, createStore, compose } from 'redux'
import getCountries from './getCountries'
const initialState = {
variable: {
data: [],
},
filters: {
searchPhrase: '',
continent: ''
}
}
const reducers = {
variable: getCountries, // variablesReducer
}
const storeReducer = combineReducers(reducers)
const store = createStore(
storeReducer,
initialState,
compose(applyMiddleware(), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // przed deployem usunac
)
export default store
SearchPhraseRedux.js
//SearchEngine
export const getData = ({variable}, filters) => {
if(!filters.searchPhrase){
return variable.data
}
//Logic
//export const getCountryByName = ({variable}, name) =>
//variable.data.find(e=>e.name.common === name)
}
const reducerName = 'variable'
const createActionName = (name) => `app/${reducerName}/${name}`
export const SET_DATA = createActionName("SET_DATA")
export const setData = payload => ({payload, type: SET_DATA})
export default function reducer(statePart=[], action={}){
switch(action.type) {
case SET_DATA:
return {...statePart, data: action.payload}
default:
return statePart
}
}
countryContainer.js
import { connect } from "react-redux"
import { getData, setData} from '../../redux/getCountries'
import Countries from "./countries"
const mapStateToProps = (state) => ({
data: getData(state),
})
const mapDispatchToProps = dispatch =>({
setData: (value) => dispatch(setData(value)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Countries)
countries.js
import React from 'react'
import { useEffect} from 'react'
import Axios from 'axios'
import '../../../src/index.css'
import {Link} from 'react-router-dom'
const Countries = ({data,setData}) => {
const url = 'https://restcountries.com/v3.1/all'
useEffect(() => {
Axios.get(url)
.then((rest) =>{
setData(rest.data)
})
},[url, setData])
return (
<div>
<section className='grid'>
{data.map((country) => {
const {population, region, capital} = country
const {png} = country.flags
const {official, common} = country.name
return <article key={common}>
<div>
<Link classsName='link' to={`/countries/${common}`}>
<img src ={png} alt={official}/>
</Link>
<div className='details'>
<h3>{common}</h3>
<h4>Population: <span>{population}</span></h4>
<h4>Region: <span>{region}</span></h4>
<h4>Capital: <span>{capital}</span></h4>
</div>
</div>
</article>
})}
</section>
</div>
)
}
export default Countries
I have tried to get One country like this:
export const getCountryByName = ({variable}, name) => variable.data.find(e=>e.name.common === name)
but,by extending this code, it is difficult for me to make search and filter
I'm following a tutorial on learning Redux and I'm stuck at this point where state that should have an image url is returned as undefined.
Image is successfully saved in firbase storage and dispatched but when I try to get the url on new route with useSelector it is undefined.
import React, {useEffect} from "react";
import {useSelector} from "react-redux";
import {useHistory} from "react-router-dom";
import "./ChatView.css";
import {selectSelectedImage} from "./features/appSlice";
function ChatView() {
const selectedImage = useSelector(selectSelectedImage);
const history = useHistory();
useEffect(() => {
if(!selectedImage) {
exit();
}
}, [selectedImage])
const exit = () => {
history.replace('/chats');
}
console.log(selectedImage)
return (
<div className="chatView">
<img src={selectedImage} onClick={exit} alt="" />
</div>
)
}
export default ChatView
reducer created for chat (slice):
import { createSlice } from '#reduxjs/toolkit';
export const appSlice = createSlice({
name: 'app',
initialState: {
user:null,
selectedImage:null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
selectImage:(state, action) => {
state.selectedImage = action.payload
},
resetImage:(state) => {
state.selectedImage = null
}
},
});
export const { login, logout, selectImage, resetImage} = appSlice.actions;
export const selectUser = (state) => state.app.user;
export const selectSelectedImage = (state) => state.app.selectImage;
export default appSlice.reducer;
and code for dispatching that imageURL which when i console.log it gives the correct url:
import {Avatar} from "#material-ui/core";
import StopRoundedIcon from "#material-ui/icons/StopRounded"
import "./Chat.css";
import ReactTimeago from "react-timeago";
import {selectImage} from "./features/appSlice";
import {useDispatch} from "react-redux";
import {db} from "./firebase";
import {useHistory} from "react-router-dom";
function Chat({id, username, timestamp, read, imageUrl, profilePic}) {
const dispatch = useDispatch();
const history = useHistory();
const open = () => {
if(!read) {
dispatch(selectImage(imageUrl));
db.collection('posts').doc(id).set({read:true,}, {merge:true});
history.push('/chats/view');
}
};
return (
<div onClick={open} className="chat">
<Avatar className="chat__avatar" src={profilePic} />
<div className="chat__info">
<h4>{username}</h4>
<p>Tap to view - <ReactTimeago date={new Date(timestamp?.toDate()).toUTCString()} /></p>
</div>
{!read && <StopRoundedIcon className="chat__readIcon" />}
</div>
)
}
export default Chat
Your selector is trying to access the wrong field.
export const selectSelectedImage = (state) => state.app.selectImage;
Should actually be:
export const selectSelectedImage = (state) => state.app.selectedImage;
as your state has selectedImage field and not selectImage.
I use the API to get Quotes, but I get an Unhandled Rejection error "(TypeError): this.props.message.map is not a function" https://ibb.co/dWqhjXK, I used debugger checked props saw that I get answers https://ibb.co/wM3mLb9 what then could be the reason? why am i getting this error? here is my code
Message.jsx
import React from 'react';
export class Message extends React.Component {
render() {
const list = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.content}</p>
</div>
});
return (
<div>
<p>{list}</p>
</div>
);
}
}
MessageContainer.js
import React from 'react';
import {connect} from "react-redux";
import {Message} from "./Message";
import {getMessageThunk} from "../../redux/message-reducer";
class MessageContainer extends React.Component {
componentDidMount() {
this.props.getMessageThunk();
}
render() {
return <Message {...this.props} />
}
}
const mapStateToProps = (state) => ({
message: state.message.users
})
export default connect(mapStateToProps, {getMessageThunk})(MessageContainer);
message-reducer.js
import {messageAPI} from "../Api/Api";
const MESSAGE = 'MESSAGE';
let initialState = {
users: [],
};
export const messageReducer = (state = initialState, action) => {
switch (action.type) {
case MESSAGE: {
return {...state, users: action.users}
}
default:
return state;
}
}
export const messageCreator = (users) => {
return {
type: MESSAGE, users
}
};
export const getMessageThunk = (users) => (dispatch) => {
messageAPI.getMessageAPI(users).then(response => {
dispatch(messageCreator(response.data));
})
}
Api.js
import * as axios from "axios";
const instance = axios.create({
withCredentials: true,
url: 'https://quotes15.p.rapidapi.com/quotes/random/',
headers: {
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'quotes15.p.rapidapi.com'
}
});
export const messageAPI = {
getMessageAPI() {
return instance.get(`https://quotes15.p.rapidapi.com/quotes/random/`)
},
};
Your message is not an array but an object. You do not have anything to iterate over there you could either simply return this.props.message.content but this is not a list.
const item = <p>{this.props.message.content}</p>
I've created a simple to do app using React. I've attempted to persist state using local storage. However, the local storage code I've added is somehow preventing my components from rendering altogether. Not only are the todos saved in state not appearing, none of my components will render. I get a blank page on refresh. Can someone help me figure out what's wrong?
Here's what happens on the initial save after the local storage code is included. It loads the components just fine, but the to dos that are already in state are not shown:
After using the form to add to dos and refreshing the page, this happens. None of the components are shown whatsoever. Just a blank page.
Here is the local storage code inside my index.js file. I'm pretty sure the problem is here but I have included the code for the other components and the reducer as well:
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
The index.js file in its entirety:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";
import { Provider } from "react-redux";
import './index.css';
import App from './App';
import { reducer } from "./reducers/todoReducer";
import * as serviceWorker from './serviceWorker';
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
the other components:
TodoList.js:
import Todo from "./Todo";
const TodoList = props => {
return (
<ul className="task-list">
{props.state.map(task => (
<Todo task={task} />
))}
</ul>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps)(TodoList);
TodoForm.js:
const TodoForm = props => {
const [newItemText, setNewItemText] = useState("");
const handleChanges = e => {
e.preventDefault();
setNewItemText(e.target.value);
};
const saveState = () => localStorage.setItem("props.state", JSON.stringify(props.state));
useEffect(() => {
const todos = localStorage.getItem('state');
if (todos) props.setState({ [props.state]: JSON.parse(props.state) })
}, [])
return (
<div className="form-div">
<input
className="add-input"
name="todo"
type="text"
placeholder="enter a task"
value={newItemText}
onChange={handleChanges}
/>
<button
className="add-button"
onClick = {e => {
e.preventDefault();
props.addItem(newItemText);
saveState();
}}>Add a Task
</button>
<button
className="add-button"
onClick={e => {
e.preventDefault();
props.removeCompleted();
}}>Remove Completed
</button>
</div>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {addItem, removeCompleted})(TodoForm);
Todo.js:
const Todo = props => {
return (
<li
className="tasks"
style={{textDecoration: props.task.completed ? 'line-through' : 'none'}}
onClick={() => props.toggleCompleted(props.task.id)}>
{props.task.item}
</li>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {toggleCompleted})(Todo);
todoReducer.js:
export const initialState = [
{ item: 'Learn about reducers', completed: false, id: 1 },
{ item: 'review material from last week', completed: false, id: 2 },
{ item: 'complete reducer todo project', completed: false, id: 3 }
]
export const reducer = (state = initialState, action) => {
switch(action.type) {
case ADD_ITEM:
// console.log(action.payload)
return [
...state,
{
item: action.payload,
completed: false,
id: Date.now()
}
]
case TOGGLE_COMPLETED:
const toggledState = [...state];
toggledState.map(item => {
if(item.id === action.payload) {
item.completed = !item.completed;
}
})
console.log(toggledState);
state = toggledState;
return state;
case REMOVE_COMPLETED:
return state.filter(item => !item.completed);
default:
return state;
}
}
export default reducer;
App.js:
import React from 'react';
import './App.css';
// components
import TodoList from "./components/TodoList";
import TodoForm from "./components/TodoForm";
function App() {
return (
<div className="App">
<h1 className="title">To Do List</h1>
<TodoList />
<TodoForm />
</div>
);
}
export default App;
actions.js:
export const ADD_ITEM = 'ADD_ITEM';
export const TOGGLE_COMPLETED = 'TOGGLE_COMPLETED';
export const REMOVE_COMPLETED = 'REMOVE_COMPLETED';
export const addItem = input => {
return {
type: ADD_ITEM, payload: input
}
};
export const toggleCompleted = (id) => {
return {
type: TOGGLE_COMPLETED, payload: id
}
};
export const removeCompleted = () => {
return {
type: REMOVE_COMPLETED
}
};
CounterContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropType from 'prop-types';
import Counter from '../components/Counter';
import * as counterActions from '../store/modules/counter';
import * as postActions from '../store/modules/post';
class CounterContainer extends Component {
handleIncrement = () => {
const { CounterActions } = this.props;
CounterActions.increment();
}
handleDecrement = () => {
const { CounterActions } = this.props;
CounterActions.decrement();
}
addDummy = () => {
const { PostActions } = this.props;
console.log(PostActions);
PostActions.addDummy({
content: 'dummy',
userUID: 123,
});
}
render() {
const { handleIncrement, handleDecrement, addDummy } = this;
const { number } = this.props;
return (
<Counter
onIncrement={handleIncrement}
onDecrement={handleDecrement}
addDummy={addDummy}
number={number}
/>
);
}
}
CounterContainer.propTypes = {
number: PropType.number.isRequired,
CounterActions: PropType.shape({
increment: PropType.func,
decrement: PropType.func,
}).isRequired,
};
export default connect(
state => ({
number: state.counter.number,
}),
dispatch => ({
CounterActions: bindActionCreators(counterActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch),
}),
)(CounterContainer);
PostContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// import { ListItem } from 'react-native-elements';
import { Text } from 'react-native';
import PropType from 'prop-types';
import Post from '../components/Post';
import * as postActions from '../store/modules/post';
class PostContainer extends Component {
refreshing = () => {}
onRefresh = () => {}
renderItem = ({ item }) => (<Text>{item.content}</Text>)
render() {
const { renderItem } = this;
const { postList } = this.props;
return (
<Post
postList={postList}
renderItem={renderItem}
/>
);
}
}
export default connect(
state => ({
postList: state.post.postList,
}),
dispatch => ({
CounterActions: bindActionCreators(postActions, dispatch),
}),
)(PostContainer);
modules/post
import { createAction, handleActions } from 'redux-actions';
const initialState = {
postList: [{
content: 'test',
userUID: 123,
},
{
content: '123123',
userUID: 123123,
},
],
};
const ADD_DUMMY = 'ADD_DUMMY';
export const addDummy = createAction(ADD_DUMMY, ({ content, userUID }) => ({ content, userUID }));
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.data, ...state.postList],
}),
}, initialState);
export default reducer;
Clicking the button adds a dummy to the postList.
However, when I click the button, I get
TypeError: Can not read property 'content' of undefined error.
I thought I made it the same as the count-up down tutorial.
But Count Up Down works.
Adding a dummy I made does not work.
Where did it go wrong?
Until I click the Add Dummy Data button
The list is worked.
i change action.data -> actions.payload
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.payload, ...state.postList],
}),
}, initialState);
It is simply a mistake.