I am a beginner in react.js. I am making a board game as a beginner project. There is a component which rolls the dice. When the dice is rolled, an action is dispatched so that the state is changed to reflect values of that rolled dice. But in the reducer, both the values of state and action.data are same. How is that possible?
I have a dice rolling component which return this:
return (
<div>
<button
onClick={this.rollDice.bind(this)}
>
Roll dice!
</button>
<TurnDisplay dispatch={this.dispatch} />
<BoardContainer dispatch={this.dispatch} />
</div>
);
When you click the button, an action is dispatched, which is supposed update the state by adding dice values in it.
rollDice(){
console.log("this.props ||||| rollDice()", this.props);
if(this.props.isDiceRolled){
alert("Move the marker...");
return;
}
this.props.dispatch({
type: "ROLL_DICE",
data: {
dice: Math.ceil(Math.random() * 5),
isDiceRolled: true
}
});
}
In reducer:
switch(action.type){
.
.
.
case "ROLL_DICE": {
console.log("ROLL_DICE");
console.log(state, action.data);
const newTurn = Object.assign(state.currentTurn, action.data);
return Object.assign(state, { currentTurn: newTurn });
}
default: {
return state;
}
}
With Redux, in the reducers, every time you have to return a new state.
So instead of these two lines:
const newTurn = Object.assign(state.currentTurn, action.data);
return Object.assign(state, { currentTurn: newTurn });
You should write something like this
const newTurn = Object.assign({}, state.currentTurn, action.data);
return Object.assign(
{},
state,
{ currentTurn: newTurn}
)
The problem should be with Object.assign. Read more here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Related
So I have this sidebar component where I load my store and my dispatcher
//select
const mapStateToProps = state => {
return { renderedEl: state.renderedEl }
}
function mapDispatchToProps(dispatch) {
return{
renderLayoutElement: element => dispatch(renderLayoutElement(element))
}
}
Then inside the same component this Is how I trigger the dispatcher
renderEl = (el) => {
var elementName = el.target.getAttribute('id');
var renderedElements = this.props.renderedEl; //this is data from the store
for (let key in renderedElements) {
if (key == elementName) {
renderedElements[key] = true
}
}
this.props.renderLayoutElement({renderedElements});
}
Then as I understand it gets sent to the reducer
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
const initialState = {
renderedEl: {
heimdall: false,
skadi: false,
mercator: false
}
}
function rootReducer(state = initialState, action){
if(action.type === RENDER_LAYOUT_ELEMENT){
return Object.assign({},state,{
renderedEl: state.renderedEl.concat(action.payload)
})
}
return state
}
export default rootReducer;
This is its action
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
export function renderLayoutElement(payload) {
return { type: RENDER_LAYOUT_ELEMENT, payload }
};
Now the thing is. Im receiving a
state.renderedEl.concat is not a function at rootreducer / at dispatch
I dont understand why does that happen.
Becuase, actually the store gets updated as I can see, but the console returns that error. And I have to reload the render that uses the props of that store (with an onhover) in order to be able to see the changes. It doesnt happen automatically as it would happen with a state
if(action.type === RENDER_LAYOUT_ELEMENT){
return { ...state, renderedEl: { ...state.renderedEl, ...action.payload } };
}
Duplicate from comments maybe it can be helpful to someone else :)
In optimizing my component for performance, I noticed that sequential shouldComponentUpdate calls were seemingly missing the prop change from my redux store. An example being:
In props:
uiBehavior: {
shouldShow: false,
}
Redux Action:
fireActionToShow()
Redux Reducer:
case ActionType.UPDATE_UI_BEHAVIOR: return {...state, shouldShow: true}
Then I'm seeing:
shouldComponentUpdate(nextProps) {
// Would expect to at some point see:
this.props.shouldShow === false
nextProps === true
// Instead, only seeing
this.props.shouldShow === false
nextProps === false
// then
this.props.shouldShow === true
nextProps === true
}
** Note that this clearly isn't the code, just an example
It seems to me that a prop change isn't causing a rerender attempt, or am I missing something?
Thanks.
Expanding for clarity, here is some of the real code:
*** the event in the Action updates the uiBehavior prop on the redux store.
componentDidUpdate(prevProps, prevState) {
const { uiBehavior } = this.props;
if (!_.isEqual(uiBehavior.lockAttributes, prevProps.uiBehavior.lockAttributes)) {
console.log('Lock has changed.'); // This never gets called
}
}
const mapStateToProps = function(state){
return {
uiBehavior: state.uiBehavior,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SlotMachine)
*** UPDATE
JS Call:
this.props.setGeneralUiKeyValue('lockAttributesInCart', !lockAttributes);
Action:
export const setGeneralUiKeyValue = (key, value) => { return { type: GENERAL_UI_UPDATE, key, value }}
Reducer:
export const uiBehavior = (state = {}, action) => {
let newUiState = {...defaultState, ...state}
switch (action.type) {
case uiActionTypes.GENERAL_UI_UPDATE:
newUiState.general = newUiState.general || {};
newUiState.general[action.key] = action.value;
return newUiState
default:
return newUiState;
}
return newUiState
My app successfully gets API data and puts it to Redux state tree.
{
"coord":{
"lon":-0.13,
"lat":51.51
},
"weather":[
{
"id":311,
"main":"Drizzle",
"description":"drizzle rain",
"icon":"09d"
},
{
"id":501,
"main":"Rain",
"description":"moderate rain",
"icon":"10d"
}
],
//--------
//--------
"id":2643741,
"name":"London",
"cod":200
}
Props.data has been passed to components but in reality I have an access only to the
first key. For example props.data.name, props.data.id are accesiible. But props.data.coord.lon
and props.data.weather.map(---), are undefined.
Please, what's wrong with my understanding of using API dataset?
Component
export const DayItem = (props) => {
return (<MuiThemeProvider>
<Paper zDepth={2}>
{props.data.coord.lon} // No way!
{props.data.name} // OK!
</Paper>
</MuiThemeProvider>)}
Saga that gets data and dispatches an action. Puts data to Redux store.
function* getPosition() {
const getCurrentPosition = () => new Promise(
(res, rej) => navigator.geolocation.getCurrentPosition(res, rej))
// Gets user's current position assigned to const
const pos = yield call(getCurrentPosition);
const {latitude, longitude} = pos.coords;
// Yields the forecast API by user coordinates
const data = yield call(getForecastByCoords, latitude, longitude)
// Yields user's local forecast to the reducer
yield put({
type: LOAD_DATA_SUCCESS,
data
});
}
And mapStateToProps
function mapStateToProps(state) {
return {
chips: state.chipsReducer,
data: state.dataReducer
}
};
dataReducer
export const dataReducer = (state = {}, action) => {
switch (action.type) {
case LOAD_DATA_SUCCESS:
return action.data;
default:
return state;
}
};
Eventually, I got the point.
The problem was in the difference of speed React rendering vs Data loading.
Loading is always behind rendering. So, the complete set of data had no existence.
Just conditional rendering made my day {this.state.isLoading ? <div>Loading</div> : <DayItem {...this.props}/>}
you must use MapStateToProps, then use componentWillReceiveProps(nextProps)
Something like this
function mapStateToProps(state) {
return {
data:state.toJS().yourReducer.data,
}
then do next:
componentWillReceiveProps(nextProps) {
if (nextProps.data) {
if (nextProps.data.coord.lon != undefined) {
this.setState({yourData:nextProps.data.coord.lon}
}
}
Background
I am working on my first project using Redux. I am trying to sort data in a reducer. The reducer has 2 sort methods. One for sorting data based on the timeStamp and one by the VoteScore.
Problem
The reducer fires off, I can console.log() the data and see it is being passed to the correct case when sort is fired off, but the data does not sort on the screen.
**
I can see when I console.log the data that the data is sorting. So it
is just not rerendering.
**
Example of Data
(2) [{…}, {…}]
{timestamp: 1468479767190, voteScore: -5, …}
{timestamp: 1467166872634, voteScore: 6, …}
Example Reducer
function posts(state = [], action) {
switch(action.type) {
case 'SORT_POSTS': {
switch (action.attribute) {
case 'BY_TIMESTAMP':
return state.sort(function(a, b) {
return a.timestamp - b.timestamp;
});
case 'BY_VOTESCORE':
return state.sort(function(a, b) {
return a.voteScore - b.voteScore;
});
default:
return state;
}
}
case SET_POSTS:
return action.posts;
default:
return state;
}
}
Example Dispatch & onClick Method
<button onClick={() => this.props.boundSortPosts(this.state.timeStamp)}>Click MeK</button>
const mapDispatchToProps = (dispatch) => ({
boundSortPosts: (attribute) => dispatch(sortPosts(attribute))
});
Example Action
export function setPosts(posts) {
return {
type: SET_POSTS,
posts,
}
}
export function sortPosts(attribute) {
return {
type: SORT_POSTS,
attribute,
}
}
Initially Rendering Data Like This
render() {
return (
<div>
<Posts />
<div><button onClick={() => this.props.boundSortPosts(this.state.timeStamp)}>Sort by Time Stamp</button></div>
<div><button onClick={() => this.props.boundSortPosts(this.state.voteScore)}>Sort by Vote Score</button></div>
{
this.props.posts.map((post, index) => {
return (
<div key={index}>
<h4>{post.timestamp}</h4>
<h4>{post.voteScore}</h4>
</div>
)
})
}
</div>
)
}
Question
What must I do to cause a rerender at this point?
First and very important thing is you should not mutate your state. The sort() method sorts the elements of an array in place and returns the array.You can create a copy of the state array by calling state.slice() before sorting in your reducer, something like this :
return state.slice().sort(function(a, b) {
return a.timestamp - b.timestamp;
});
Regarding your component not re-rendered, please check your mapStateToProps. It should be something like this:
function mapStateToProps(state} {
return {
posts: state.posts // posts is reducer which is passed to your store.
}
}
I know there are a lot of similar questions, but I was unable to find the right answer sifting through the others. The issue seems to be that {loopToDo} does not directly reference a prop from the store. How can I set my code up so that it updates when the store changes, like I want it to?
#connect((germzFirstStore) => {
return {
taskList: germzFirstStore.tasks
}
})
class TaskBoard extends React.Component {
render() {
function toDoStatus(value) {
return value.taskstatus === "toDo";
}
var toDoTasks = this.props.taskList.tasks.filter(toDoStatus);
var loopToDo = toDoTasks.map((tasksEntered) => {
return (
<div id={tasksEntered.idtasks} className="taskBox">{tasksEntered.task}</div>
);
});
return(
<div ref="toDo" id="toDo" className="container toDo">{loopToDo}</div>
)
}
}
the reducer:
const tasksReducer = (state=tasksInitialState, action) => {
if (action.type === "ADD") {
state = {...state, tasks: [...state.tasks, action.newTask]}
}
return state; }
The problem here is that by doing this
state = {...state, tasks: [...state.tasks, action.newTask]}
You are effectively mutating the state before returning it and that probably is the reason why your components are not re-rendering on updating state.
What you can do in your reducer is
if (action.type === "ADD") {
return {...state, tasks: [...state.tasks, action.newTask]}
}
or
if (action.type === "ADD") {
return Object.assign({}, state, {tasks: [...state.tasks, action.newTask]})
}
Hope it helps :)
If germzFirstStore.tasks has updated then the component will re-render it has nothing to do with what is inside the render function. My guess is that you are mutating the state in your reducer instead of updating it and returning the updated version of the state.